SEBSERV-75 added Moodle course restriction on SEB Server side

This commit is contained in:
anhefti 2020-07-23 16:11:40 +02:00
parent cfaaf964fe
commit 532ca816bc
7 changed files with 317 additions and 31 deletions

View file

@ -0,0 +1,65 @@
/*
* Copyright (c) 2020 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.gbl.model.exam;
import java.util.Collection;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import ch.ethz.seb.sebserver.gbl.util.Utils;
@JsonIgnoreProperties(ignoreUnknown = true)
public class MoodleSEBRestriction {
public static final String ATTR_BROWSER_KEYS = "BROWSER_KEYS";
public static final String ATTR_CONFIG_KEYS = "CONFIG_KEYS";
@JsonProperty(ATTR_CONFIG_KEYS)
public final Collection<String> configKeys;
@JsonProperty(ATTR_BROWSER_KEYS)
public final Collection<String> browserExamKeys;
@JsonCreator
protected MoodleSEBRestriction(
@JsonProperty(ATTR_CONFIG_KEYS) final Collection<String> configKeys,
@JsonProperty(ATTR_BROWSER_KEYS) final Collection<String> browserExamKeys) {
this.configKeys = Utils.immutableCollectionOf(configKeys);
this.browserExamKeys = Utils.immutableCollectionOf(browserExamKeys);
}
public Collection<String> getConfigKeys() {
return this.configKeys;
}
public Collection<String> getBrowserExamKeys() {
return this.browserExamKeys;
}
public static MoodleSEBRestriction from(final SEBRestriction sebRestrictionData) {
return new MoodleSEBRestriction(
sebRestrictionData.configKeys,
sebRestrictionData.browserExamKeys);
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append("MoodleSEBRestriction [configKeys=");
builder.append(this.configKeys);
builder.append(", browserExamKeys=");
builder.append(this.browserExamKeys);
builder.append("]");
return builder.toString();
}
}

View file

@ -173,4 +173,12 @@ public final class SEBRestriction implements Entity {
attrs); attrs);
} }
public static SEBRestriction from(final Long examId, final MoodleSEBRestriction restriction) {
return new SEBRestriction(
examId,
restriction.configKeys,
restriction.browserExamKeys,
null);
}
} }

View file

@ -133,7 +133,6 @@ final class OpenEdxLmsAPITemplate implements LmsAPITemplate {
return this.openEdxCourseRestriction.deleteSEBRestriction(exam.externalId) return this.openEdxCourseRestriction.deleteSEBRestriction(exam.externalId)
.map(result -> exam); .map(result -> exam);
} }
} }

View file

@ -0,0 +1,168 @@
/*
* Copyright (c) 2020 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.servicelayer.lms.impl.moodle;
import java.util.ArrayList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import com.fasterxml.jackson.core.type.TypeReference;
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
import ch.ethz.seb.sebserver.gbl.model.exam.MoodleSEBRestriction;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestTemplateFactory.MoodleAPIRestTemplate;
/** GET:
* http://yourmoodle.org/webservice/rest/server.php?wstoken={token}&moodlewsrestformat=json&wsfunction=seb_restriction&courseId=123
*
* Response (JSON):
* {"courseId"="123", "configKeys"=["key1","key2","key3",...], "browserKeys"=["bkey1", "bkey2", "bkey3",...]}
*
* Set keys:
* POST:
* http://yourmoodle.org/webservice/rest/server.php?wstoken={token}&moodlewsrestformat=json&wsfunction=seb_restriction&courseId=123&configKey[0]=key1&configKey[1]=key2&browserKey[0]=bkey1&browserKey[1]=bkey2
*
* Delete all key (and remove restrictions):
* POST:
* http://yourmoodle.org/webservice/rest/server.php?wstoken={token}&moodlewsrestformat=json&wsfunction=seb_restriction_delete&courseId=123 */
public class MoodleCourseRestriction {
private static final Logger log = LoggerFactory.getLogger(MoodleCourseRestriction.class);
private static final String MOODLE_DEFAULT_COURSE_RESTRICTION_WS_FUNCTION = "seb_restriction";
private static final String MOODLE_DEFAULT_COURSE_RESTRICTION_WS_FUNCTION_DELETE = "seb_restriction_delete";
private static final String MOODLE_DEFAULT_COURSE_RESTRICTION_COURSE_ID = "courseId";
private static final String MOODLE_DEFAULT_COURSE_RESTRICTION_CONFIG_KEY = "configKey";
private static final String MOODLE_DEFAULT_COURSE_RESTRICTION_BROWSER_KEY = "browserKey";
private final JSONMapper jsonMapper;
private final LmsSetup lmsSetup;
private final MoodleRestTemplateFactory moodleRestTemplateFactory;
private MoodleAPIRestTemplate restTemplate;
protected MoodleCourseRestriction(
final JSONMapper jsonMapper,
final LmsSetup lmsSetup,
final MoodleRestTemplateFactory moodleRestTemplateFactory) {
this.jsonMapper = jsonMapper;
this.lmsSetup = lmsSetup;
this.moodleRestTemplateFactory = moodleRestTemplateFactory;
}
LmsSetupTestResult initAPIAccess() {
// TODO test availability
return LmsSetupTestResult.ofQuizAccessAPIError("not available yet");
}
Result<MoodleSEBRestriction> getSEBRestriction(final String courseId) {
if (log.isDebugEnabled()) {
log.debug("GET SEB Client restriction on course: {}", courseId);
}
return Result.tryCatch(() -> {
final MoodleAPIRestTemplate template = getRestTemplate()
.getOrThrow();
final MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
queryParams.add(MOODLE_DEFAULT_COURSE_RESTRICTION_COURSE_ID, courseId);
final String resultJSON = template.callMoodleAPIFunction(
MOODLE_DEFAULT_COURSE_RESTRICTION_WS_FUNCTION,
queryParams,
null);
final MoodleSEBRestriction restrictiondata = this.jsonMapper.readValue(
resultJSON,
new TypeReference<MoodleSEBRestriction>() {
});
return restrictiondata;
});
}
Result<Boolean> putSEBRestriction(
final String courseId,
final MoodleSEBRestriction restriction) {
if (log.isDebugEnabled()) {
log.debug("PUT SEB Client restriction on course: {} : {}", courseId, restriction);
}
return Result.tryCatch(() -> {
final MoodleAPIRestTemplate template = getRestTemplate()
.getOrThrow();
final MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
queryParams.add(MOODLE_DEFAULT_COURSE_RESTRICTION_COURSE_ID, courseId);
final MultiValueMap<String, String> queryAttributes = new LinkedMultiValueMap<>();
queryAttributes.addAll(
MOODLE_DEFAULT_COURSE_RESTRICTION_CONFIG_KEY,
new ArrayList<>(restriction.configKeys));
queryAttributes.addAll(
MOODLE_DEFAULT_COURSE_RESTRICTION_BROWSER_KEY,
new ArrayList<>(restriction.browserExamKeys));
template.callMoodleAPIFunction(
MOODLE_DEFAULT_COURSE_RESTRICTION_WS_FUNCTION,
queryParams,
queryAttributes);
return true;
});
}
Result<Boolean> deleteSEBRestriction(final String courseId) {
if (log.isDebugEnabled()) {
log.debug("DELETE SEB Client restriction on course: {}", courseId);
}
return Result.tryCatch(() -> {
final MoodleAPIRestTemplate template = getRestTemplate()
.getOrThrow();
final MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
queryParams.add(MOODLE_DEFAULT_COURSE_RESTRICTION_COURSE_ID, courseId);
template.callMoodleAPIFunction(
MOODLE_DEFAULT_COURSE_RESTRICTION_WS_FUNCTION_DELETE,
queryParams,
null);
return true;
});
}
private Result<MoodleAPIRestTemplate> getRestTemplate() {
if (this.restTemplate == null) {
final Result<MoodleAPIRestTemplate> templateRequest = this.moodleRestTemplateFactory
.createRestTemplate();
if (templateRequest.hasError()) {
return templateRequest;
} else {
this.restTemplate = templateRequest.get();
}
}
return Result.of(this.restTemplate);
}
}

