diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsAPITemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsAPITemplate.java index 79cec7f8..58933c0e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsAPITemplate.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsAPITemplate.java @@ -79,7 +79,7 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm private OlatLmsRestTemplate cachedRestTemplate; - protected OlatLmsAPITemplate( + public OlatLmsAPITemplate( final ClientHttpRequestFactoryService clientHttpRequestFactoryService, final ClientCredentialService clientCredentialService, final APITemplateDataSupplier apiTemplateDataSupplier, @@ -344,8 +344,8 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm final RestrictionDataPost post = new RestrictionDataPost(); post.browserExamKeys = new ArrayList<>(restriction.browserExamKeys); post.configKeys = new ArrayList<>(restriction.configKeys); - post.quitLink = restriction.getAdditionalProperties().getOrDefault(ADDITIONAL_ATTR_QUIT_LINK, null); - post.quitSecret = restriction.getAdditionalProperties().getOrDefault(ADDITIONAL_ATTR_QUIT_SECRET, null); + post.quitLink = this.getQuitLink(restriction.examId); + post.quitSecret = this.getQuitSecret(restriction.examId); final RestrictionData r = this.apiPost(restTemplate, url, post, RestrictionDataPost.class, RestrictionData.class); return new SEBRestriction(Long.valueOf(id), r.configKeys, r.browserExamKeys, new HashMap()); @@ -370,7 +370,6 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm final Exam exam, final SEBRestriction sebRestrictionData) { - populateWithQuitLinkAndSecret(exam, sebRestrictionData); return getRestTemplate() .map(t -> this.setRestrictionForAssignmentId(t, exam.externalId, sebRestrictionData)); } @@ -403,8 +402,13 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm return res.getBody(); } - private R apiPost(final RestTemplate restTemplate, final String url, final P post, final Class

postType, + private R apiPost( + final RestTemplate restTemplate, + final String url, + final P post, + final Class

postType, final Class responseType) { + final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup(); final HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.set("content-type", "application/json"); @@ -459,36 +463,42 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm }); } - private void populateWithQuitLinkAndSecret(final Exam exam, final SEBRestriction sebRestrictionData) { + private String getQuitSecret(final Long examId) { try { - final String quitLink = this.examConfigurationValueService.getMappedDefaultConfigAttributeValue( - exam.id, - CONFIG_ATTR_NAME_QUIT_LINK); - - final String quitSecret = this.examConfigurationValueService.getMappedDefaultConfigAttributeValue( - exam.id, + final String quitSecretEncrypted = this.examConfigurationValueService.getMappedDefaultConfigAttributeValue( + examId, CONFIG_ATTR_NAME_QUIT_SECRET); - if (StringUtils.isNotEmpty(quitLink)) { - sebRestrictionData.additionalProperties.put(ADDITIONAL_ATTR_QUIT_LINK, quitLink); - } - - if (StringUtils.isNotEmpty(quitSecret)) { + if (StringUtils.isNotEmpty(quitSecretEncrypted)) { try { - final String decryptedSecret = this.cryptor - .encrypt(quitSecret) + + return this.cryptor + .decrypt(quitSecretEncrypted) .getOrThrow() .toString(); - sebRestrictionData.additionalProperties.put(ADDITIONAL_ATTR_QUIT_SECRET, decryptedSecret); } catch (final Exception e) { log.error("Failed to decrypt quitSecret: ", e); } } + } catch (final Exception e) { + log.error("Failed to get SEB restriction with quit secret: ", e); + } + + return null; + } + + private String getQuitLink(final Long examId) { + try { + + return this.examConfigurationValueService.getMappedDefaultConfigAttributeValue( + examId, + CONFIG_ATTR_NAME_QUIT_LINK); } catch (final Exception e) { - log.error("Failed to populate SEB restriction with quit link and quit secret: ", e); + log.error("Failed to get SEB restriction with quit link: ", e); + return null; } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsData.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsData.java index 5897f1f1..c507b66d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsData.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsData.java @@ -55,7 +55,7 @@ public final class OlatLmsData { } @JsonIgnoreProperties(ignoreUnknown = true) - static final class RestrictionData { + public static final class RestrictionData { /* * OLAT API example: * { @@ -74,7 +74,7 @@ public final class OlatLmsData { } @JsonIgnoreProperties(ignoreUnknown = true) - static final class RestrictionDataPost { + public static final class RestrictionDataPost { /* * OLAT API example: * { diff --git a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/ExamConfigurationValueServiceTest.java b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/ExamConfigurationValueServiceTest.java new file mode 100644 index 00000000..1dda0151 --- /dev/null +++ b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/ExamConfigurationValueServiceTest.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2022 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.webservice.integration.api.admin; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.jdbc.Sql; + +import ch.ethz.seb.sebserver.gbl.util.Cryptor; +import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamConfigurationValueService; + +@Sql(scripts = { "classpath:schema-test.sql", "classpath:data-test.sql", "classpath:data-test-additional.sql" }) +public class ExamConfigurationValueServiceTest extends AdministrationAPIIntegrationTester { + + @Autowired + private ExamConfigurationValueService examConfigurationValueService; + @Autowired + private Cryptor cryptor; + + @Test + public void testGetConfigValues() { + final String allowQuit = this.examConfigurationValueService + .getMappedDefaultConfigAttributeValue(2L, "allowQuit"); + + assertNotNull(allowQuit); + assertEquals("true", allowQuit); + + final String hashedQuitPassword = this.examConfigurationValueService + .getMappedDefaultConfigAttributeValue(2L, "hashedQuitPassword"); + + assertNotNull(hashedQuitPassword); + assertEquals( + "e6494b842987b4e039a101f14d4a76acc338d33205336f2562c7d8071e3ed65886edbbe3b71a4a33cc09c6", + hashedQuitPassword); + + final CharSequence plainQuitPassword = this.cryptor.decrypt(hashedQuitPassword).getOrThrow(); + assertEquals("123", plainQuitPassword); + } + +} diff --git a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/OlatLmsAPITemplateTest.java b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/OlatLmsAPITemplateTest.java new file mode 100644 index 00000000..bcb2071b --- /dev/null +++ b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/OlatLmsAPITemplateTest.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2022 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.webservice.integration.api.admin; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.lang.reflect.Field; +import java.util.Arrays; + +import org.junit.Test; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.CacheManager; +import org.springframework.http.HttpEntity; +import org.springframework.test.context.jdbc.Sql; + +import ch.ethz.seb.sebserver.gbl.Constants; +import ch.ethz.seb.sebserver.gbl.model.exam.Exam; +import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus; +import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamType; +import ch.ethz.seb.sebserver.gbl.model.exam.SEBRestriction; +import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup; +import ch.ethz.seb.sebserver.gbl.util.Cryptor; +import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamConfigurationValueService; +import ch.ethz.seb.sebserver.webservice.servicelayer.lms.APITemplateDataSupplier; +import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.olat.OlatLmsAPITemplate; +import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.olat.OlatLmsData.RestrictionDataPost; +import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.olat.OlatLmsRestTemplate; + +@Sql(scripts = { "classpath:schema-test.sql", "classpath:data-test.sql", "classpath:data-test-additional.sql" }) +public class OlatLmsAPITemplateTest extends AdministrationAPIIntegrationTester { + + @Autowired + private ExamConfigurationValueService examConfigurationValueService; + @Autowired + private Cryptor cryptor; + @Autowired + private CacheManager cacheManager; + + @Test + public void testSetRestriction() throws Exception { + + final OlatLmsRestTemplate restTemplateMock = Mockito.mock(OlatLmsRestTemplate.class); + final APITemplateDataSupplier apiTemplateDataSupplier = Mockito.mock(APITemplateDataSupplier.class); + Mockito.when(apiTemplateDataSupplier.getLmsSetup()).thenReturn(new LmsSetup( + 1L, 1L, null, null, null, null, null, null, null, null, null, null, false, null)); + + final OlatLmsAPITemplate olatLmsAPITemplate = new OlatLmsAPITemplate( + null, + null, + apiTemplateDataSupplier, + this.examConfigurationValueService, + this.cryptor, + this.cacheManager); + + Mockito.when(restTemplateMock.exchange(Mockito.any(), Mockito.any(), Mockito.any(), + (Class) Mockito.any(), (Object[]) Mockito.any())).then(new Answer() { + + @Override + public Object answer(final InvocationOnMock invocation) throws Throwable { + final HttpEntity argument2 = invocation.getArgument(2, HttpEntity.class); + assertNotNull(argument2); + final RestrictionDataPost body = argument2.getBody(); + assertNotNull(body); + assertEquals("seb://quitlink.seb", body.quitLink); + assertEquals("123", body.quitSecret); + return null; + } + + }); + + final Field field = OlatLmsAPITemplate.class.getDeclaredField("cachedRestTemplate"); + field.setAccessible(true); + field.set(olatLmsAPITemplate, restTemplateMock); + + final Exam exam = new Exam( + 2L, + 1L, + 1L, + Constants.EMPTY_NOTE, + false, + Constants.EMPTY_NOTE, + null, + null, + ExamType.UNDEFINED, + null, + null, + ExamStatus.FINISHED, + Boolean.FALSE, + null, + Boolean.FALSE, + null, + null, + null, + null); + + final SEBRestriction sebRestriction = new SEBRestriction( + 2L, + Arrays.asList("configKey1", "configKey2"), + Arrays.asList("browserKey1", "browserKey2"), + null); + + olatLmsAPITemplate.applySEBClientRestriction(exam, sebRestriction); + + } + +} diff --git a/src/test/resources/data-test-additional.sql b/src/test/resources/data-test-additional.sql index 377469b1..7f533a6d 100644 --- a/src/test/resources/data-test-additional.sql +++ b/src/test/resources/data-test-additional.sql @@ -539,7 +539,7 @@ INSERT IGNORE INTO configuration_value VALUES (1,1,1,1,0,NULL), (2,1,1,2,0,'true'), (3,1,1,3,0,'false'), - (4,1,1,4,0,NULL), + (4,1,1,4,0,'e6494b842987b4e039a101f14d4a76acc338d33205336f2562c7d8071e3ed65886edbbe3b71a4a33cc09c6'), (5,1,1,5,0,'2'), (6,1,1,6,0,'10'), (7,1,1,7,0,'5'), @@ -602,7 +602,7 @@ INSERT IGNORE INTO configuration_value VALUES (64,1,1,64,0,'true'), (65,1,1,65,0,'true'), (66,1,1,66,0,'true'), - (67,1,1,67,0,NULL), + (67,1,1,67,0,'seb://quitlink.seb'), (68,1,1,68,0,'true'), (69,1,1,69,0,'false'), (70,1,1,70,0,NULL),