diff --git a/pom.xml b/pom.xml index a6d3caf1..c1e57cd2 100644 --- a/pom.xml +++ b/pom.xml @@ -16,7 +16,7 @@ jar - 1.6.0 + 1.6.1 ${sebserver-version} ${sebserver-version} UTF-8 diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserFeatures.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserFeatures.java index cd95dbbd..d98ec9ea 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserFeatures.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserFeatures.java @@ -35,7 +35,7 @@ public class UserFeatures { LMS_SETUP_MOODLE_PLUGIN("lms.setup.type.MOODLE_PLUGIN"), LMS_SETUP_OPEN_EDX("lms.setup.type.OPEN_EDX"), LMS_SETUP_ANS("lms.setup.type.ANS_DELFT"), - LMS_SETUP_OPEN_OLAT("lms.setup.type.OPEN_OLAT"), + LMS_SETUP_OLAT("lms.setup.type.OLAT"), QUIZ_LOOKUP("lms.quiz.lookup"), diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBClientConfigForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBClientConfigForm.java index 369bfb2b..5f85cfea 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBClientConfigForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBClientConfigForm.java @@ -416,7 +416,7 @@ public class SEBClientConfigForm implements TemplateComposer { SEBClientConfig.ATTR_EXAM_SELECTION, FORM_EXAM_SELECTION_TEXT_KEY, StringUtils.join(clientConfig.selectedExams, Constants.LIST_SEPARATOR), - () -> pageService.getResourceService().getExamLogSelectionResources()) + () -> pageService.getResourceService().getActiveExamResources()) .withInputSpan(5)) .withDefaultSpanEmptyCell(1); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java index c12c7892..3c31a084 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java @@ -717,6 +717,24 @@ public class ResourceService { .collect(Collectors.toList()); } + public List> getActiveExamResources() { + final UserInfo userInfo = this.currentUser.get(); + return this.restService.getBuilder(GetExams.class) + .withQueryParam(Entity.FILTER_ATTR_INSTITUTION, String.valueOf(userInfo.getInstitutionId())) + .withQueryParam(Exam.FILTER_CACHED_QUIZZES, Constants.TRUE_STRING) + .call() + .getOr(Collections.emptyList()) + .stream() + .filter(exam -> exam != null && + exam.getStatus() != ExamStatus.FINISHED && + exam.getStatus() != ExamStatus.ARCHIVED) + .map(exam -> new Tuple<>( + exam.getModelId(), + StringUtils.isBlank(exam.name) ? exam.externalId : exam.name)) + .sorted(RESOURCE_COMPARATOR) + .collect(Collectors.toList()); + } + public List> getExamResources() { final UserInfo userInfo = this.currentUser.get(); return this.restService.getBuilder(GetExamNames.class) diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/datalayer/checks/ClipboardSEBSettingsGUICheck.java b/src/main/java/ch/ethz/seb/sebserver/webservice/datalayer/checks/ClipboardSEBSettingsGUICheck.java new file mode 100644 index 00000000..0461cae4 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/datalayer/checks/ClipboardSEBSettingsGUICheck.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2019 ETH Zürich, IT Services + * + * 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.datalayer.checks; + +import static ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.OrientationRecordDynamicSqlSupport.*; +import static ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.OrientationRecordDynamicSqlSupport.title; + +import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; +import ch.ethz.seb.sebserver.gbl.util.Result; +import ch.ethz.seb.sebserver.webservice.DBIntegrityCheck; +import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.OrientationRecordDynamicSqlSupport; +import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.OrientationRecordMapper; +import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.OrientationRecord; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.OrientationDAO; +import org.mybatis.dynamic.sql.SqlBuilder; +import org.mybatis.dynamic.sql.update.UpdateDSL; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +@Lazy +@Component +@WebServiceProfile +public class ClipboardSEBSettingsGUICheck implements DBIntegrityCheck { + + public static final Logger INIT_LOGGER = LoggerFactory.getLogger("ch.ethz.seb.SEB_SERVER_INIT"); + + private final OrientationRecordMapper orientationRecordMapper; + + public ClipboardSEBSettingsGUICheck(final OrientationRecordMapper orientationRecordMapper) { + this.orientationRecordMapper = orientationRecordMapper; + } + + @Override + public String name() { + return "ClipboardSEBSettingsGUICheck"; + } + + @Override + public String description() { + return "Check if clipboardPolicy SEB Setting is missing in the GUI and if so add it to GUI"; + } + + @Override + public Result applyCheck(boolean tryFix) { + return Result.tryCatch(() -> { + // check if clipboardPolicy SEB Setting is missing + final Long count = orientationRecordMapper.countByExample() + .where(templateId, SqlBuilder.isEqualTo(0L)) + .and(configAttributeId, SqlBuilder.isEqualTo(1201L)) + .build() + .execute(); + + if (count != null && count.intValue() > 0) { + return "clipboardPolicy SEB Setting detected in GUI"; + } + + INIT_LOGGER.info("--------> Missing clipboardPolicy SEB Setting in GUI detected. Add it"); + + // move allowedSEBVersion setting + UpdateDSL.updateWithMapper(orientationRecordMapper::update, orientationRecord) + .set(yPosition).equalTo(21) + .where(templateId, SqlBuilder.isEqualTo(0L)) + .and(configAttributeId, SqlBuilder.isEqualTo(1578L)) + .build() + .execute(); + + // add clipboardPolicy setting + orientationRecordMapper.insert(new OrientationRecord( + null, + 1201L, + 0L, + 9L, + "clipboardPolicy", + 7, + 18, + 5, + 2, + "NONE" + )); + + return "Missing clipboardPolicy SEB Setting in GUI successfully added"; + }); + } +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/datalayer/checks/DowngradeSEBSettingsCheck.java b/src/main/java/ch/ethz/seb/sebserver/webservice/datalayer/checks/DowngradeSEBSettingsCheck.java index 7ed8a967..bb234a77 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/datalayer/checks/DowngradeSEBSettingsCheck.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/datalayer/checks/DowngradeSEBSettingsCheck.java @@ -103,7 +103,7 @@ public class DowngradeSEBSettingsCheck implements DBIntegrityCheck { .map(OrientationRecord::getConfigAttributeId) .collect(Collectors.toList()); - if (attributeIds.isEmpty()) { + if (attributeIds.isEmpty() || attributeIds.get(0).intValue() == 1201) { return "No additional SEB Settings orientations for downgrading found."; } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleUtils.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleUtils.java index e2750f4b..83cf6d0f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleUtils.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleUtils.java @@ -56,13 +56,23 @@ public abstract class MoodleUtils { private static String maskShortName(final String shortname) { return shortname .replace(Constants.SEMICOLON.toString(), "_SC_") - .replace(Constants.COLON.toString(), "_COLON_"); + .replace(Constants.COLON.toString(), "_COLON_") + .replace(Constants.SLASH.toString(), "_SL_") + .replace(Constants.BACKSLASH.toString(), "_BSL_") + .replace(Constants.AMPERSAND.toString(), "_AMP_") + .replace(Constants.ANGLE_BRACE_OPEN.toString(), "_AO_") + .replace(Constants.ANGLE_BRACE_CLOSE.toString(), "_AC_"); } private static String unmaskShortName(final String shortname) { return shortname .replace("_SC_", Constants.SEMICOLON.toString()) - .replace("_COLON_", Constants.COLON.toString()); + .replace("_COLON_", Constants.COLON.toString()) + .replace("_SL_", Constants.SLASH.toString()) + .replace("_BSL_", Constants.BACKSLASH.toString()) + .replace("_AMP_", Constants.AMPERSAND.toString()) + .replace("_AO_", Constants.ANGLE_BRACE_OPEN.toString()) + .replace("_AC_", Constants.ANGLE_BRACE_CLOSE.toString()); } public static String getQuizId(final String internalQuizId) { 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 589f6763..922a0900 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 @@ -275,7 +275,7 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm final DateTime quizFromTime = (filterMap != null) ? filterMap.getQuizFromTime() : null; final long fromCutTime = (quizFromTime != null) ? Utils.toUnixTimeInSeconds(quizFromTime) : -1; - String url = "/restapi/assessment_modes/seb?"; + String url = "/restapi/repo/assessmentmodes?"; if (fromCutTime != -1) { url = String.format("%sdateFrom=%s&", url, fromCutTime); } @@ -295,8 +295,8 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm lmsSetup.getLmsType(), a.name, a.description, - Utils.toDateTimeUTC(a.dateFrom), - Utils.toDateTimeUTC(a.dateTo), + Utils.toDateTimeUTC(a.begin - a.leadTime * 1000 * 60), + Utils.toDateTimeUTC(a.end + a.followupTime * 1000 * 60), examUrl(a.repositoryEntryKey), new HashMap()); }) @@ -316,7 +316,7 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm private QuizData quizById(final OlatLmsRestTemplate restTemplate, final String id) { final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup(); - final String url = String.format("/restapi/assessment_modes/%s", id); + final String url = String.format("/restapi/repo/assessmentmodes/%s", id); final AssessmentData a = this.apiGet(restTemplate, url, AssessmentData.class); return new QuizData( String.format("%d", a.key), @@ -325,26 +325,26 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm lmsSetup.getLmsType(), a.name, a.description, - Utils.toDateTimeUTC(a.dateFrom), - Utils.toDateTimeUTC(a.dateTo), + Utils.toDateTimeUTC(a.begin - a.leadTime * 1000 * 60), + Utils.toDateTimeUTC(a.end + a.followupTime * 1000 * 60), examUrl(a.repositoryEntryKey), new HashMap()); } private ExamineeAccountDetails getExamineeById(final RestTemplate restTemplate, final String id) { - final String url = String.format("/restapi/users/%s/name_username", id); + final String url = String.format("/restapi/users/%s", id); final UserData u = this.apiGet(restTemplate, url, UserData.class); final Map attrs = new HashMap<>(); return new ExamineeAccountDetails( String.valueOf(u.key), u.lastName + ", " + u.firstName, - u.username, - "OLAT API does not provide email addresses", + u.login, + u.email, attrs); } private SEBRestriction getRestrictionForAssignmentId(final RestTemplate restTemplate, final String id) { - final String url = String.format("/restapi/assessment_modes/%s/seb_restriction", id); + final String url = String.format("/restapi/repo/assessmentmodes/%s/seb", id); final RestrictionData r = this.apiGet(restTemplate, url, RestrictionData.class); final HashMap additionalAttributes = new HashMap<>(); if (StringUtils.isNotBlank(r.quitLink)) { @@ -362,7 +362,7 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm final String id, final SEBRestriction restriction) { - final String url = String.format("/restapi/assessment_modes/%s/seb_restriction", id); + final String url = String.format("/restapi/repo/assessmentmodes/%s/seb", id); final RestrictionDataPost post = new RestrictionDataPost(); post.browserExamKeys = new ArrayList<>(restriction.browserExamKeys); post.configKeys = new ArrayList<>(restriction.configKeys); @@ -377,7 +377,7 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm } private SEBRestriction deleteRestrictionForAssignmentId(final RestTemplate restTemplate, final String id) { - final String url = String.format("/restapi/assessment_modes/%s/seb_restriction", id); + final String url = String.format("/restapi/repo/assessmentmodes/%s/seb", id); final RestrictionData r = this.apiDelete(restTemplate, url, RestrictionData.class); // OLAT returns RestrictionData with null values upon deletion. // We return it here for consistency, even though SEB server does not need it @@ -473,7 +473,7 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm .getOrThrow(); final ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails(); - details.setAccessTokenUri(lmsSetup.lmsApiUrl + "/restapi/auth/"); + details.setAccessTokenUri(lmsSetup.lmsApiUrl + "/restapi/auth/{username}?password={password}"); details.setClientId(plainClientId.toString()); details.setClientSecret(plainClientSecret.toString()); @@ -489,4 +489,5 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm }); } + } 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 7a7eeb2d..050e695a 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 @@ -20,22 +20,24 @@ public final class OlatLmsData { /* * OLAT API example: * { - * "courseName": "course 1", - * "dateFrom": 1624420800000, - * "dateTo": 1624658400000, + * "begin": 1624420800000, + * "end": 1624658400000, * "description": "", * "key": 6356992, * “repositoryEntryKey”: 462324, - * "name": "SEB test" + * "name": "SEB test", + * "leadTime": 15, + * "followupTime", 5 * } */ public long key; public long repositoryEntryKey; public String name; public String description; - public String courseName; - public long dateFrom; - public long dateTo; + public Long begin; + public Long end; + public long leadTime; + public long followupTime; } @JsonIgnoreProperties(ignoreUnknown = true) @@ -46,13 +48,15 @@ public final class OlatLmsData { * "firstName": "OpenOLAT", * "key": 360448, * "lastName": "Administrator", - * "username": "administrator" + * "login": "administrator", + * "email": "admin@example.org" * } */ public long key; public String firstName; public String lastName; - public String username; + public String login; + public String email; } @JsonIgnoreProperties(ignoreUnknown = true) diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsRestTemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsRestTemplate.java index 046c6f07..f2fd8601 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsRestTemplate.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsRestTemplate.java @@ -16,6 +16,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; import org.springframework.http.HttpRequest; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -116,14 +117,14 @@ public class OlatLmsRestTemplate extends RestTemplate { // Authenticate with OLAT and store the received X-OLAT-TOKEN this.token = "authenticating"; final String authUrl = this.details.getAccessTokenUri(); - final Map credentials = new HashMap<>(); - credentials.put("username", this.details.getClientId()); - credentials.put("password", this.details.getClientSecret()); + final Map parameters = new HashMap<>(); + parameters.put("username", this.details.getClientId()); + parameters.put("password", this.details.getClientSecret()); final HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.set("content-type", "application/json"); - final HttpEntity> requestEntity = new HttpEntity<>(credentials, httpHeaders); + final HttpEntity> requestEntity = new HttpEntity<>(httpHeaders); try { - final ResponseEntity response = this.postForEntity(authUrl, requestEntity, String.class); + final ResponseEntity response = this.exchange(authUrl, HttpMethod.GET, requestEntity, String.class, parameters); final HttpHeaders responseHeaders = response.getHeaders(); if (log.isDebugEnabled()) { diff --git a/src/main/resources/config/application-dev-ws.properties b/src/main/resources/config/application-dev-ws.properties index 15ae100f..bab5fe41 100644 --- a/src/main/resources/config/application-dev-ws.properties +++ b/src/main/resources/config/application-dev-ws.properties @@ -89,7 +89,7 @@ sebserver.feature.exam.seb.screenProctoring.bundled.sebserveraccount.username=SE #sebserver.feature.config.certificate.enabled=false # #sebserver.feature.lms.setup.type.ANS_DELFT.enabled=false -#sebserver.feature.lms.setup.type.OPEN_OLAT.enabled=false +#sebserver.feature.lms.setup.type.OLAT.enabled=false # #sebserver.feature.exam.ask.enabled=false #sebserver.feature.exam.seb.restriction.enabled=false diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 9e5395b1..c4eb5349 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -344,7 +344,7 @@ sebserver.lmssetup.type.MOODLE_PLUGIN=Moodle with SEB Server Plugin sebserver.lmssetup.type.MOODLE_PLUGIN.tooltip=Moodle with SEB Server integration plugin installed sebserver.lmssetup.type.OPEN_EDX=Open edX sebserver.lmssetup.type.ANS_DELFT=Ans Delft -sebserver.lmssetup.type.OPEN_OLAT=Open OLAT +sebserver.lmssetup.type.OPEN_OLAT=OLAT sebserver.lmssetup.list.actions= sebserver.lmssetup.list.action.no.modify.privilege=No Access: A Assessment Tool Setup from other institution cannot be modified. @@ -1942,6 +1942,7 @@ sebserver.examconfig.props.validation.SEBVersionValidator=At least one SEB Versi sebserver.examconfig.props.label.signature=Signature sebserver.examconfig.props.label.signature.tooltip=The hash / thumbprint of the certificate used to sign the executable. +sebserver.examconfig.props.group.clipboardPolicy=Clipboard Policy sebserver.examconfig.props.label.clipboardPolicy=Clipboard Policy sebserver.examconfig.props.label.clipboardPolicy.tooltip= sebserver.examconfig.props.label.clipboardPolicy.0=Allow