SEBSERV-301 adapted to Moodle plugin

This commit is contained in:
anhefti 2023-01-25 09:04:35 +01:00
parent 574c397a54
commit 4888b4596c
13 changed files with 118 additions and 45 deletions

View file

@ -61,7 +61,7 @@ public class ClientCredentialServiceImpl implements ClientCredentialService {
@Override
public Result<CharSequence> getPlainClientSecret(final ClientCredentials credentials) {
if (credentials == null || !credentials.hasSecret()) {
return null;
return Result.ofRuntimeError("ClientCredentials has no secret");
}
return this.cryptor.decrypt(credentials.secret);

View file

@ -658,7 +658,7 @@ public final class Utils {
(sb, entry) -> {
final String name = entry.getKey();
final List<String> values = entry.getValue();
if (values == null || values.isEmpty()) {
if (values == null) {
return sb;
}
if (sb.length() > 0) {

View file

@ -76,6 +76,8 @@ public class LmsSetupForm implements TemplateComposer {
new LocTextKey("sebserver.lmssetup.form.secret.lms");
private static final LocTextKey FORM_CLIENTNAME_LMS_TEXT_KEY =
new LocTextKey("sebserver.lmssetup.form.clientname.lms");
private static final LocTextKey FORM_TOKEN_LMS_TEXT_KEY =
new LocTextKey("sebserver.lmssetup.form.accesstoken.lms");
private static final LocTextKey FORM_URL_TEXT_KEY =
new LocTextKey("sebserver.lmssetup.form.url");
private static final LocTextKey FORM_TYPE_TEXT_KEY =
@ -227,6 +229,13 @@ public class LmsSetupForm implements TemplateComposer {
.asPasswordField()
.mandatory(!readonly))
.addFieldIf(
isEdit,
() -> FormBuilder.text(
Domain.LMS_SETUP.ATTR_LMS_REST_API_TOKEN,
FORM_TOKEN_LMS_TEXT_KEY)
.mandatory(!readonly))
.addFieldIf(
isEdit,
() -> FormBuilder.checkbox(

View file

@ -178,7 +178,7 @@ public class LmsSetupDAOImpl implements LmsSetupDAO {
final ClientCredentials proxyCredentials = createProxyClientCredentials(lmsSetup);
final LmsSetupRecord newRecord = new LmsSetupRecord(
lmsSetup.id,
null,
lmsSetup.institutionId,
lmsSetup.name,
(lmsSetup.lmsType != null) ? lmsSetup.lmsType.name() : null,
lmsSetup.lmsApiUrl,
@ -196,7 +196,7 @@ public class LmsSetupDAOImpl implements LmsSetupDAO {
System.currentTimeMillis(),
savedRecord.getActive());
this.lmsSetupRecordMapper.updateByPrimaryKeySelective(newRecord);
this.lmsSetupRecordMapper.updateByPrimaryKey(newRecord);
return this.lmsSetupRecordMapper.selectByPrimaryKey(lmsSetup.id);
})
.flatMap(this::toDomainModel)

View file

@ -14,16 +14,34 @@ 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.APITemplateDataSupplier;
/** Defines a MoodleAPIRestTemplate factory for moodle */
public interface MoodleRestTemplateFactory {
/** Use this to test the LMSSetup input for MoodleAPIRestTemplate
*
* @return LmsSetupTestResult containing the result of the test */
LmsSetupTestResult test();
/** Get the LMSSetup data supplier for this factory.
*
* @return APITemplateDataSupplier */
APITemplateDataSupplier getApiTemplateDataSupplier();
/** Get all known, defined API access token request paths.
*
* @return Set of known and configured API access token paths */
Set<String> getKnownTokenAccessPaths();
/** Creates a MoodleAPIRestTemplate for the bundled LMSSetup of this factory.
*
* @return Result refer to the MoodleAPIRestTemplate or to an error when happened */
Result<MoodleAPIRestTemplate> createRestTemplate();
/** Creates a MoodleAPIRestTemplate for the bundled LMSSetup of this factory.
* Uses specified access token request path to request an access token.
*
* @param accessTokenPath access token request path to request an access token
* @return Result refer to the MoodleAPIRestTemplate or to an error when happened */
Result<MoodleAPIRestTemplate> createRestTemplate(final String accessTokenPath);
}

View file

@ -114,15 +114,17 @@ public class MoodleRestTemplateFactoryImpl implements MoodleRestTemplateFactory
}
if (StringUtils.isBlank(lmsSetup.lmsRestApiToken)) {
if (!credentials.hasClientId()) {
missingAttrs.add(APIMessage.fieldValidationError(
LMS_SETUP.ATTR_LMS_CLIENTNAME,
"lmsSetup:lmsClientname:notNull"));
}
if (!credentials.hasSecret()) {
missingAttrs.add(APIMessage.fieldValidationError(
LMS_SETUP.ATTR_LMS_CLIENTSECRET,
"lmsSetup:lmsClientsecret:notNull"));
if (!credentials.hasAccessToken()) {
if (!credentials.hasClientId()) {
missingAttrs.add(APIMessage.fieldValidationError(
LMS_SETUP.ATTR_LMS_CLIENTNAME,
"lmsSetup:lmsClientname:notNull"));
}
if (!credentials.hasSecret()) {
missingAttrs.add(APIMessage.fieldValidationError(
LMS_SETUP.ATTR_LMS_CLIENTSECRET,
"lmsSetup:lmsClientsecret:notNull"));
}
}
}
@ -170,14 +172,17 @@ public class MoodleRestTemplateFactoryImpl implements MoodleRestTemplateFactory
final CharSequence plainClientId = credentials.clientId;
final CharSequence plainClientSecret = this.clientCredentialService
.getPlainClientSecret(credentials)
.getOrThrow();
.getOr(StringUtils.EMPTY);
final CharSequence plainAPIToken = this.clientCredentialService
.getPlainAccessToken(credentials)
.getOr(StringUtils.EMPTY);
final MoodleAPIRestTemplateImpl restTemplate = new MoodleAPIRestTemplateImpl(
this.jsonMapper,
this.apiTemplateDataSupplier,
lmsSetup.lmsApiUrl,
accessTokenPath,
lmsSetup.lmsRestApiToken,
plainAPIToken,
plainClientId,
plainClientSecret);
@ -200,6 +205,7 @@ public class MoodleRestTemplateFactoryImpl implements MoodleRestTemplateFactory
public static class MoodleAPIRestTemplateImpl extends RestTemplate implements MoodleAPIRestTemplate {
private static final String MOODLE_MOBILE_APP_SERVICE = "moodle_mobile_app";
private static final String REST_API_TEST_FUNCTION = "core_webservice_get_site_info";
final JSONMapper jsonMapper;
@ -208,6 +214,7 @@ public class MoodleRestTemplateFactoryImpl implements MoodleRestTemplateFactory
private final String serverURL;
private final String tokenPath;
private final CharSequence apiToken;
private CharSequence accessToken;
private final Map<String, String> tokenReqURIVars;
@ -218,7 +225,7 @@ public class MoodleRestTemplateFactoryImpl implements MoodleRestTemplateFactory
final APITemplateDataSupplier apiTemplateDataSupplier,
final String serverURL,
final String tokenPath,
final CharSequence accessToken,
final CharSequence apiToken,
final CharSequence username,
final CharSequence password) {
@ -227,12 +234,13 @@ public class MoodleRestTemplateFactoryImpl implements MoodleRestTemplateFactory
this.serverURL = serverURL;
this.tokenPath = tokenPath;
this.accessToken = StringUtils.isNotBlank(accessToken) ? accessToken : null;
this.apiToken = apiToken;
this.accessToken = StringUtils.isNotBlank(apiToken) ? apiToken : null;
this.tokenReqURIVars = new HashMap<>();
this.tokenReqURIVars.put(URI_VAR_USER_NAME, String.valueOf(username));
this.tokenReqURIVars.put(URI_VAR_PASSWORD, String.valueOf(password));
this.tokenReqURIVars.put(URI_VAR_SERVICE, "moodle_mobile_app");
this.tokenReqURIVars.put(URI_VAR_SERVICE, MOODLE_MOBILE_APP_SERVICE);
}
@ -336,6 +344,8 @@ public class MoodleRestTemplateFactoryImpl implements MoodleRestTemplateFactory
functionReqEntity,
String.class);
System.out.println("*************** response: " + response);
final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup();
if (response.getStatusCode() != HttpStatus.OK) {
throw new RuntimeException(
@ -362,6 +372,11 @@ public class MoodleRestTemplateFactoryImpl implements MoodleRestTemplateFactory
private void requestAccessToken() {
if (StringUtils.isNotBlank(this.apiToken)) {
this.accessToken = this.apiToken;
return;
}
final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup();
try {

View file

@ -75,13 +75,14 @@ public class MoodlePluginCourseAccess extends AbstractCachedCourseAccess impleme
public static final String ATTR_ID = "id";
public static final String ATTR_SHORTNAME = "shortname";
public static final String PARAM_COURSE_ID = "courseid";
public static final String PARAM_COURSE_ID_ARRAY = "courseid[]";
public static final String PARAM_SQL_CONDITIONS = "conditions";
public static final String PARAM_PAGE_START = "startneedle";
public static final String PARAM_PAGE_SIZE = "perpage";
public static final String SQL_CONDITION_TEMPLATE =
"(startdate >= %s or timecreated >=%s) and (enddate is null or enddate is 0 or enddate is >= %s)";
//"(startdate >= %s or timecreated >=%s) and (enddate is null or enddate = 0 or enddate >= %s)";
"(startdate is null OR startdate = 0 OR startdate >= %s) AND (enddate is null or enddate = 0 OR enddate >= %s)";
private final JSONMapper jsonMapper;
private final MoodleRestTemplateFactory restTemplateFactory;
@ -423,10 +424,12 @@ public class MoodlePluginCourseAccess extends AbstractCachedCourseAccess impleme
final String sqlCondition = String.format(
SQL_CONDITION_TEMPLATE,
String.valueOf(cutoffDate),
String.valueOf(cutoffDate),
String.valueOf(filterDate));
final String fromElement = String.valueOf(page * size);
final LinkedMultiValueMap<String, String> attributes = new LinkedMultiValueMap<>();
// Note: courseid[]=0 means all courses. Moodle don't like empty parameter
attributes.add(PARAM_COURSE_ID_ARRAY, "0");
attributes.add(PARAM_SQL_CONDITIONS, sqlCondition);
attributes.add(PARAM_PAGE_START, fromElement);
attributes.add(PARAM_PAGE_SIZE, String.valueOf(size));
@ -526,7 +529,7 @@ public class MoodlePluginCourseAccess extends AbstractCachedCourseAccess impleme
}
final LinkedMultiValueMap<String, String> attributes = new LinkedMultiValueMap<>();
attributes.put(PARAM_COURSE_ID, new ArrayList<>(courseIds));
attributes.put(PARAM_COURSE_ID_ARRAY, new ArrayList<>(courseIds));
final String coursePageJSON = restTemplate.callMoodleAPIFunction(
COURSES_API_FUNCTION_NAME,
attributes);
@ -560,7 +563,9 @@ public class MoodlePluginCourseAccess extends AbstractCachedCourseAccess impleme
if (templateRequest.hasError()) {
return templateRequest;
} else {
this.restTemplate = templateRequest.get();
final MoodleAPIRestTemplate moodleAPIRestTemplate = templateRequest.get();
moodleAPIRestTemplate.setService(MooldePluginLmsAPITemplateFactory.SEB_SERVER_SERVICE_NAME);
this.restTemplate = moodleAPIRestTemplate;
}
}

View file

@ -43,11 +43,14 @@ public class MoodlePluginCourseRestriction implements SEBRestrictionAPI {
public static final String RESTRICTION_GET_FUNCTION_NAME = "quizaccess_sebserver_get_restriction";
public static final String RESTRICTION_SET_FUNCTION_NAME = "quizaccess_sebserver_set_restriction";
public static final String ATTRIBUTE_QUIZ_ID = "quiz_id";
public static final String ATTRIBUTE_CONFIG_KEYS = "config_keys";
public static final String ATTRIBUTE_BROWSER_EXAM_KEYS = "browser_exam_keys";
public static final String ATTRIBUTE_QUIT_URL = "quit_link";
public static final String ATTRIBUTE_QUIT_SECRET = "quit_secret";
public static final String ATTRIBUTE_QUIZ_ID = "quizid";
public static final String ATTRIBUTE_CONFIG_KEYS = "configkeys[]";
public static final String ATTRIBUTE_BROWSER_EXAM_KEYS = "browserkeys[]";
public static final String ATTRIBUTE_QUIT_URL = "quitlink";
public static final String ATTRIBUTE_QUIT_SECRET = "quitsecret";
private static final String NO_RESTRICTION_SET_WARNING = "error/SEB Server is not enabled";
private static final String DELETED_RESTRICTION_WARNING = "You have deleted restriction";
private final JSONMapper jsonMapper;
private final MoodleRestTemplateFactory restTemplateFactory;
@ -168,18 +171,20 @@ public class MoodlePluginCourseRestriction implements SEBRestrictionAPI {
final LinkedMultiValueMap<String, String> addQuery = new LinkedMultiValueMap<>();
addQuery.add(ATTRIBUTE_QUIZ_ID, quizId);
final String quitLink = this.examConfigurationValueService.getQuitLink(exam.id);
final String quitSecret = this.examConfigurationValueService.getQuitSecret(exam.id);
final LinkedMultiValueMap<String, String> queryAttributes = new LinkedMultiValueMap<>();
queryAttributes.add(ATTRIBUTE_QUIT_URL, quitLink);
queryAttributes.add(ATTRIBUTE_QUIT_SECRET, quitSecret);
restTemplate.callMoodleAPIFunction(
queryAttributes.add(ATTRIBUTE_CONFIG_KEYS, StringUtils.EMPTY);
queryAttributes.add(ATTRIBUTE_BROWSER_EXAM_KEYS, StringUtils.EMPTY);
final String srJSON = restTemplate.callMoodleAPIFunction(
RESTRICTION_SET_FUNCTION_NAME,
addQuery,
queryAttributes);
if (!srJSON.contains(DELETED_RESTRICTION_WARNING)) {
log.warn("Release SEB restriction seems to failed. Moodle response: {}", srJSON);
}
return exam;
});
}
@ -187,6 +192,7 @@ public class MoodlePluginCourseRestriction implements SEBRestrictionAPI {
private SEBRestriction restrictionFromJson(final Exam exam, final String srJSON) {
try {
// check blank result
if (StringUtils.isBlank(srJSON)) {
return new SEBRestriction(
exam.id,
@ -195,6 +201,20 @@ public class MoodlePluginCourseRestriction implements SEBRestrictionAPI {
Collections.emptyMap());
}
// check no restriction set
if (srJSON.contains(NO_RESTRICTION_SET_WARNING)) {
if (log.isDebugEnabled()) {
log.debug("No restriction set for exam: {}", exam);
}
return new SEBRestriction(
exam.id,
Collections.emptyList(),
Collections.emptyList(),
Collections.emptyMap());
}
// fist try to get from multiple data
final MoodleQuizRestrictions moodleRestrictions = this.jsonMapper.readValue(
srJSON,
@ -262,7 +282,9 @@ public class MoodlePluginCourseRestriction implements SEBRestrictionAPI {
if (templateRequest.hasError()) {
return templateRequest;
} else {
this.restTemplate = templateRequest.get();
final MoodleAPIRestTemplate moodleAPIRestTemplate = templateRequest.get();
moodleAPIRestTemplate.setService(MooldePluginLmsAPITemplateFactory.SEB_SERVER_SERVICE_NAME);
this.restTemplate = moodleAPIRestTemplate;
}
}

View file

@ -36,6 +36,8 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestT
@WebServiceProfile
public class MooldePluginLmsAPITemplateFactory implements LmsAPITemplateFactory {
public static final String SEB_SERVER_SERVICE_NAME = "seb-server-service";
private final JSONMapper jsonMapper;
private final CacheManager cacheManager;
private final AsyncService asyncService;

View file

@ -390,6 +390,8 @@ sebserver.lmssetup.form.clientname.lms=LMS Server Username
sebserver.lmssetup.form.clientname.lms.tooltip=The client name of the API client access to the LMS<br/><br/>This is usually provided by an LMS administrator that has created a API access account for SEB Server binding within the LMS server.
sebserver.lmssetup.form.secret.lms=LMS Server Password
sebserver.lmssetup.form.secret.lms.tooltip=The secret or password of the API client access to the LMS<br/><br/>This is usually provided by an LMS administrator that has created a API access account for SEB Server binding within the LMS server.
sebserver.lmssetup.form.accesstoken.lms=Access Token
sebserver.lmssetup.form.accesstoken.lms.tooltip=The Access Token for the LMS API if LMS supports access token
sebserver.lmssetup.form.proxy=Proxy
sebserver.lmssetup.form.proxy.check=With Proxy
sebserver.lmssetup.form.proxy.check.tooltip=Check and give detailed information if the SEB Server runs behind a proxy<br/>and need proxy settings to connect to the internet

View file

@ -35,7 +35,6 @@ public class AsyncBlockingTest {
final Collection<Future<String>> features = new ArrayList<>();
for (int i = 0; i < TASKS; i++) {
final Future<String> runAsync = this.asyncRunner.runAsync(this::doAsync);
//System.out.println("*********** run async: " + i);
features.add(runAsync);
}
assertEquals(TASKS, features.size());

View file

@ -248,11 +248,12 @@ public class MoodleMockupRestTemplateFactory implements MoodleRestTemplateFactor
private String respondCourses(final MultiValueMap<String, String> queryAttributes) {
try {
final List<String> ids = queryAttributes.get(MoodlePluginCourseAccess.PARAM_COURSE_ID);
final List<String> ids = queryAttributes.get(MoodlePluginCourseAccess.PARAM_COURSE_ID_ARRAY);
final String from = queryAttributes.getFirst(MoodlePluginCourseAccess.PARAM_PAGE_START);
final String size = queryAttributes.getFirst(MoodlePluginCourseAccess.PARAM_PAGE_SIZE);
final List<MockCD> courses;
if (ids != null && !ids.isEmpty()) {
if (ids != null && !ids.isEmpty() && !ids.get(0).equals("0")) {
courses = this.courses.stream().filter(c -> ids.contains(c.id)).collect(Collectors.toList());
} else if (from != null && Integer.valueOf(from) < this.courses.size()) {

View file

@ -84,8 +84,8 @@ public class MoodlePluginCourseAccessTest {
+ "callMoodleAPIFunction: quizaccess_sebserver_get_exams, "
+ "callMoodleAPIFunction: quizaccess_sebserver_get_exams], "
+ "callLog=["
+ "<conditions=(startdate >= -94694400 or timecreated >=-94694400) and (enddate is null or enddate is 0 or enddate is >= -94694400)&startneedle=0&perpage=500,[Content-Type:\"application/x-www-form-urlencoded\"]>, "
+ "<conditions=(startdate >= -94694400 or timecreated >=-94694400) and (enddate is null or enddate is 0 or enddate is >= -94694400)&startneedle=500&perpage=500,[Content-Type:\"application/x-www-form-urlencoded\"]>]]]",
+ "<courseid[]=0&conditions=(startdate is null OR startdate = 0 OR startdate >= -94694400) AND (enddate is null or enddate = 0 OR enddate >= -94694400)&startneedle=0&perpage=500,[Content-Type:\"application/x-www-form-urlencoded\"]>, "
+ "<courseid[]=0&conditions=(startdate is null OR startdate = 0 OR startdate >= -94694400) AND (enddate is null or enddate = 0 OR enddate >= -94694400)&startneedle=500&perpage=500,[Content-Type:\"application/x-www-form-urlencoded\"]>]]]",
candidate.toTestString());
final List<String> ids =
@ -152,11 +152,11 @@ public class MoodlePluginCourseAccessTest {
+ "callMoodleAPIFunction: quizaccess_sebserver_get_exams, "
+ "callMoodleAPIFunction: quizaccess_sebserver_get_exams], "
+ "callLog=["
+ "<conditions=(startdate >= -94694400 or timecreated >=-94694400) and (enddate is null or enddate is 0 or enddate is >= -94694400)&startneedle=0&perpage=5,[Content-Type:\"application/x-www-form-urlencoded\"]>, "
+ "<conditions=(startdate >= -94694400 or timecreated >=-94694400) and (enddate is null or enddate is 0 or enddate is >= -94694400)&startneedle=5&perpage=5,[Content-Type:\"application/x-www-form-urlencoded\"]>, "
+ "<conditions=(startdate >= -94694400 or timecreated >=-94694400) and (enddate is null or enddate is 0 or enddate is >= -94694400)&startneedle=10&perpage=5,[Content-Type:\"application/x-www-form-urlencoded\"]>, "
+ "<conditions=(startdate >= -94694400 or timecreated >=-94694400) and (enddate is null or enddate is 0 or enddate is >= -94694400)&startneedle=15&perpage=5,[Content-Type:\"application/x-www-form-urlencoded\"]>, "
+ "<conditions=(startdate >= -94694400 or timecreated >=-94694400) and (enddate is null or enddate is 0 or enddate is >= -94694400)&startneedle=20&perpage=5,[Content-Type:\"application/x-www-form-urlencoded\"]>]]]",
+ "<courseid[]=0&conditions=(startdate is null OR startdate = 0 OR startdate >= -94694400) AND (enddate is null or enddate = 0 OR enddate >= -94694400)&startneedle=0&perpage=5,[Content-Type:\"application/x-www-form-urlencoded\"]>, "
+ "<courseid[]=0&conditions=(startdate is null OR startdate = 0 OR startdate >= -94694400) AND (enddate is null or enddate = 0 OR enddate >= -94694400)&startneedle=5&perpage=5,[Content-Type:\"application/x-www-form-urlencoded\"]>, "
+ "<courseid[]=0&conditions=(startdate is null OR startdate = 0 OR startdate >= -94694400) AND (enddate is null or enddate = 0 OR enddate >= -94694400)&startneedle=10&perpage=5,[Content-Type:\"application/x-www-form-urlencoded\"]>, "
+ "<courseid[]=0&conditions=(startdate is null OR startdate = 0 OR startdate >= -94694400) AND (enddate is null or enddate = 0 OR enddate >= -94694400)&startneedle=15&perpage=5,[Content-Type:\"application/x-www-form-urlencoded\"]>, "
+ "<courseid[]=0&conditions=(startdate is null OR startdate = 0 OR startdate >= -94694400) AND (enddate is null or enddate = 0 OR enddate >= -94694400)&startneedle=20&perpage=5,[Content-Type:\"application/x-www-form-urlencoded\"]>]]]",
candidate.toTestString());
final List<String> ids =