View file

@ -12,8 +12,12 @@ import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.ethz.seb.sebserver.gbl.model.exam.Chapters; import ch.ethz.seb.sebserver.gbl.model.exam.Chapters;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.exam.MoodleSEBRestriction;
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData; import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
import ch.ethz.seb.sebserver.gbl.model.exam.SEBRestriction; import ch.ethz.seb.sebserver.gbl.model.exam.SEBRestriction;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup; import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
@ -25,15 +29,20 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate;
public class MoodleLmsAPITemplate implements LmsAPITemplate { public class MoodleLmsAPITemplate implements LmsAPITemplate {
private static final Logger log = LoggerFactory.getLogger(MoodleLmsAPITemplate.class);
private final LmsSetup lmsSetup; private final LmsSetup lmsSetup;
private final MoodleCourseAccess moodleCourseAccess; private final MoodleCourseAccess moodleCourseAccess;
private final MoodleCourseRestriction moodleCourseRestriction;
protected MoodleLmsAPITemplate( protected MoodleLmsAPITemplate(
final LmsSetup lmsSetup, final LmsSetup lmsSetup,
final MoodleCourseAccess moodleCourseAccess) { final MoodleCourseAccess moodleCourseAccess,
final MoodleCourseRestriction moodleCourseRestriction) {
this.lmsSetup = lmsSetup; this.lmsSetup = lmsSetup;
this.moodleCourseAccess = moodleCourseAccess; this.moodleCourseAccess = moodleCourseAccess;
this.moodleCourseRestriction = moodleCourseRestriction;
} }
@Override @Override
@ -87,7 +96,13 @@ public class MoodleLmsAPITemplate implements LmsAPITemplate {
@Override @Override
public Result<SEBRestriction> getSEBClientRestriction(final Exam exam) { public Result<SEBRestriction> getSEBClientRestriction(final Exam exam) {
return Result.ofError(new UnsupportedOperationException("SEB Restriction API not available yet")); if (log.isDebugEnabled()) {
log.debug("Get SEB Client restriction for Exam: {}", exam);
}
return this.moodleCourseRestriction
.getSEBRestriction(exam.externalId)
.map(restriction -> SEBRestriction.from(exam.id, restriction));
} }
@Override @Override
@ -95,12 +110,25 @@ public class MoodleLmsAPITemplate implements LmsAPITemplate {
final String externalExamId, final String externalExamId,
final SEBRestriction sebRestrictionData) { final SEBRestriction sebRestrictionData) {
return Result.ofError(new UnsupportedOperationException("SEB Restriction API not available yet")); if (log.isDebugEnabled()) {
log.debug("Apply SEB Client restriction: {}", sebRestrictionData);
}
return this.moodleCourseRestriction
.putSEBRestriction(
externalExamId,
MoodleSEBRestriction.from(sebRestrictionData))
.map(result -> sebRestrictionData);
} }
@Override @Override
public Result<Exam> releaseSEBClientRestriction(final Exam exam) { public Result<Exam> releaseSEBClientRestriction(final Exam exam) {
return Result.ofError(new UnsupportedOperationException("SEB Restriction API not available yet")); if (log.isDebugEnabled()) {
log.debug("Release SEB Client restriction for Exam: {}", exam);
}
return this.moodleCourseRestriction.deleteSEBRestriction(exam.externalId)
.map(result -> exam);
} }
} }

View file

@ -73,9 +73,15 @@ public class MoodleLmsAPITemplateFactory {
moodleRestTemplateFactory, moodleRestTemplateFactory,
this.asyncService); this.asyncService);
final MoodleCourseRestriction moodleCourseRestriction = new MoodleCourseRestriction(
this.jsonMapper,
lmsSetup,
moodleRestTemplateFactory);
return new MoodleLmsAPITemplate( return new MoodleLmsAPITemplate(
lmsSetup, lmsSetup,
moodleCourseAccess); moodleCourseAccess,
moodleCourseRestriction);
}); });
} }

View file

@ -8,21 +8,17 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle; package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle;
import ch.ethz.seb.sebserver.ClientHttpRequestFactoryService; import java.util.ArrayList;
import ch.ethz.seb.sebserver.gbl.api.APIMessage; import java.util.Arrays;
import ch.ethz.seb.sebserver.gbl.api.JSONMapper; import java.util.Collection;
import ch.ethz.seb.sebserver.gbl.client.ClientCredentialService; import java.util.HashMap;
import ch.ethz.seb.sebserver.gbl.client.ClientCredentials; import java.util.HashSet;
import ch.ethz.seb.sebserver.gbl.client.ProxyData; import java.util.List;
import ch.ethz.seb.sebserver.gbl.model.Domain.LMS_SETUP; import java.util.Map;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup; import java.util.Set;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult; import java.util.function.Function;
import ch.ethz.seb.sebserver.gbl.util.Result; import java.util.stream.Collectors;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpEntity; import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
@ -36,16 +32,21 @@ import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriComponentsBuilder;
import java.util.ArrayList; import com.fasterxml.jackson.annotation.JsonCreator;
import java.util.Arrays; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import java.util.Collection; import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.HashMap;
import java.util.HashSet; import ch.ethz.seb.sebserver.ClientHttpRequestFactoryService;
import java.util.List; import ch.ethz.seb.sebserver.gbl.api.APIMessage;
import java.util.Map; import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
import java.util.Set; import ch.ethz.seb.sebserver.gbl.client.ClientCredentialService;
import java.util.function.Function; import ch.ethz.seb.sebserver.gbl.client.ClientCredentials;
import java.util.stream.Collectors; import ch.ethz.seb.sebserver.gbl.client.ProxyData;
import ch.ethz.seb.sebserver.gbl.model.Domain.LMS_SETUP;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gbl.util.Utils;
final class MoodleRestTemplateFactory { final class MoodleRestTemplateFactory {
@ -243,12 +244,19 @@ final class MoodleRestTemplateFactory {
} }
public String callMoodleAPIFunction(final String functionName) { public String callMoodleAPIFunction(final String functionName) {
return callMoodleAPIFunction(functionName, null); return callMoodleAPIFunction(functionName, null, null);
} }
public String callMoodleAPIFunction( public String callMoodleAPIFunction(
final String functionName, final String functionName,
final MultiValueMap<String, String> queryAttributes) { final MultiValueMap<String, String> queryAttributes) {
return callMoodleAPIFunction(functionName, null, queryAttributes);
}
public String callMoodleAPIFunction(
final String functionName,
final MultiValueMap<String, String> queryParams,
final MultiValueMap<String, String> queryAttributes) {
getAccessToken(); getAccessToken();
@ -258,6 +266,10 @@ final class MoodleRestTemplateFactory {
.queryParam(REST_REQUEST_FUNCTION_NAME, functionName) .queryParam(REST_REQUEST_FUNCTION_NAME, functionName)
.queryParam(REST_REQUEST_FORMAT_NAME, "json"); .queryParam(REST_REQUEST_FORMAT_NAME, "json");
if (queryParams != null && !queryParams.isEmpty()) {
queryParam.queryParams(queryParams);
}
final boolean usePOST = queryAttributes != null && !queryAttributes.isEmpty(); final boolean usePOST = queryAttributes != null && !queryAttributes.isEmpty();
HttpEntity<?> functionReqEntity; HttpEntity<?> functionReqEntity;
if (usePOST) { if (usePOST) {