refactoring and improvement of LMS binding API
This commit is contained in:
parent
0bda2cb292
commit
dd6150ec2a
36 changed files with 790 additions and 544 deletions
|
@ -129,6 +129,7 @@ public final class API {
|
|||
public static final String EXAM_ADMINISTRATION_ENDPOINT = "/exam";
|
||||
public static final String EXAM_ADMINISTRATION_DOWNLOAD_CONFIG_PATH_SEGMENT = "/download-config";
|
||||
public static final String EXAM_ADMINISTRATION_CONSISTENCY_CHECK_PATH_SEGMENT = "/check-consistency";
|
||||
public static final String EXAM_ADMINISTRATION_CONSISTENCY_CHECK_INCLUDE_RESTRICTION = "include-restriction";
|
||||
public static final String EXAM_ADMINISTRATION_SEB_RESTRICTION_PATH_SEGMENT = "/seb-restriction";
|
||||
public static final String EXAM_ADMINISTRATION_CHECK_RESTRICTION_PATH_SEGMENT = "/check-seb-restriction";
|
||||
public static final String EXAM_ADMINISTRATION_CHECK_IMPORTED_PATH_SEGMENT = "/check-imported";
|
||||
|
|
|
@ -47,7 +47,7 @@ public final class Exam implements GrantEntity {
|
|||
null,
|
||||
null,
|
||||
ExamStatus.FINISHED,
|
||||
// Boolean.FALSE,
|
||||
Boolean.FALSE,
|
||||
null,
|
||||
Boolean.FALSE,
|
||||
null);
|
||||
|
@ -115,6 +115,9 @@ public final class Exam implements GrantEntity {
|
|||
@JsonProperty(EXAM.ATTR_STATUS)
|
||||
public final ExamStatus status;
|
||||
|
||||
@JsonProperty(EXAM.ATTR_LMS_SEB_RESTRICTION)
|
||||
public final Boolean sebRestriction;
|
||||
|
||||
@JsonProperty(EXAM.ATTR_BROWSER_KEYS)
|
||||
public final String browserExamKeys;
|
||||
|
||||
|
@ -139,6 +142,7 @@ public final class Exam implements GrantEntity {
|
|||
@JsonProperty(EXAM.ATTR_OWNER) final String owner,
|
||||
@JsonProperty(EXAM.ATTR_SUPPORTER) final Collection<String> supporter,
|
||||
@JsonProperty(EXAM.ATTR_STATUS) final ExamStatus status,
|
||||
@JsonProperty(EXAM.ATTR_LMS_SEB_RESTRICTION) final Boolean sebRestriction,
|
||||
@JsonProperty(EXAM.ATTR_BROWSER_KEYS) final String browserExamKeys,
|
||||
@JsonProperty(EXAM.ATTR_ACTIVE) final Boolean active,
|
||||
@JsonProperty(EXAM.ATTR_LASTUPDATE) final String lastUpdate) {
|
||||
|
@ -155,6 +159,7 @@ public final class Exam implements GrantEntity {
|
|||
this.type = type;
|
||||
this.owner = owner;
|
||||
this.status = (status != null) ? status : getStatusFromDate(startTime, endTime);
|
||||
this.sebRestriction = sebRestriction;
|
||||
this.browserExamKeys = browserExamKeys;
|
||||
this.active = (active != null) ? active : Boolean.TRUE;
|
||||
this.lastUpdate = lastUpdate;
|
||||
|
@ -181,6 +186,7 @@ public final class Exam implements GrantEntity {
|
|||
EXAM.ATTR_STATUS,
|
||||
ExamStatus.class,
|
||||
getStatusFromDate(this.startTime, this.endTime));
|
||||
this.sebRestriction = null;
|
||||
this.browserExamKeys = mapper.getString(EXAM.ATTR_BROWSER_KEYS);
|
||||
this.active = mapper.getBoolean(EXAM.ATTR_ACTIVE);
|
||||
this.supporter = mapper.getStringSet(EXAM.ATTR_SUPPORTER);
|
||||
|
@ -204,6 +210,7 @@ public final class Exam implements GrantEntity {
|
|||
this.type = null;
|
||||
this.owner = null;
|
||||
this.status = (status != null) ? status : getStatusFromDate(this.startTime, this.endTime);
|
||||
this.sebRestriction = null;
|
||||
this.browserExamKeys = null;
|
||||
this.active = null;
|
||||
this.supporter = null;
|
||||
|
|
|
@ -29,6 +29,7 @@ import ch.ethz.seb.sebserver.gbl.util.Utils;
|
|||
|
||||
public final class QuizData implements GrantEntity {
|
||||
|
||||
public static final String FILTER_ATTR_QUIZ_NAME = "quiz_name";
|
||||
public static final String FILTER_ATTR_START_TIME = "start_timestamp";
|
||||
|
||||
public static final String ATTR_ADDITIONAL_ATTRIBUTES = "ADDITIONAL_ATTRIBUTES";
|
||||
|
|
|
@ -37,16 +37,32 @@ public final class LmsSetup implements GrantEntity, Activatable {
|
|||
public static final String FILTER_ATTR_LMS_SETUP = "lms_setup";
|
||||
public static final String FILTER_ATTR_LMS_TYPE = "lms_type";
|
||||
|
||||
/** LMS binding and API features */
|
||||
public enum Features {
|
||||
/** The course API allows the application to securely connect to a LMS service
|
||||
* and request course or quiz data from that LMS as well as requesting some
|
||||
* limited LMS user account data like user name or display name. */
|
||||
COURSE_API,
|
||||
/** The SEB restriction API allows the application to securely connect to a LMS service
|
||||
* and place or release SEB restrictions, for a particular course or quiz, on the LMS.
|
||||
* The SEB restriciton is usually in the form of certain hash keys and addition
|
||||
* restriction settings that prompt the LMS to check access on course/quiz connection and
|
||||
* allow only access for a dedicated SEB client with the right configuration in place. */
|
||||
SEB_RESTRICTION
|
||||
}
|
||||
|
||||
/** Defines the supported types if LMS bindings.
|
||||
* Also defines the supports feature(s) for each type of LMS binding. */
|
||||
public enum LmsType {
|
||||
/** Mockup LMS type used to create test setups */
|
||||
MOCKUP(Features.COURSE_API),
|
||||
/** The Open edX LMS binding features both APIs, course access as well as SEB restrcition */
|
||||
OPEN_EDX(Features.COURSE_API, Features.SEB_RESTRICTION),
|
||||
/** The Moodle binding features only the course access API so far */
|
||||
MOODLE(Features.COURSE_API /* , Features.SEB_RESTRICTION */),
|
||||
/** The Ans Delft binding is on the way */
|
||||
ANS_DELFT(/* Features.COURSE_API , Features.SEB_RESTRICTION */),
|
||||
/** The OpenOLAT binding is on the way */
|
||||
OPEN_OLAT(/* Features.COURSE_API , Features.SEB_RESTRICTION */);
|
||||
|
||||
public final EnumSet<Features> features;
|
||||
|
|
|
@ -226,6 +226,8 @@ public class ExamForm implements TemplateComposer {
|
|||
final ExamStatus examStatus = exam.getStatus();
|
||||
final boolean editable = modifyGrant && (examStatus == ExamStatus.UP_COMING ||
|
||||
examStatus == ExamStatus.RUNNING);
|
||||
|
||||
// TODO this is not performat try to improve by doing one check with the CheckExamConsistency above
|
||||
final boolean sebRestrictionAvailable = testSEBRestrictionAPI(exam);
|
||||
final boolean isRestricted = readonly && sebRestrictionAvailable && this.restService
|
||||
.getBuilder(CheckSEBRestriction.class)
|
||||
|
|
|
@ -29,4 +29,5 @@ public interface LmsSetupDAO extends ActivatableEntityDAO<LmsSetup, LmsSetup>, B
|
|||
* @param lmsSetupId the LMS Setup identifier
|
||||
* @return Result refer to the proxy data or to an error if happened */
|
||||
Result<ProxyData> getLmsAPIAccessProxyData(String lmsSetupId);
|
||||
|
||||
}
|
||||
|
|
|
@ -1010,6 +1010,7 @@ public class ExamDAOImpl implements ExamDAO {
|
|||
record.getOwner(),
|
||||
supporter,
|
||||
(quizData != null) ? status : (statusOverride != null) ? statusOverride : status,
|
||||
BooleanUtils.toBooleanObject(record.getLmsSebRestriction()),
|
||||
record.getBrowserKeys(),
|
||||
BooleanUtils.toBooleanObject((quizData != null) ? record.getActive() : null),
|
||||
record.getLastupdate());
|
||||
|
|
|
@ -41,17 +41,6 @@ public interface ExamAdminService {
|
|||
* @return Result refer to the restriction flag or to an error when happened */
|
||||
Result<Boolean> isRestricted(Exam exam);
|
||||
|
||||
// /** Get the proctoring service settings for a certain exam to an error when happened.
|
||||
// *
|
||||
// * @param examId the exam instance
|
||||
// * @return Result refer to proctoring service settings for the exam. */
|
||||
// default Result<ProctoringServiceSettings> getProctoringServiceSettings(final Exam exam) {
|
||||
// if (exam == null || exam.id == null) {
|
||||
// return Result.ofRuntimeError("Invalid Exam model");
|
||||
// }
|
||||
// return getProctoringServiceSettings(exam.id);
|
||||
// }
|
||||
|
||||
/** Get proctoring service settings for a certain exam to an error when happened.
|
||||
*
|
||||
* @param examId the exam identifier
|
||||
|
@ -99,29 +88,4 @@ public interface ExamAdminService {
|
|||
.flatMap(settings -> getExamProctoringService(settings.serverType));
|
||||
}
|
||||
|
||||
// /** Get the exam proctoring service implementation of specified type.
|
||||
// *
|
||||
// * @param settings the ProctoringSettings that defines the ProctoringServerType
|
||||
// * @return ExamProctoringService instance */
|
||||
// default Result<ExamProctoringService> getExamProctoringService(final ProctoringServiceSettings settings) {
|
||||
// return Result.tryCatch(() -> getExamProctoringService(settings.serverType).getOrThrow());
|
||||
// }
|
||||
//
|
||||
// /** Get the exam proctoring service implementation for specified exam.
|
||||
// *
|
||||
// * @param exam the exam instance
|
||||
// * @return ExamProctoringService instance */
|
||||
// default Result<ExamProctoringService> getExamProctoringService(final Exam exam) {
|
||||
// return Result.tryCatch(() -> getExamProctoringService(exam.id).getOrThrow());
|
||||
// }
|
||||
//
|
||||
// /** Get the exam proctoring service implementation for specified exam.
|
||||
// *
|
||||
// * @param examId the exam identifier
|
||||
// * @return ExamProctoringService instance */
|
||||
// default Result<ExamProctoringService> getExamProctoringService(final Long examId) {
|
||||
// return getProctoringServiceSettings(examId)
|
||||
// .flatMap(this::getExamProctoringService);
|
||||
// }
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2021 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;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.client.ClientCredentials;
|
||||
import ch.ethz.seb.sebserver.gbl.client.ProxyData;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||
|
||||
/** Supplier for LmsAPITemplate to supply the templat with the needed LMS connection data. */
|
||||
public interface APITemplateDataSupplier {
|
||||
|
||||
/** Get the LmsSetup instance containing all setup attributes
|
||||
*
|
||||
* @return the LmsSetup instance containing all setup attributes */
|
||||
LmsSetup getLmsSetup();
|
||||
|
||||
/** Get the encoded LMS setup client credentials needed to access the LMS API.
|
||||
*
|
||||
* @return the encoded LMS setup client credentials needed to access the LMS API. */
|
||||
ClientCredentials getLmsClientCredentials();
|
||||
|
||||
/** Get the proxy data if available and if needed for LMS connection
|
||||
*
|
||||
* @return the proxy data if available and if needed for LMS connection */
|
||||
ProxyData getProxyData();
|
||||
|
||||
}
|
|
@ -35,6 +35,9 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
|||
* changes this service will be notifies about the change and release the related LmsAPITemplate from cache. */
|
||||
public interface LmsAPIService {
|
||||
|
||||
/** Reset and cleanup the caches if there are some */
|
||||
void cleanup();
|
||||
|
||||
/** Get the specified LmsSetup model by primary key
|
||||
*
|
||||
* @param id The identifier (PK) of the LmsSetup model
|
||||
|
|
|
@ -17,6 +17,7 @@ import java.util.Set;
|
|||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.async.MemoizingCircuitBreaker;
|
||||
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.QuizData;
|
||||
|
@ -27,64 +28,114 @@ import ch.ethz.seb.sebserver.gbl.model.user.ExamineeAccountDetails;
|
|||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.AbstractCourseAccess;
|
||||
|
||||
/** Defines an LMS API access template to build SEB Server LMS integration.
|
||||
*
|
||||
* </p>
|
||||
* A LMS integration consists of two main parts so far:
|
||||
* - The course API to search and request course data from LMS as well as resolve some LMS account details for a given
|
||||
* examineeId
|
||||
* - The SEB restriction API to apply SEB restriction data to the LMS to restrict a certain course for SEB
|
||||
* </p>
|
||||
*
|
||||
* A LmsAPITemplate is been constructed within a LmsSetup that defines the LMS setup data that is needed to connect to
|
||||
* a specific LMS instance of implemented type.
|
||||
*
|
||||
* The enum LmsSetup.LmsType defines the supported LMS types and for each type the supported API part(s).
|
||||
*
|
||||
* SEB Server uses the test functions that are defined for each LMS API part to test API access for a certain LMS
|
||||
* instance respectively the underling LMSSetup. Concrete implementations can do various tests to check full
|
||||
* or partial API Access and can flag missing or wrong LMSSetup attributes with the resulting LmsSetupTestResult.
|
||||
* <pre>
|
||||
* - The course API to search and request course data from LMS as well as resolve some
|
||||
* LMS account details for a given examineeId.
|
||||
* - The SEB restriction API to apply SEB restriction data to the LMS to restrict a
|
||||
* certain course for SEB.
|
||||
* </pre>
|
||||
* </p>
|
||||
*
|
||||
* <b>Course API</b></br>
|
||||
* All course API requests of this template shall not block and return as fast as possible
|
||||
* with the best result it can provide for the time on that the request was made.
|
||||
* </p>
|
||||
* Since the course API requests course data from potentially thousands of existing and
|
||||
* active courses, the course API can implement some caches if needed.</br>
|
||||
* A cache in the course API has manly two purposes; The first and prior purpose is to
|
||||
* be able to provide course data as fast as possible even if the LMS is not available or
|
||||
* busy at the time. The second purpose is to guarantee fast data access for the system
|
||||
* if this is needed and data actuality has second priority.</br>
|
||||
* Therefore usual get quiz data functions like {@link #getQuizzes(FilterMap filterMap) },
|
||||
* {@link #getQuizzes(Set<String> ids) } and {@link #getQuiz(final String id) }
|
||||
* shall always first try to connect to the LMS and request the specified data from the LMS.
|
||||
* If this succeeds the cache shall be updated with the received quizzes data and return them.
|
||||
* If this is not possible within a certain time, the implementation shall get as much of the
|
||||
* requested data from the cache and return them to the caller to not block the call too long
|
||||
* and allow the caller to return fast and present as much data as possible.</br>
|
||||
* This can be done with a {@link MemoizingCircuitBreaker} or a simple {@link CircuitBreaker}
|
||||
* with a separated cache, for example. The abstract implementation; {@link AbstractCourseAccess}
|
||||
* provides already defined wrapped circuit breaker for each call. To use it, just extend the
|
||||
* abstract class and implement the needed suppliers.</br>
|
||||
* On the other hand, dedicated cache access functions like {@link #getQuizzesFromCache(Set<String> ids) }
|
||||
* shall always first look into the cache to geht the requested data and if not
|
||||
* available, call the LMS and request the data from the LMS. If partial data is needed to get
|
||||
* be requested from the LMS, this functions shall also update the catch with the requested
|
||||
* and cache missed data afterwards.
|
||||
* </p>
|
||||
* <b>SEB restriction API</b></br>
|
||||
* For this API we need no caching since this is mostly about pushing data to the LMS for the LMS
|
||||
* to use. But this calls sahl also be protected within some kind of circuit breaker pattern to
|
||||
* avoid blocking on long latency.
|
||||
* </p>
|
||||
* </p>
|
||||
* A {@link LmsAPITemplate } will be constructed within the application with a {@link LmsSetup } instances.
|
||||
* The application constructs a {@link LmsAPITemplate } for each type of LMS setup when needed or requested and
|
||||
* there is not already a cached template or the cached template is out of date.</br>
|
||||
* The {@link LmsSetup } defines the data that is needed to connect to a specific LMS instance of implemented type
|
||||
* and is wrapped within a {@link LmsAPITemplate } instance that lives as long as there are no changes to the
|
||||
* {@link LmsSetup and the {@link LmsSetup } that is wrapped within the {@link LmsAPITemplate } is up to date.
|
||||
* <p>
|
||||
* The enum {@link LmsSetup.LmsType } defines the supported LMS types and for each type the supported API part(s).
|
||||
* <p>
|
||||
* The application uses the test functions that are defined for each LMS API part to test API access for a certain LMS
|
||||
* instance respectively the underling {@link LmsSetup }. Concrete implementations can do various tests to check full
|
||||
* or partial API Access and can flag missing or wrong {@link LmsSetup } attributes with the resulting
|
||||
* {@link LmsSetupTestResult }.</br>
|
||||
* SEB Server than uses an instance of this template to communicate with the an LMS. */
|
||||
public interface LmsAPITemplate {
|
||||
|
||||
/** Get the underling LMSSetup configuration for this LmsAPITemplate
|
||||
/** Get the LMS type of the concrete template implementation
|
||||
*
|
||||
* @return the underling LMSSetup configuration for this LmsAPITemplate */
|
||||
* @return the LMS type of the concrete template implementation */
|
||||
LmsSetup.LmsType getType();
|
||||
|
||||
/** Get the underling {@link LmsSetup } configuration for this LmsAPITemplate
|
||||
*
|
||||
* @return the underling {@link LmsSetup } configuration for this LmsAPITemplate */
|
||||
LmsSetup lmsSetup();
|
||||
|
||||
/** Performs a test for the underling LmsSetup configuration and checks if the
|
||||
// *******************************************************************
|
||||
// **** Course API functions *****************************************
|
||||
|
||||
/** Performs a test for the underling {@link LmsSetup } configuration and checks if the
|
||||
* LMS and the course API of the LMS can be accessed or if there are some difficulties,
|
||||
* missing configuration data or connection/authentication errors.
|
||||
*
|
||||
* @return LmsSetupTestResult instance with the test result report */
|
||||
* @return {@link LmsSetupTestResult } instance with the test result report */
|
||||
LmsSetupTestResult testCourseAccessAPI();
|
||||
|
||||
/** Performs a test for the underling LmsSetup configuration and checks if the
|
||||
* LMS and the course restriction API of the LMS can be accessed or if there are some difficulties,
|
||||
* missing configuration data or connection/authentication errors.
|
||||
/** Get an unsorted List of filtered {@link QuizData } from the LMS course/quiz API
|
||||
*
|
||||
* @return LmsSetupTestResult instance with the test result report */
|
||||
LmsSetupTestResult testCourseRestrictionAPI();
|
||||
|
||||
/** Get an unsorted List of filtered QuizData from the LMS course/quiz API
|
||||
* @param filterMap the {@link FilterMap } to get a filtered result. Possible filter attributes are:
|
||||
*
|
||||
* @param filterMap the FilterMap to get a filtered result. For possible filter attributes
|
||||
* see documentation on QuizData
|
||||
* @return Result of an unsorted List of filtered QuizData from the LMS course/quiz API
|
||||
* <pre>
|
||||
* {@link QuizData.FILTER_ATTR_QUIZ_NAME } The quiz name filter text (exclude all names that do not contain the given text)
|
||||
* {@link QuizData.FILTER_ATTR_START_TIME } The quiz start time (exclude all quizzes that starts before)
|
||||
* </pre>
|
||||
*
|
||||
* @return Result of an unsorted List of filtered {@link QuizData } from the LMS course/quiz API
|
||||
* or refer to an error when happened */
|
||||
Result<List<QuizData>> getQuizzes(FilterMap filterMap);
|
||||
|
||||
/** Get all QuizData for the set of QuizData identifiers from LMS API in a collection
|
||||
* of Result. If particular Quiz cannot be loaded because of errors or deletion,
|
||||
/** Get all {@link QuizData } for the set of {@link QuizData } identifiers from LMS API in a collection
|
||||
* of Result. If particular quiz cannot be loaded because of errors or deletion,
|
||||
* the Result will have an error reference.
|
||||
*
|
||||
* @param ids the Set of Quiz identifiers to get the QuizData for
|
||||
* @return Collection of all QuizData from the given id set */
|
||||
* @param ids the Set of Quiz identifiers to get the {@link QuizData } for
|
||||
* @return Collection of all {@link QuizData } from the given id set */
|
||||
Collection<Result<QuizData>> getQuizzes(Set<String> ids);
|
||||
|
||||
/** Get the quiz data with specified identifier.
|
||||
*
|
||||
* Default implementation: Uses getQuizzes(Set<String> ids) and returns the first matching or an error.
|
||||
* Default implementation: Uses {@link #getQuizzes(Set<String> ids) } and returns the first matching or an error.
|
||||
*
|
||||
* @param id the quiz data identifier
|
||||
* @return Result refer to the quiz data or to an error when happened */
|
||||
|
@ -99,26 +150,27 @@ public interface LmsAPITemplate {
|
|||
.orElse(Result.ofError(new ResourceNotFoundException(EntityType.EXAM, id)));
|
||||
}
|
||||
|
||||
/** Get all QuizData for the set of QuizData-identifiers (ids) from the LMS defined within the
|
||||
* underling LMSSetup, in a collection of Results.
|
||||
/** Get all {@link QuizData } for the set of {@link QuizData }-identifiers (ids) from the LMS defined within the
|
||||
* underling LmsSetup, in a collection of Results.
|
||||
*
|
||||
* If there is caching involved this function shall try to get the data from the cache first.
|
||||
*
|
||||
* NOTE: This function depends on the specific LMS implementation and on whether caching the quiz data
|
||||
* makes sense or not. Following strategy is recommended:
|
||||
* Looks first in the cache if the whole set of QuizData can be get from the cache.
|
||||
* Looks first in the cache if the whole set of {@link QuizData } can be get from the cache.
|
||||
* If all quizzes are cached, returns all from cache.
|
||||
* If one or more quiz is not in the cache, requests all quizzes from the API and refreshes the cache
|
||||
*
|
||||
* @param ids the Set of Quiz identifiers to get the QuizData for
|
||||
* @return Collection of all QuizData from the given id set */
|
||||
* @param ids the Set of Quiz identifiers to get the {@link QuizData } for
|
||||
* @return Collection of all {@link QuizData } from the given id set */
|
||||
Collection<Result<QuizData>> getQuizzesFromCache(Set<String> ids);
|
||||
|
||||
/** Convert an anonymous or temporary examineeUserId, sent by the SEB Client on LMS login,
|
||||
* to LMS examinee account details by requesting them on the LMS API with the given examineeUserId
|
||||
*
|
||||
* @param examineeUserId the examinee user identifier derived from SEB Client
|
||||
* @return a Result refer to the ExamineeAccountDetails instance or to an error when happened or not supported */
|
||||
* @return a Result refer to the {@link ExamineeAccountDetails } instance or to an error when happened or not
|
||||
* supported */
|
||||
Result<ExamineeAccountDetails> getExamineeAccountDetails(String examineeUserId);
|
||||
|
||||
/** Used to convert an anonymous or temporary examineeUserId, sent by the SEB Client on LMS login,
|
||||
|
@ -142,11 +194,23 @@ public interface LmsAPITemplate {
|
|||
* @return Result referencing to the Chapters model for the given course or to an error when happened. */
|
||||
Result<Chapters> getCourseChapters(String courseId);
|
||||
|
||||
/** Get SEB restriction data form LMS within a SEBRestrictionData instance. The available restriction details
|
||||
// ****************************************************************************
|
||||
// **** SEB restriction API functions *****************************************
|
||||
|
||||
/** Performs a test for the underling {@link LmsSetup } configuration and checks if the
|
||||
* LMS and the course restriction API of the LMS can be accessed or if there are some difficulties,
|
||||
* missing configuration data or connection/authentication errors.
|
||||
*
|
||||
* @return {@link LmsSetupTestResult } instance with the test result report */
|
||||
LmsSetupTestResult testCourseRestrictionAPI();
|
||||
|
||||
/** Get SEB restriction data form LMS within a {@link SEBRestrictionData } instance. The available restriction
|
||||
* details
|
||||
* depends on the type of LMS but shall at least contains the config-key(s) and the browser-exam-key(s).
|
||||
*
|
||||
* @param exam the exam to get the SEB restriction data for
|
||||
* @return Result refer to the SEBRestrictionData instance or to an ResourceNotFoundException if the restriction is
|
||||
* @return Result refer to the {@link SEBRestrictionData } instance or to an ResourceNotFoundException if the
|
||||
* restriction is
|
||||
* missing or to another exception on unexpected error case */
|
||||
Result<SEBRestriction> getSEBClientRestriction(Exam exam);
|
||||
|
||||
|
@ -154,7 +218,8 @@ public interface LmsAPITemplate {
|
|||
*
|
||||
* @param externalExamId The exam/course identifier from LMS side (Exam.externalId)
|
||||
* @param sebRestrictionData containing all data for SEB Client restriction to apply to the LMS
|
||||
* @return Result refer to the given SEBRestrictionData if restriction was successful or to an error if not */
|
||||
* @return Result refer to the given {@link SEBRestrictionData } if restriction was successful or to an error if
|
||||
* not */
|
||||
Result<SEBRestriction> applySEBClientRestriction(
|
||||
String externalExamId,
|
||||
SEBRestriction sebRestrictionData);
|
||||
|
|
|
@ -8,9 +8,6 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.webservice.servicelayer.lms;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.client.ClientCredentials;
|
||||
import ch.ethz.seb.sebserver.gbl.client.ProxyData;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
|
||||
|
@ -23,16 +20,10 @@ public interface LmsAPITemplateFactory {
|
|||
* @return the LMS type if a specific implementation */
|
||||
LmsType lmsType();
|
||||
|
||||
/** Creates a LmsAPITemplate for the specific implements LMS type.
|
||||
/** Creates a {@link LmsAPITemplate } for the specific implements LMS type
|
||||
* And provides it with the needed {@link APITemplateDataSupplier }
|
||||
*
|
||||
* @param lmsSetup the LMS setup data to initialize the template
|
||||
* @param credentials the access data for accessing the LMS API. Either client credentials or access token from LMS
|
||||
* setup input
|
||||
* @param proxyData The proxy data used to connect to the LMS if needed.
|
||||
* @return Result refer to the LmsAPITemplate or to an error when happened */
|
||||
Result<LmsAPITemplate> create(
|
||||
final LmsSetup lmsSetup,
|
||||
final ClientCredentials credentials,
|
||||
final ProxyData proxyData);
|
||||
* @param apiTemplateDataSupplier supplies all needed actual LMS setup data */
|
||||
Result<LmsAPITemplate> create(final APITemplateDataSupplier apiTemplateDataSupplier);
|
||||
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.webservice.servicelayer.lms;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.SEBRestriction;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
|
@ -19,6 +21,9 @@ public interface SEBRestrictionService {
|
|||
|
||||
String SEB_RESTRICTION_ADDITIONAL_PROPERTY_CONFIG_KEY = "config_key";
|
||||
|
||||
/** Get the LmsAPIService that is used by the SEBRestrictionService */
|
||||
LmsAPIService getLmsAPIService();
|
||||
|
||||
/** Get the SEBRestriction properties for specified Exam.
|
||||
*
|
||||
* @param exam the Exam
|
||||
|
@ -50,4 +55,6 @@ public interface SEBRestrictionService {
|
|||
* @return Result refer to the Exam instance or to an error if happened */
|
||||
Result<Exam> releaseSEBClientRestriction(Exam exam);
|
||||
|
||||
boolean checkConsistency(@NotNull Long lmsSetupId, Exam exam);
|
||||
|
||||
}
|
||||
|
|
|
@ -35,9 +35,9 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
|||
* API requests in a protected environment.
|
||||
*
|
||||
* Extend this to implement a concrete course access API for a given type of LMS. */
|
||||
public abstract class CourseAccess {
|
||||
public abstract class AbstractCourseAccess {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(CourseAccess.class);
|
||||
private static final Logger log = LoggerFactory.getLogger(AbstractCourseAccess.class);
|
||||
|
||||
/** Fetch status that indicates an asynchronous quiz data fetch status if the
|
||||
* concrete implementation has such. */
|
||||
|
@ -54,7 +54,7 @@ public abstract class CourseAccess {
|
|||
/** CircuitBreaker for protected examinee account details requests */
|
||||
protected final CircuitBreaker<ExamineeAccountDetails> accountDetailRequest;
|
||||
|
||||
protected CourseAccess(
|
||||
protected AbstractCourseAccess(
|
||||
final AsyncService asyncService,
|
||||
final Environment environment) {
|
||||
|
|
@ -22,10 +22,10 @@ import org.apache.commons.lang3.StringUtils;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.client.ClientCredentialService;
|
||||
import ch.ethz.seb.sebserver.gbl.client.ClientCredentials;
|
||||
import ch.ethz.seb.sebserver.gbl.client.ProxyData;
|
||||
|
@ -38,6 +38,8 @@ import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
|||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.LmsSetupDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.APITemplateDataSupplier;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplateFactory;
|
||||
|
@ -71,21 +73,9 @@ public class LmsAPIServiceImpl implements LmsAPIService {
|
|||
this.templateFactories = new EnumMap<>(factories);
|
||||
}
|
||||
|
||||
/** Listen to LmsSetupChangeEvent to release an affected LmsAPITemplate from cache
|
||||
*
|
||||
* @param event the event holding the changed LmsSetup */
|
||||
@EventListener
|
||||
public void notifyLmsSetupChange(final LmsSetupChangeEvent event) {
|
||||
final LmsSetup lmsSetup = event.getLmsSetup();
|
||||
if (lmsSetup == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("LmsSetup changed. Update cache by removing eventually used references");
|
||||
}
|
||||
|
||||
this.cache.remove(new CacheKey(lmsSetup.getModelId(), 0));
|
||||
@Override
|
||||
public void cleanup() {
|
||||
this.cache.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -107,10 +97,19 @@ public class LmsAPIServiceImpl implements LmsAPIService {
|
|||
|
||||
@Override
|
||||
public Result<LmsAPITemplate> getLmsAPITemplate(final String lmsSetupId) {
|
||||
return Result.tryCatch(() -> this.lmsSetupDAO
|
||||
.byModelId(lmsSetupId)
|
||||
.getOrThrow())
|
||||
.flatMap(this::getLmsAPITemplate);
|
||||
return Result.tryCatch(() -> {
|
||||
LmsAPITemplate lmsAPITemplate = getFromCache(lmsSetupId);
|
||||
if (lmsAPITemplate == null) {
|
||||
lmsAPITemplate = createLmsSetupTemplate(lmsSetupId);
|
||||
if (lmsAPITemplate != null) {
|
||||
this.cache.put(new CacheKey(lmsSetupId, System.currentTimeMillis()), lmsAPITemplate);
|
||||
}
|
||||
}
|
||||
if (lmsAPITemplate == null) {
|
||||
throw new ResourceNotFoundException(EntityType.LMS_SETUP, lmsSetupId);
|
||||
}
|
||||
return lmsAPITemplate;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -129,23 +128,22 @@ public class LmsAPIServiceImpl implements LmsAPIService {
|
|||
|
||||
@Override
|
||||
public LmsSetupTestResult testAdHoc(final LmsSetup lmsSetup) {
|
||||
final ClientCredentials lmsCredentials = this.clientCredentialService.encryptClientCredentials(
|
||||
lmsSetup.lmsAuthName,
|
||||
lmsSetup.lmsAuthSecret,
|
||||
lmsSetup.lmsRestApiToken)
|
||||
.getOrThrow();
|
||||
final AdHocAPITemplateDataSupplier apiTemplateDataSupplier = new AdHocAPITemplateDataSupplier(
|
||||
lmsSetup,
|
||||
this.clientCredentialService);
|
||||
|
||||
final ProxyData proxyData = (StringUtils.isNoneBlank(lmsSetup.proxyHost))
|
||||
? new ProxyData(
|
||||
lmsSetup.proxyHost,
|
||||
lmsSetup.proxyPort,
|
||||
this.clientCredentialService.encryptClientCredentials(
|
||||
lmsSetup.proxyAuthUsername,
|
||||
lmsSetup.proxyAuthSecret)
|
||||
.getOrThrow())
|
||||
: null;
|
||||
final LmsAPITemplate lmsSetupTemplate = createLmsSetupTemplate(apiTemplateDataSupplier);
|
||||
|
||||
return test(createLmsSetupTemplate(lmsSetup, lmsCredentials, proxyData));
|
||||
final LmsSetupTestResult testCourseAccessAPI = lmsSetupTemplate.testCourseAccessAPI();
|
||||
if (!testCourseAccessAPI.isOk()) {
|
||||
return testCourseAccessAPI;
|
||||
}
|
||||
|
||||
if (lmsSetupTemplate.lmsSetup().getLmsType().features.contains(LmsSetup.Features.SEB_RESTRICTION)) {
|
||||
return lmsSetupTemplate.testCourseRestrictionAPI();
|
||||
}
|
||||
|
||||
return LmsSetupTestResult.ofOkay();
|
||||
}
|
||||
|
||||
/** Collect all QuizData from all affecting LmsSetup.
|
||||
|
@ -183,6 +181,7 @@ public class LmsAPIServiceImpl implements LmsAPIService {
|
|||
return this.lmsSetupDAO.all(institutionId, true)
|
||||
.getOrThrow()
|
||||
.parallelStream()
|
||||
.map(LmsSetup::getModelId)
|
||||
.map(this::getLmsAPITemplate)
|
||||
.flatMap(Result::onErrorLogAndSkip)
|
||||
.map(template -> template.getQuizzes(filterMap))
|
||||
|
@ -193,18 +192,7 @@ public class LmsAPIServiceImpl implements LmsAPIService {
|
|||
});
|
||||
}
|
||||
|
||||
private Result<LmsAPITemplate> getLmsAPITemplate(final LmsSetup lmsSetup) {
|
||||
return Result.tryCatch(() -> {
|
||||
LmsAPITemplate lmsAPITemplate = getFromCache(lmsSetup);
|
||||
if (lmsAPITemplate == null) {
|
||||
lmsAPITemplate = createLmsSetupTemplate(lmsSetup);
|
||||
this.cache.put(new CacheKey(lmsSetup.getModelId(), System.currentTimeMillis()), lmsAPITemplate);
|
||||
}
|
||||
return lmsAPITemplate;
|
||||
});
|
||||
}
|
||||
|
||||
private LmsAPITemplate getFromCache(final LmsSetup lmsSetup) {
|
||||
private LmsAPITemplate getFromCache(final String lmsSetupId) {
|
||||
// first cleanup the cache by removing old instances
|
||||
final long currentTimeMillis = System.currentTimeMillis();
|
||||
new ArrayList<>(this.cache.keySet())
|
||||
|
@ -212,40 +200,103 @@ public class LmsAPIServiceImpl implements LmsAPIService {
|
|||
.filter(key -> key.creationTimestamp - currentTimeMillis > Constants.DAY_IN_MILLIS)
|
||||
.forEach(this.cache::remove);
|
||||
// get from cache
|
||||
return this.cache.get(new CacheKey(lmsSetup.getModelId(), 0));
|
||||
return this.cache.get(new CacheKey(lmsSetupId, 0));
|
||||
}
|
||||
|
||||
private LmsAPITemplate createLmsSetupTemplate(final LmsSetup lmsSetup) {
|
||||
private LmsAPITemplate createLmsSetupTemplate(final String lmsSetupId) {
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Create new LmsAPITemplate for id: {}", lmsSetup.getModelId());
|
||||
log.debug("Create new LmsAPITemplate for id: {}", lmsSetupId);
|
||||
}
|
||||
|
||||
final ClientCredentials credentials = this.lmsSetupDAO
|
||||
.getLmsAPIAccessCredentials(lmsSetup.getModelId())
|
||||
.getOrThrow();
|
||||
|
||||
final ProxyData proxyData = this.lmsSetupDAO
|
||||
.getLmsAPIAccessProxyData(lmsSetup.getModelId())
|
||||
.getOr(null);
|
||||
|
||||
return createLmsSetupTemplate(lmsSetup, credentials, proxyData);
|
||||
return createLmsSetupTemplate(new PersistentAPITemplateDataSupplier(
|
||||
lmsSetupId,
|
||||
this.lmsSetupDAO));
|
||||
}
|
||||
|
||||
private LmsAPITemplate createLmsSetupTemplate(
|
||||
final LmsSetup lmsSetup,
|
||||
final ClientCredentials credentials,
|
||||
final ProxyData proxyData) {
|
||||
private LmsAPITemplate createLmsSetupTemplate(final APITemplateDataSupplier apiTemplateDataSupplier) {
|
||||
|
||||
if (!this.templateFactories.containsKey(lmsSetup.lmsType)) {
|
||||
throw new UnsupportedOperationException("No support for LMS Type: " + lmsSetup.lmsType);
|
||||
final LmsType lmsType = apiTemplateDataSupplier.getLmsSetup().lmsType;
|
||||
|
||||
if (!this.templateFactories.containsKey(lmsType)) {
|
||||
throw new UnsupportedOperationException("No support for LMS Type: " + lmsType);
|
||||
}
|
||||
|
||||
final LmsAPITemplateFactory lmsAPITemplateFactory = this.templateFactories.get(lmsSetup.lmsType);
|
||||
return lmsAPITemplateFactory.create(lmsSetup, credentials, proxyData)
|
||||
final LmsAPITemplateFactory lmsAPITemplateFactory = this.templateFactories
|
||||
.get(lmsType);
|
||||
|
||||
return lmsAPITemplateFactory
|
||||
.create(apiTemplateDataSupplier)
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
/** Used to always get the actual LMS connection data from persistent */
|
||||
private static final class PersistentAPITemplateDataSupplier implements APITemplateDataSupplier {
|
||||
|
||||
private final String lmsSetupId;
|
||||
private final LmsSetupDAO lmsSetupDAO;
|
||||
|
||||
public PersistentAPITemplateDataSupplier(final String lmsSetupId, final LmsSetupDAO lmsSetupDAO) {
|
||||
this.lmsSetupId = lmsSetupId;
|
||||
this.lmsSetupDAO = lmsSetupDAO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LmsSetup getLmsSetup() {
|
||||
return this.lmsSetupDAO.byModelId(this.lmsSetupId).getOrThrow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientCredentials getLmsClientCredentials() {
|
||||
return this.lmsSetupDAO.getLmsAPIAccessCredentials(this.lmsSetupId).getOrThrow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProxyData getProxyData() {
|
||||
return this.lmsSetupDAO.getLmsAPIAccessProxyData(this.lmsSetupId).getOr(null);
|
||||
}
|
||||
}
|
||||
|
||||
/** Used to test LMS connection data that are not yet persistently stored */
|
||||
private static final class AdHocAPITemplateDataSupplier implements APITemplateDataSupplier {
|
||||
|
||||
private final LmsSetup lmsSetup;
|
||||
private final ClientCredentialService clientCredentialService;
|
||||
|
||||
public AdHocAPITemplateDataSupplier(
|
||||
final LmsSetup lmsSetup,
|
||||
final ClientCredentialService clientCredentialService) {
|
||||
this.lmsSetup = lmsSetup;
|
||||
this.clientCredentialService = clientCredentialService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LmsSetup getLmsSetup() {
|
||||
return this.lmsSetup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientCredentials getLmsClientCredentials() {
|
||||
return this.clientCredentialService.encryptClientCredentials(
|
||||
this.lmsSetup.getLmsAuthName(),
|
||||
this.lmsSetup.getLmsAuthSecret())
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProxyData getProxyData() {
|
||||
return (StringUtils.isNoneBlank(this.lmsSetup.proxyHost))
|
||||
? new ProxyData(
|
||||
this.lmsSetup.proxyHost,
|
||||
this.lmsSetup.proxyPort,
|
||||
this.clientCredentialService.encryptClientCredentials(
|
||||
this.lmsSetup.proxyAuthUsername,
|
||||
this.lmsSetup.proxyAuthSecret)
|
||||
.getOrThrow())
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class CacheKey {
|
||||
final String lmsSetupId;
|
||||
final long creationTimestamp;
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 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;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||
|
||||
public class LmsSetupChangeEvent extends ApplicationEvent {
|
||||
|
||||
private static final long serialVersionUID = -7239994198026689531L;
|
||||
|
||||
public LmsSetupChangeEvent(final LmsSetup source) {
|
||||
super(source);
|
||||
}
|
||||
|
||||
public LmsSetup getLmsSetup() {
|
||||
return (LmsSetup) this.source;
|
||||
}
|
||||
|
||||
}
|
|
@ -18,6 +18,8 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -29,6 +31,7 @@ import ch.ethz.seb.sebserver.gbl.Constants;
|
|||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
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.Features;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
|
@ -62,15 +65,38 @@ public class SEBRestrictionServiceImpl implements SEBRestrictionService {
|
|||
this.examConfigService = examConfigService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LmsAPIService getLmsAPIService() {
|
||||
return this.lmsAPIService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkConsistency(@NotNull final Long lmsSetupId, final Exam exam) {
|
||||
final LmsSetup lmsSetup = this.lmsAPIService
|
||||
.getLmsSetup(exam.lmsSetupId)
|
||||
.getOr(null);
|
||||
|
||||
// check only if SEB_RESTRICTION feature is on
|
||||
if (lmsSetup != null && lmsSetup.lmsType.features.contains(Features.SEB_RESTRICTION)) {
|
||||
if (!exam.sebRestriction) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Result<SEBRestriction> getSEBRestrictionFromExam(final Exam exam) {
|
||||
return Result.tryCatch(() -> {
|
||||
// load the config keys from restriction and merge with new generated config keys
|
||||
final long currentTimeMillis = System.currentTimeMillis();
|
||||
final Set<String> configKeys = new HashSet<>();
|
||||
final Collection<String> generatedKeys = this.examConfigService
|
||||
.generateConfigKeys(exam.institutionId, exam.id)
|
||||
.getOrThrow();
|
||||
System.out.println("******* " + (System.currentTimeMillis() - currentTimeMillis));
|
||||
|
||||
configKeys.addAll(generatedKeys);
|
||||
if (generatedKeys != null && !generatedKeys.isEmpty()) {
|
||||
|
@ -134,6 +160,7 @@ public class SEBRestrictionServiceImpl implements SEBRestrictionService {
|
|||
null, null, null, null, null, null, null, null, null, null,
|
||||
exam.supporter,
|
||||
exam.status,
|
||||
null,
|
||||
(browserExamKeys != null && !browserExamKeys.isEmpty())
|
||||
? StringUtils.join(browserExamKeys, Constants.LIST_SEPARATOR_CHAR)
|
||||
: StringUtils.EMPTY,
|
||||
|
@ -167,28 +194,31 @@ public class SEBRestrictionServiceImpl implements SEBRestrictionService {
|
|||
|
||||
@Override
|
||||
public Result<Exam> applySEBClientRestriction(final Exam exam) {
|
||||
if (!this.lmsAPIService
|
||||
.getLmsSetup(exam.lmsSetupId)
|
||||
.getOrThrow().lmsType.features.contains(Features.SEB_RESTRICTION)) {
|
||||
return Result.tryCatch(() -> {
|
||||
if (!this.lmsAPIService
|
||||
.getLmsSetup(exam.lmsSetupId)
|
||||
.getOrThrow().lmsType.features.contains(Features.SEB_RESTRICTION)) {
|
||||
|
||||
return Result.of(exam);
|
||||
}
|
||||
return exam;
|
||||
}
|
||||
|
||||
return this.getSEBRestrictionFromExam(exam)
|
||||
.map(sebRestrictionData -> {
|
||||
return this.getSEBRestrictionFromExam(exam)
|
||||
.map(sebRestrictionData -> {
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Applying SEB Client restriction on LMS with: {}", sebRestrictionData);
|
||||
}
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Applying SEB Client restriction on LMS with: {}", sebRestrictionData);
|
||||
}
|
||||
|
||||
return this.lmsAPIService
|
||||
.getLmsAPITemplate(exam.lmsSetupId)
|
||||
.flatMap(lmsTemplate -> lmsTemplate.applySEBClientRestriction(
|
||||
exam.externalId,
|
||||
sebRestrictionData))
|
||||
.map(data -> exam)
|
||||
.getOrThrow();
|
||||
});
|
||||
return this.lmsAPIService
|
||||
.getLmsAPITemplate(exam.lmsSetupId)
|
||||
.flatMap(lmsTemplate -> lmsTemplate.applySEBClientRestriction(
|
||||
exam.externalId,
|
||||
sebRestrictionData))
|
||||
.map(data -> exam)
|
||||
.getOrThrow();
|
||||
})
|
||||
.getOrThrow();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.edx;
|
|||
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -53,12 +54,13 @@ import ch.ethz.seb.sebserver.gbl.util.Result;
|
|||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.CourseAccess;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.APITemplateDataSupplier;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.AbstractCourseAccess;
|
||||
|
||||
/** Implements the LmsAPITemplate for Open edX LMS Course API access.
|
||||
*
|
||||
* See also: https://course-catalog-api-guide.readthedocs.io */
|
||||
final class OpenEdxCourseAccess extends CourseAccess {
|
||||
final class OpenEdxCourseAccess extends AbstractCourseAccess {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(OpenEdxCourseAccess.class);
|
||||
|
||||
|
@ -70,7 +72,6 @@ final class OpenEdxCourseAccess extends CourseAccess {
|
|||
private static final String OPEN_EDX_DEFAULT_USER_PROFILE_ENDPOINT = "/api/user/v1/accounts?username=";
|
||||
|
||||
private final JSONMapper jsonMapper;
|
||||
private final LmsSetup lmsSetup;
|
||||
private final OpenEdxRestTemplateFactory openEdxRestTemplateFactory;
|
||||
private final WebserviceInfo webserviceInfo;
|
||||
private final MemoizingCircuitBreaker<List<QuizData>> allQuizzesRequest;
|
||||
|
@ -80,7 +81,6 @@ final class OpenEdxCourseAccess extends CourseAccess {
|
|||
|
||||
public OpenEdxCourseAccess(
|
||||
final JSONMapper jsonMapper,
|
||||
final LmsSetup lmsSetup,
|
||||
final OpenEdxRestTemplateFactory openEdxRestTemplateFactory,
|
||||
final WebserviceInfo webserviceInfo,
|
||||
final AsyncService asyncService,
|
||||
|
@ -88,7 +88,6 @@ final class OpenEdxCourseAccess extends CourseAccess {
|
|||
|
||||
super(asyncService, environment);
|
||||
this.jsonMapper = jsonMapper;
|
||||
this.lmsSetup = lmsSetup;
|
||||
this.openEdxRestTemplateFactory = openEdxRestTemplateFactory;
|
||||
this.webserviceInfo = webserviceInfo;
|
||||
|
||||
|
@ -130,8 +129,14 @@ final class OpenEdxCourseAccess extends CourseAccess {
|
|||
};
|
||||
}
|
||||
|
||||
APITemplateDataSupplier getApiTemplateDataSupplier() {
|
||||
return this.openEdxRestTemplateFactory.apiTemplateDataSupplier;
|
||||
}
|
||||
|
||||
LmsSetupTestResult initAPIAccess() {
|
||||
|
||||
final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup();
|
||||
|
||||
final LmsSetupTestResult attributesCheck = this.openEdxRestTemplateFactory.test();
|
||||
if (!attributesCheck.isOk()) {
|
||||
return attributesCheck;
|
||||
|
@ -148,13 +153,14 @@ final class OpenEdxCourseAccess extends CourseAccess {
|
|||
final OAuth2RestTemplate restTemplate = restTemplateRequest.get();
|
||||
|
||||
try {
|
||||
this.getEdxPage(this.lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_COURSE_ENDPOINT, restTemplate);
|
||||
restTemplate.getAccessToken();
|
||||
//this.getEdxPage(lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_COURSE_ENDPOINT, restTemplate);
|
||||
} catch (final RuntimeException e) {
|
||||
|
||||
restTemplate.setAuthenticator(new EdxOAuth2RequestAuthenticator());
|
||||
|
||||
try {
|
||||
this.getEdxPage(this.lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_COURSE_ENDPOINT, restTemplate);
|
||||
this.getEdxPage(lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_COURSE_ENDPOINT, restTemplate);
|
||||
} catch (final RuntimeException ee) {
|
||||
log.error("Failed to access Open edX course API: ", ee);
|
||||
return LmsSetupTestResult.ofQuizAccessAPIError(ee.getMessage());
|
||||
|
@ -167,14 +173,18 @@ final class OpenEdxCourseAccess extends CourseAccess {
|
|||
@Override
|
||||
public Result<ExamineeAccountDetails> getExamineeAccountDetails(final String examineeSessionId) {
|
||||
return Result.tryCatch(() -> {
|
||||
final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup();
|
||||
final HttpHeaders httpHeaders = new HttpHeaders();
|
||||
final OAuth2RestTemplate template = getRestTemplate()
|
||||
.getOrThrow();
|
||||
|
||||
final String externalStartURI = this.webserviceInfo.getLmsExternalAddressAlias(this.lmsSetup.lmsApiUrl);
|
||||
final String externalStartURI = this.webserviceInfo
|
||||
.getLmsExternalAddressAlias(lmsSetup.lmsApiUrl);
|
||||
|
||||
final String uri = (externalStartURI != null)
|
||||
? externalStartURI + OPEN_EDX_DEFAULT_USER_PROFILE_ENDPOINT + examineeSessionId
|
||||
: this.lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_USER_PROFILE_ENDPOINT + examineeSessionId;
|
||||
final HttpHeaders httpHeaders = new HttpHeaders();
|
||||
: lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_USER_PROFILE_ENDPOINT + examineeSessionId;
|
||||
|
||||
final String responseJSON = template.exchange(
|
||||
uri,
|
||||
HttpMethod.GET,
|
||||
|
@ -211,9 +221,24 @@ final class OpenEdxCourseAccess extends CourseAccess {
|
|||
|
||||
@Override
|
||||
protected Supplier<List<QuizData>> quizzesSupplier(final Set<String> ids) {
|
||||
return () -> getRestTemplate()
|
||||
.map(template -> this.collectQuizzes(template, ids))
|
||||
.getOrThrow();
|
||||
|
||||
if (ids.size() == 1) {
|
||||
return () -> {
|
||||
final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup();
|
||||
final String externalStartURI = getExternalLMSServerAddress(lmsSetup);
|
||||
return Arrays.asList(quizDataOf(
|
||||
lmsSetup,
|
||||
getOneCourses(
|
||||
lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_COURSE_ENDPOINT,
|
||||
getRestTemplate().getOrThrow(),
|
||||
ids.iterator().next()),
|
||||
externalStartURI));
|
||||
};
|
||||
} else {
|
||||
return () -> getRestTemplate()
|
||||
.map(template -> this.collectQuizzes(template, ids))
|
||||
.getOrThrow();
|
||||
}
|
||||
}
|
||||
|
||||
private Supplier<List<QuizData>> quizzesSupplier() {
|
||||
|
@ -225,8 +250,10 @@ final class OpenEdxCourseAccess extends CourseAccess {
|
|||
@Override
|
||||
protected Supplier<Chapters> getCourseChaptersSupplier(final String courseId) {
|
||||
return () -> {
|
||||
final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup();
|
||||
|
||||
final String uri =
|
||||
this.lmsSetup.lmsApiUrl +
|
||||
lmsSetup.lmsApiUrl +
|
||||
OPEN_EDX_DEFAULT_BLOCKS_ENDPOINT +
|
||||
Utils.encodeFormURL_UTF_8(courseId);
|
||||
return new Chapters(getCourseBlocks(uri)
|
||||
|
@ -239,16 +266,18 @@ final class OpenEdxCourseAccess extends CourseAccess {
|
|||
}
|
||||
|
||||
private ArrayList<QuizData> collectQuizzes(final OAuth2RestTemplate restTemplate, final Set<String> ids) {
|
||||
final String externalStartURI = getExternalLMSServerAddress(this.lmsSetup);
|
||||
final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup();
|
||||
final String externalStartURI = getExternalLMSServerAddress(lmsSetup);
|
||||
|
||||
return collectCourses(
|
||||
this.lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_COURSE_ENDPOINT,
|
||||
lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_COURSE_ENDPOINT,
|
||||
restTemplate,
|
||||
ids)
|
||||
.stream()
|
||||
.reduce(
|
||||
new ArrayList<>(),
|
||||
(list, courseData) -> {
|
||||
list.add(quizDataOf(this.lmsSetup, courseData, externalStartURI));
|
||||
list.add(quizDataOf(lmsSetup, courseData, externalStartURI));
|
||||
return list;
|
||||
},
|
||||
(list1, list2) -> {
|
||||
|
@ -258,15 +287,16 @@ final class OpenEdxCourseAccess extends CourseAccess {
|
|||
}
|
||||
|
||||
private ArrayList<QuizData> collectAllQuizzes(final OAuth2RestTemplate restTemplate) {
|
||||
final String externalStartURI = getExternalLMSServerAddress(this.lmsSetup);
|
||||
final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup();
|
||||
final String externalStartURI = getExternalLMSServerAddress(lmsSetup);
|
||||
return collectAllCourses(
|
||||
this.lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_COURSE_ENDPOINT,
|
||||
lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_COURSE_ENDPOINT,
|
||||
restTemplate)
|
||||
.stream()
|
||||
.reduce(
|
||||
new ArrayList<>(),
|
||||
(list, courseData) -> {
|
||||
list.add(quizDataOf(this.lmsSetup, courseData, externalStartURI));
|
||||
list.add(quizDataOf(lmsSetup, courseData, externalStartURI));
|
||||
return list;
|
||||
},
|
||||
(list1, list2) -> {
|
||||
|
@ -322,6 +352,21 @@ final class OpenEdxCourseAccess extends CourseAccess {
|
|||
return collector;
|
||||
}
|
||||
|
||||
private CourseData getOneCourses(
|
||||
final String pageURI,
|
||||
final OAuth2RestTemplate restTemplate,
|
||||
final String id) {
|
||||
|
||||
final HttpHeaders httpHeaders = new HttpHeaders();
|
||||
final ResponseEntity<CourseData> exchange = restTemplate.exchange(
|
||||
pageURI + "/" + id,
|
||||
HttpMethod.GET,
|
||||
new HttpEntity<>(httpHeaders),
|
||||
CourseData.class);
|
||||
|
||||
return exchange.getBody();
|
||||
}
|
||||
|
||||
private List<CourseData> collectAllCourses(final String pageURI, final OAuth2RestTemplate restTemplate) {
|
||||
final List<CourseData> collector = new ArrayList<>();
|
||||
EdXPage page = getEdxPage(pageURI, restTemplate).getBody();
|
||||
|
|
|
@ -8,8 +8,6 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.edx;
|
||||
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpEntity;
|
||||
|
@ -23,8 +21,6 @@ import org.springframework.web.client.HttpClientErrorException;
|
|||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException;
|
||||
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.OpenEdxSEBRestriction;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||
|
@ -43,19 +39,16 @@ public class OpenEdxCourseRestriction {
|
|||
private static final String OPEN_EDX_DEFAULT_COURSE_RESTRICTION_API_PATH =
|
||||
"/seb-openedx/api/v1/course/%s/configuration/";
|
||||
|
||||
private final LmsSetup lmsSetup;
|
||||
private final JSONMapper jsonMapper;
|
||||
private final OpenEdxRestTemplateFactory openEdxRestTemplateFactory;
|
||||
|
||||
private OAuth2RestTemplate restTemplate;
|
||||
|
||||
protected OpenEdxCourseRestriction(
|
||||
final LmsSetup lmsSetup,
|
||||
final JSONMapper jsonMapper,
|
||||
final OpenEdxRestTemplateFactory openEdxRestTemplateFactory,
|
||||
final int restrictionAPIPushCount) {
|
||||
|
||||
this.lmsSetup = lmsSetup;
|
||||
this.jsonMapper = jsonMapper;
|
||||
this.openEdxRestTemplateFactory = openEdxRestTemplateFactory;
|
||||
}
|
||||
|
@ -75,15 +68,16 @@ public class OpenEdxCourseRestriction {
|
|||
}
|
||||
|
||||
final OAuth2RestTemplate restTemplate = restTemplateRequest.get();
|
||||
|
||||
// NOTE: since the OPEN_EDX_DEFAULT_COURSE_RESTRICTION_API_INFO endpoint is
|
||||
// not accessible within OAuth2 authentication (just with user - authentication),
|
||||
// we can only check if the endpoint is available for now. This is checked
|
||||
// if there is no 404 response.
|
||||
// TODO: Ask eduNEXT to implement also OAuth2 API access for this endpoint to be able
|
||||
// to check the version of the installed plugin.
|
||||
final String url = this.lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_COURSE_RESTRICTION_API_INFO;
|
||||
try {
|
||||
final LmsSetup lmsSetup = this.openEdxRestTemplateFactory.apiTemplateDataSupplier.getLmsSetup();
|
||||
|
||||
// NOTE: since the OPEN_EDX_DEFAULT_COURSE_RESTRICTION_API_INFO endpoint is
|
||||
// not accessible within OAuth2 authentication (just with user - authentication),
|
||||
// we can only check if the endpoint is available for now. This is checked
|
||||
// if there is no 404 response.
|
||||
// TODO: Ask eduNEXT to implement also OAuth2 API access for this endpoint to be able
|
||||
// to check the version of the installed plugin.
|
||||
final String url = lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_COURSE_RESTRICTION_API_INFO;
|
||||
|
||||
restTemplate.exchange(
|
||||
url,
|
||||
|
@ -111,8 +105,10 @@ public class OpenEdxCourseRestriction {
|
|||
log.debug("GET SEB Client restriction on course: {}", courseId);
|
||||
}
|
||||
|
||||
final LmsSetup lmsSetup = this.openEdxRestTemplateFactory.apiTemplateDataSupplier.getLmsSetup();
|
||||
|
||||
return Result.tryCatch(() -> {
|
||||
final String url = this.lmsSetup.lmsApiUrl + getSEBRestrictionUrl(courseId);
|
||||
final String url = lmsSetup.lmsApiUrl + getSEBRestrictionUrl(courseId);
|
||||
final HttpHeaders httpHeaders = new HttpHeaders();
|
||||
httpHeaders.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
|
||||
httpHeaders.add(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate");
|
||||
|
@ -146,9 +142,28 @@ public class OpenEdxCourseRestriction {
|
|||
log.debug("PUT SEB Client restriction on course: {} : {}", courseId, restriction);
|
||||
}
|
||||
|
||||
return handleSEBRestriction(pushSEBRestrictionFunction(
|
||||
restriction,
|
||||
courseId));
|
||||
return Result.tryCatch(() -> {
|
||||
final LmsSetup lmsSetup = this.openEdxRestTemplateFactory.apiTemplateDataSupplier.getLmsSetup();
|
||||
final String url = lmsSetup.lmsApiUrl + getSEBRestrictionUrl(courseId);
|
||||
final HttpHeaders httpHeaders = new HttpHeaders();
|
||||
httpHeaders.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
|
||||
httpHeaders.add(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate");
|
||||
final OpenEdxSEBRestriction body = this
|
||||
.getRestTemplate()
|
||||
.getOrThrow()
|
||||
.exchange(
|
||||
url,
|
||||
HttpMethod.PUT,
|
||||
new HttpEntity<>(toJson(restriction), httpHeaders),
|
||||
OpenEdxSEBRestriction.class)
|
||||
.getBody();
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Successfully PUT SEB Client restriction on course: {} : {}", courseId, body);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
Result<Boolean> deleteSEBRestriction(final String courseId) {
|
||||
|
@ -157,74 +172,99 @@ public class OpenEdxCourseRestriction {
|
|||
log.debug("DELETE SEB Client restriction on course: {}", courseId);
|
||||
}
|
||||
|
||||
return handleSEBRestriction(deleteSEBRestrictionFunction(courseId));
|
||||
}
|
||||
|
||||
private BooleanSupplier pushSEBRestrictionFunction(
|
||||
final OpenEdxSEBRestriction restriction,
|
||||
final String courseId) {
|
||||
|
||||
final String url = this.lmsSetup.lmsApiUrl + getSEBRestrictionUrl(courseId);
|
||||
final HttpHeaders httpHeaders = new HttpHeaders();
|
||||
httpHeaders.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
|
||||
httpHeaders.add(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate");
|
||||
return () -> {
|
||||
final OpenEdxSEBRestriction body = this.restTemplate.exchange(
|
||||
url,
|
||||
HttpMethod.PUT,
|
||||
new HttpEntity<>(toJson(restriction), httpHeaders),
|
||||
OpenEdxSEBRestriction.class)
|
||||
.getBody();
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Successfully PUT SEB Client restriction on course: {} : {}", courseId, body);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
private BooleanSupplier deleteSEBRestrictionFunction(final String courseId) {
|
||||
|
||||
final String url = this.lmsSetup.lmsApiUrl + getSEBRestrictionUrl(courseId);
|
||||
return () -> {
|
||||
return Result.tryCatch(() -> {
|
||||
final LmsSetup lmsSetup = this.openEdxRestTemplateFactory.apiTemplateDataSupplier.getLmsSetup();
|
||||
final String url = lmsSetup.lmsApiUrl + getSEBRestrictionUrl(courseId);
|
||||
final HttpHeaders httpHeaders = new HttpHeaders();
|
||||
httpHeaders.add(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate");
|
||||
final ResponseEntity<Object> exchange = this.restTemplate.exchange(
|
||||
url,
|
||||
HttpMethod.DELETE,
|
||||
new HttpEntity<>(httpHeaders),
|
||||
Object.class);
|
||||
final ResponseEntity<Object> exchange = this
|
||||
.getRestTemplate()
|
||||
.getOrThrow()
|
||||
.exchange(
|
||||
url,
|
||||
HttpMethod.DELETE,
|
||||
new HttpEntity<>(httpHeaders),
|
||||
Object.class);
|
||||
|
||||
if (exchange.getStatusCode() == HttpStatus.NO_CONTENT) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Successfully PUT SEB Client restriction on course: {}", courseId);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
log.error("Unexpected response for deletion: {}", exchange);
|
||||
return false;
|
||||
throw new RuntimeException("Unexpected response for deletion: " + exchange);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
private Result<Boolean> handleSEBRestriction(final BooleanSupplier task) {
|
||||
return getRestTemplate()
|
||||
.map(restTemplate -> {
|
||||
try {
|
||||
return task.getAsBoolean();
|
||||
} catch (final HttpClientErrorException ce) {
|
||||
if (ce.getStatusCode() == HttpStatus.UNAUTHORIZED) {
|
||||
throw new APIMessageException(APIMessage.ErrorMessage.UNAUTHORIZED.of(ce.getMessage()
|
||||
+ " Unable to get access for API. Please check the corresponding LMS Setup "));
|
||||
}
|
||||
throw ce;
|
||||
} catch (final Exception e) {
|
||||
throw new RuntimeException("Unexpected: ", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
// private BooleanSupplier pushSEBRestrictionFunction(
|
||||
// final OpenEdxSEBRestriction restriction,
|
||||
// final String courseId) {
|
||||
//
|
||||
// final LmsSetup lmsSetup = this.openEdxRestTemplateFactory.apiTemplateDataSupplier.getLmsSetup();
|
||||
// final String url = lmsSetup.lmsApiUrl + getSEBRestrictionUrl(courseId);
|
||||
// final HttpHeaders httpHeaders = new HttpHeaders();
|
||||
// httpHeaders.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
|
||||
// httpHeaders.add(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate");
|
||||
// return () -> {
|
||||
// final OpenEdxSEBRestriction body = this.restTemplate.exchange(
|
||||
// url,
|
||||
// HttpMethod.PUT,
|
||||
// new HttpEntity<>(toJson(restriction), httpHeaders),
|
||||
// OpenEdxSEBRestriction.class)
|
||||
// .getBody();
|
||||
//
|
||||
// if (log.isDebugEnabled()) {
|
||||
// log.debug("Successfully PUT SEB Client restriction on course: {} : {}", courseId, body);
|
||||
// }
|
||||
//
|
||||
// return true;
|
||||
// };
|
||||
// }
|
||||
|
||||
// private BooleanSupplier deleteSEBRestrictionFunction(final String courseId) {
|
||||
//
|
||||
// final LmsSetup lmsSetup = this.openEdxRestTemplateFactory.apiTemplateDataSupplier.getLmsSetup();
|
||||
// final String url = lmsSetup.lmsApiUrl + getSEBRestrictionUrl(courseId);
|
||||
// return () -> {
|
||||
// final HttpHeaders httpHeaders = new HttpHeaders();
|
||||
// httpHeaders.add(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate");
|
||||
// final ResponseEntity<Object> exchange = this.restTemplate.exchange(
|
||||
// url,
|
||||
// HttpMethod.DELETE,
|
||||
// new HttpEntity<>(httpHeaders),
|
||||
// Object.class);
|
||||
//
|
||||
// if (exchange.getStatusCode() == HttpStatus.NO_CONTENT) {
|
||||
// if (log.isDebugEnabled()) {
|
||||
// log.debug("Successfully PUT SEB Client restriction on course: {}", courseId);
|
||||
// }
|
||||
// } else {
|
||||
// log.error("Unexpected response for deletion: {}", exchange);
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// return true;
|
||||
// };
|
||||
// }
|
||||
|
||||
// private Result<Boolean> handleSEBRestriction(final BooleanSupplier task) {
|
||||
// return getRestTemplate()
|
||||
// .map(restTemplate -> {
|
||||
// try {
|
||||
// return task.getAsBoolean();
|
||||
// } catch (final HttpClientErrorException ce) {
|
||||
// if (ce.getStatusCode() == HttpStatus.UNAUTHORIZED) {
|
||||
// throw new APIMessageException(APIMessage.ErrorMessage.UNAUTHORIZED.of(ce.getMessage()
|
||||
// + " Unable to get access for API. Please check the corresponding LMS Setup "));
|
||||
// }
|
||||
// throw ce;
|
||||
// } catch (final Exception e) {
|
||||
// throw new RuntimeException("Unexpected: ", e);
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
private String getSEBRestrictionUrl(final String courseId) {
|
||||
return String.format(OPEN_EDX_DEFAULT_COURSE_RESTRICTION_API_PATH, courseId);
|
||||
|
|
|
@ -24,6 +24,7 @@ import ch.ethz.seb.sebserver.gbl.model.exam.OpenEdxSEBRestriction;
|
|||
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.institution.LmsSetup;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.ExamineeAccountDetails;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
|
@ -39,23 +40,27 @@ final class OpenEdxLmsAPITemplate implements LmsAPITemplate {
|
|||
|
||||
private static final Logger log = LoggerFactory.getLogger(OpenEdxLmsAPITemplate.class);
|
||||
|
||||
private final LmsSetup lmsSetup;
|
||||
private final OpenEdxCourseAccess openEdxCourseAccess;
|
||||
private final OpenEdxCourseRestriction openEdxCourseRestriction;
|
||||
|
||||
OpenEdxLmsAPITemplate(
|
||||
final LmsSetup lmsSetup,
|
||||
final OpenEdxCourseAccess openEdxCourseAccess,
|
||||
final OpenEdxCourseRestriction openEdxCourseRestriction) {
|
||||
|
||||
this.lmsSetup = lmsSetup;
|
||||
this.openEdxCourseAccess = openEdxCourseAccess;
|
||||
this.openEdxCourseRestriction = openEdxCourseRestriction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LmsType getType() {
|
||||
return LmsType.OPEN_EDX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LmsSetup lmsSetup() {
|
||||
return this.lmsSetup;
|
||||
return this.openEdxCourseAccess
|
||||
.getApiTemplateDataSupplier()
|
||||
.getLmsSetup();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -150,7 +155,8 @@ final class OpenEdxLmsAPITemplate implements LmsAPITemplate {
|
|||
log.debug("Release SEB Client restriction for Exam: {}", exam);
|
||||
}
|
||||
|
||||
return this.openEdxCourseRestriction.deleteSEBRestriction(exam.externalId)
|
||||
return this.openEdxCourseRestriction
|
||||
.deleteSEBRestriction(exam.externalId)
|
||||
.map(result -> exam);
|
||||
}
|
||||
|
||||
|
|
|
@ -19,13 +19,11 @@ import ch.ethz.seb.sebserver.gbl.Constants;
|
|||
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
|
||||
import ch.ethz.seb.sebserver.gbl.async.AsyncService;
|
||||
import ch.ethz.seb.sebserver.gbl.client.ClientCredentialService;
|
||||
import ch.ethz.seb.sebserver.gbl.client.ClientCredentials;
|
||||
import ch.ethz.seb.sebserver.gbl.client.ProxyData;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.APITemplateDataSupplier;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplateFactory;
|
||||
|
||||
|
@ -71,37 +69,29 @@ public class OpenEdxLmsAPITemplateFactory implements LmsAPITemplateFactory {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Result<LmsAPITemplate> create(
|
||||
final LmsSetup lmsSetup,
|
||||
final ClientCredentials credentials,
|
||||
final ProxyData proxyData) {
|
||||
public Result<LmsAPITemplate> create(final APITemplateDataSupplier apiTemplateDataSupplier) {
|
||||
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
final OpenEdxRestTemplateFactory openEdxRestTemplateFactory = new OpenEdxRestTemplateFactory(
|
||||
lmsSetup,
|
||||
credentials,
|
||||
proxyData,
|
||||
apiTemplateDataSupplier,
|
||||
this.clientCredentialService,
|
||||
this.clientHttpRequestFactoryService,
|
||||
this.alternativeTokenRequestPaths);
|
||||
|
||||
final OpenEdxCourseAccess openEdxCourseAccess = new OpenEdxCourseAccess(
|
||||
this.jsonMapper,
|
||||
lmsSetup,
|
||||
openEdxRestTemplateFactory,
|
||||
this.webserviceInfo,
|
||||
this.asyncService,
|
||||
this.environment);
|
||||
|
||||
final OpenEdxCourseRestriction openEdxCourseRestriction = new OpenEdxCourseRestriction(
|
||||
lmsSetup,
|
||||
this.jsonMapper,
|
||||
openEdxRestTemplateFactory,
|
||||
this.restrictionAPIPushCount);
|
||||
|
||||
return new OpenEdxLmsAPITemplate(
|
||||
lmsSetup,
|
||||
openEdxCourseAccess,
|
||||
openEdxCourseRestriction);
|
||||
});
|
||||
|
|
|
@ -43,30 +43,25 @@ 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;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.APITemplateDataSupplier;
|
||||
|
||||
final class OpenEdxRestTemplateFactory {
|
||||
|
||||
private static final String OPEN_EDX_DEFAULT_TOKEN_REQUEST_PATH = "/oauth2/access_token";
|
||||
|
||||
final LmsSetup lmsSetup;
|
||||
final ClientCredentials credentials;
|
||||
final ProxyData proxyData;
|
||||
final APITemplateDataSupplier apiTemplateDataSupplier;
|
||||
final ClientHttpRequestFactoryService clientHttpRequestFactoryService;
|
||||
final ClientCredentialService clientCredentialService;
|
||||
final Set<String> knownTokenAccessPaths;
|
||||
|
||||
OpenEdxRestTemplateFactory(
|
||||
final LmsSetup lmsSetup,
|
||||
final ClientCredentials credentials,
|
||||
final ProxyData proxyData,
|
||||
final APITemplateDataSupplier apiTemplateDataSupplier,
|
||||
final ClientCredentialService clientCredentialService,
|
||||
final ClientHttpRequestFactoryService clientHttpRequestFactoryService,
|
||||
final String[] alternativeTokenRequestPaths) {
|
||||
|
||||
this.lmsSetup = lmsSetup;
|
||||
this.apiTemplateDataSupplier = apiTemplateDataSupplier;
|
||||
this.clientCredentialService = clientCredentialService;
|
||||
this.credentials = credentials;
|
||||
this.proxyData = proxyData;
|
||||
this.clientHttpRequestFactoryService = clientHttpRequestFactoryService;
|
||||
|
||||
this.knownTokenAccessPaths = new HashSet<>();
|
||||
|
@ -76,26 +71,34 @@ final class OpenEdxRestTemplateFactory {
|
|||
}
|
||||
}
|
||||
|
||||
APITemplateDataSupplier getApiTemplateDataSupplier() {
|
||||
return this.apiTemplateDataSupplier;
|
||||
}
|
||||
|
||||
public LmsSetupTestResult test() {
|
||||
|
||||
final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup();
|
||||
final ClientCredentials lmsClientCredentials = this.apiTemplateDataSupplier.getLmsClientCredentials();
|
||||
|
||||
final List<APIMessage> missingAttrs = new ArrayList<>();
|
||||
if (StringUtils.isBlank(this.lmsSetup.lmsApiUrl)) {
|
||||
if (StringUtils.isBlank(lmsSetup.lmsApiUrl)) {
|
||||
missingAttrs.add(APIMessage.fieldValidationError(
|
||||
LMS_SETUP.ATTR_LMS_URL,
|
||||
"lmsSetup:lmsUrl:notNull"));
|
||||
} else {
|
||||
// try to connect to the url
|
||||
if (!Utils.pingHost(this.lmsSetup.lmsApiUrl)) {
|
||||
if (!Utils.pingHost(lmsSetup.lmsApiUrl)) {
|
||||
missingAttrs.add(APIMessage.fieldValidationError(
|
||||
LMS_SETUP.ATTR_LMS_URL,
|
||||
"lmsSetup:lmsUrl:url.invalid"));
|
||||
}
|
||||
}
|
||||
if (!this.credentials.hasClientId()) {
|
||||
if (!lmsClientCredentials.hasClientId()) {
|
||||
missingAttrs.add(APIMessage.fieldValidationError(
|
||||
LMS_SETUP.ATTR_LMS_CLIENTNAME,
|
||||
"lmsSetup:lmsClientname:notNull"));
|
||||
}
|
||||
if (!this.credentials.hasSecret()) {
|
||||
if (!lmsClientCredentials.hasSecret()) {
|
||||
missingAttrs.add(APIMessage.fieldValidationError(
|
||||
LMS_SETUP.ATTR_LMS_CLIENTSECRET,
|
||||
"lmsSetup:lmsClientsecret:notNull"));
|
||||
|
@ -120,10 +123,7 @@ final class OpenEdxRestTemplateFactory {
|
|||
|
||||
Result<OAuth2RestTemplate> createOAuthRestTemplate(final String accessTokenPath) {
|
||||
return Result.tryCatch(() -> {
|
||||
final OAuth2RestTemplate template = createRestTemplate(
|
||||
this.lmsSetup,
|
||||
this.credentials,
|
||||
accessTokenPath);
|
||||
final OAuth2RestTemplate template = createRestTemplate(accessTokenPath);
|
||||
|
||||
final OAuth2AccessToken accessToken = template.getAccessToken();
|
||||
if (accessToken == null) {
|
||||
|
@ -134,10 +134,11 @@ final class OpenEdxRestTemplateFactory {
|
|||
});
|
||||
}
|
||||
|
||||
private OAuth2RestTemplate createRestTemplate(
|
||||
final LmsSetup lmsSetup,
|
||||
final ClientCredentials credentials,
|
||||
final String accessTokenRequestPath) throws URISyntaxException {
|
||||
private OAuth2RestTemplate createRestTemplate(final String accessTokenRequestPath) throws URISyntaxException {
|
||||
|
||||
final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup();
|
||||
final ClientCredentials credentials = this.apiTemplateDataSupplier.getLmsClientCredentials();
|
||||
final ProxyData proxyData = this.apiTemplateDataSupplier.getProxyData();
|
||||
|
||||
final CharSequence plainClientId = credentials.clientId;
|
||||
final CharSequence plainClientSecret = this.clientCredentialService
|
||||
|
@ -150,7 +151,7 @@ final class OpenEdxRestTemplateFactory {
|
|||
details.setClientSecret(plainClientSecret.toString());
|
||||
|
||||
final ClientHttpRequestFactory clientHttpRequestFactory = this.clientHttpRequestFactoryService
|
||||
.getClientHttpRequestFactory(this.proxyData)
|
||||
.getClientHttpRequestFactory(proxyData)
|
||||
.getOrThrow();
|
||||
|
||||
final OAuth2RestTemplate template = new OAuth2RestTemplate(details);
|
||||
|
|
|
@ -6,18 +6,16 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl;
|
||||
package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.mockup;
|
||||
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.client.ClientCredentials;
|
||||
import ch.ethz.seb.sebserver.gbl.client.ProxyData;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.APITemplateDataSupplier;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplateFactory;
|
||||
|
||||
|
@ -38,14 +36,9 @@ public class MockLmsAPITemplateFactory implements LmsAPITemplateFactory {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Result<LmsAPITemplate> create(
|
||||
final LmsSetup lmsSetup,
|
||||
final ClientCredentials credentials,
|
||||
final ProxyData proxyData) {
|
||||
|
||||
public Result<LmsAPITemplate> create(final APITemplateDataSupplier apiTemplateDataSupplier) {
|
||||
return Result.tryCatch(() -> new MockupLmsAPITemplate(
|
||||
lmsSetup,
|
||||
credentials,
|
||||
apiTemplateDataSupplier,
|
||||
this.webserviceInfo));
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl;
|
||||
package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.mockup;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
@ -35,31 +35,32 @@ import ch.ethz.seb.sebserver.gbl.model.user.ExamineeAccountDetails;
|
|||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.APITemplateDataSupplier;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.NoSEBRestrictionException;
|
||||
|
||||
final class MockupLmsAPITemplate implements LmsAPITemplate {
|
||||
public class MockupLmsAPITemplate implements LmsAPITemplate {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(MockupLmsAPITemplate.class);
|
||||
|
||||
private final LmsSetup lmsSetup;
|
||||
private final ClientCredentials credentials;
|
||||
private final Collection<QuizData> mockups;
|
||||
private final WebserviceInfo webserviceInfo;
|
||||
private final APITemplateDataSupplier apiTemplateDataSupplier;
|
||||
|
||||
MockupLmsAPITemplate(
|
||||
final LmsSetup lmsSetup,
|
||||
final ClientCredentials credentials,
|
||||
final APITemplateDataSupplier apiTemplateDataSupplier,
|
||||
final WebserviceInfo webserviceInfo) {
|
||||
|
||||
this.lmsSetup = lmsSetup;
|
||||
this.credentials = credentials;
|
||||
this.apiTemplateDataSupplier = apiTemplateDataSupplier;
|
||||
this.webserviceInfo = webserviceInfo;
|
||||
this.mockups = new ArrayList<>();
|
||||
|
||||
final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup();
|
||||
final Long lmsSetupId = lmsSetup.id;
|
||||
final Long institutionId = lmsSetup.getInstitutionId();
|
||||
final LmsType lmsType = lmsSetup.getLmsType();
|
||||
this.mockups = new ArrayList<>();
|
||||
|
||||
this.mockups.add(new QuizData(
|
||||
"quiz1", institutionId, lmsSetupId, lmsType, "Demo Quiz 1 (MOCKUP)", "<p>Demo Quiz Mockup</p>",
|
||||
"2020-01-01T09:00:00Z", null, "http://lms.mockup.com/api/"));
|
||||
|
@ -92,24 +93,31 @@ final class MockupLmsAPITemplate implements LmsAPITemplate {
|
|||
"http://lms.mockup.com/api/"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public LmsType getType() {
|
||||
return LmsType.MOCKUP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LmsSetup lmsSetup() {
|
||||
return this.lmsSetup;
|
||||
return this.apiTemplateDataSupplier.getLmsSetup();
|
||||
}
|
||||
|
||||
private List<APIMessage> checkAttributes() {
|
||||
final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup();
|
||||
final ClientCredentials lmsClientCredentials = this.apiTemplateDataSupplier.getLmsClientCredentials();
|
||||
final List<APIMessage> missingAttrs = new ArrayList<>();
|
||||
if (StringUtils.isBlank(this.lmsSetup.lmsApiUrl)) {
|
||||
if (StringUtils.isBlank(lmsSetup.lmsApiUrl)) {
|
||||
missingAttrs.add(APIMessage.fieldValidationError(
|
||||
LMS_SETUP.ATTR_LMS_URL,
|
||||
"lmsSetup:lmsUrl:notNull"));
|
||||
}
|
||||
if (!this.credentials.hasClientId()) {
|
||||
if (!lmsClientCredentials.hasClientId()) {
|
||||
missingAttrs.add(APIMessage.fieldValidationError(
|
||||
LMS_SETUP.ATTR_LMS_CLIENTNAME,
|
||||
"lmsSetup:lmsClientname:notNull"));
|
||||
}
|
||||
if (!this.credentials.hasSecret()) {
|
||||
if (!lmsClientCredentials.hasSecret()) {
|
||||
missingAttrs.add(APIMessage.fieldValidationError(
|
||||
LMS_SETUP.ATTR_LMS_CLIENTSECRET,
|
||||
"lmsSetup:lmsClientsecret:notNull"));
|
||||
|
@ -119,7 +127,7 @@ final class MockupLmsAPITemplate implements LmsAPITemplate {
|
|||
|
||||
@Override
|
||||
public LmsSetupTestResult testCourseAccessAPI() {
|
||||
log.info("Test Lms Binding for Mockup and LmsSetup: {}", this.lmsSetup);
|
||||
log.info("Test Lms Binding for Mockup and LmsSetup: {}", this.apiTemplateDataSupplier.getLmsSetup());
|
||||
|
||||
final List<APIMessage> missingAttrs = checkAttributes();
|
||||
|
||||
|
@ -136,7 +144,6 @@ final class MockupLmsAPITemplate implements LmsAPITemplate {
|
|||
|
||||
@Override
|
||||
public LmsSetupTestResult testCourseRestrictionAPI() {
|
||||
// TODO Auto-generated method stub
|
||||
return LmsSetupTestResult.ofQuizRestrictionAPIError("unsupported");
|
||||
}
|
||||
|
||||
|
@ -239,7 +246,7 @@ final class MockupLmsAPITemplate implements LmsAPITemplate {
|
|||
private boolean authenticate() {
|
||||
try {
|
||||
|
||||
final CharSequence plainClientId = this.credentials.clientId;
|
||||
final CharSequence plainClientId = this.apiTemplateDataSupplier.getLmsClientCredentials().clientId;
|
||||
if (plainClientId == null || plainClientId.length() <= 0) {
|
||||
throw new IllegalAccessException("Wrong client credential");
|
||||
}
|
|
@ -45,7 +45,8 @@ import ch.ethz.seb.sebserver.gbl.model.user.ExamineeAccountDetails;
|
|||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.CourseAccess;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.APITemplateDataSupplier;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.AbstractCourseAccess;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleCourseDataAsyncLoader.CourseDataShort;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestTemplateFactory.MoodleAPIRestTemplate;
|
||||
|
||||
|
@ -63,7 +64,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestT
|
|||
* background task if needed and return immediately to do not block the request.
|
||||
* The planed Moodle integration on moodle side also defines an improved course access API. This will
|
||||
* possibly make this synchronous fetch strategy obsolete in the future. */
|
||||
public class MoodleCourseAccess extends CourseAccess {
|
||||
public class MoodleCourseAccess extends AbstractCourseAccess {
|
||||
|
||||
private static final long INITIAL_WAIT_TIME = 3 * Constants.SECOND_IN_MILLIS;
|
||||
|
||||
|
@ -86,7 +87,6 @@ public class MoodleCourseAccess extends CourseAccess {
|
|||
static final String MOODLE_COURSE_API_SEARCH_PAGE_SIZE = "perpage";
|
||||
|
||||
private final JSONMapper jsonMapper;
|
||||
private final LmsSetup lmsSetup;
|
||||
private final MoodleRestTemplateFactory moodleRestTemplateFactory;
|
||||
private final MoodleCourseDataAsyncLoader moodleCourseDataAsyncLoader;
|
||||
private final CircuitBreaker<List<QuizData>> allQuizzesRequest;
|
||||
|
@ -96,7 +96,6 @@ public class MoodleCourseAccess extends CourseAccess {
|
|||
|
||||
protected MoodleCourseAccess(
|
||||
final JSONMapper jsonMapper,
|
||||
final LmsSetup lmsSetup,
|
||||
final MoodleRestTemplateFactory moodleRestTemplateFactory,
|
||||
final MoodleCourseDataAsyncLoader moodleCourseDataAsyncLoader,
|
||||
final AsyncService asyncService,
|
||||
|
@ -104,7 +103,6 @@ public class MoodleCourseAccess extends CourseAccess {
|
|||
|
||||
super(asyncService, environment);
|
||||
this.jsonMapper = jsonMapper;
|
||||
this.lmsSetup = lmsSetup;
|
||||
this.moodleCourseDataAsyncLoader = moodleCourseDataAsyncLoader;
|
||||
this.moodleRestTemplateFactory = moodleRestTemplateFactory;
|
||||
|
||||
|
@ -136,6 +134,10 @@ public class MoodleCourseAccess extends CourseAccess {
|
|||
};
|
||||
}
|
||||
|
||||
APITemplateDataSupplier getApiTemplateDataSupplier() {
|
||||
return this.moodleRestTemplateFactory.apiTemplateDataSupplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<ExamineeAccountDetails> getExamineeAccountDetails(final String examineeSessionId) {
|
||||
return Result.tryCatch(() -> {
|
||||
|
@ -151,8 +153,9 @@ public class MoodleCourseAccess extends CourseAccess {
|
|||
queryAttributes);
|
||||
|
||||
if (checkAccessDeniedError(userDetailsJSON)) {
|
||||
final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup();
|
||||
log.error("Get access denied error from Moodle: {} for API call: {}, response: {}",
|
||||
this.lmsSetup,
|
||||
lmsSetup,
|
||||
MOODLE_USER_PROFILE_API_FUNCTION_NAME,
|
||||
Utils.truncateText(userDetailsJSON, 2000));
|
||||
throw new RuntimeException("No user details on Moodle API request (access-denied)");
|
||||
|
@ -257,9 +260,11 @@ public class MoodleCourseAccess extends CourseAccess {
|
|||
final MoodleAPIRestTemplate restTemplate,
|
||||
final FilterMap filterMap) {
|
||||
|
||||
final String urlPrefix = (this.lmsSetup.lmsApiUrl.endsWith(Constants.URL_PATH_SEPARATOR))
|
||||
? this.lmsSetup.lmsApiUrl + MOODLE_QUIZ_START_URL_PATH
|
||||
: this.lmsSetup.lmsApiUrl + Constants.URL_PATH_SEPARATOR + MOODLE_QUIZ_START_URL_PATH;
|
||||
final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup();
|
||||
|
||||
final String urlPrefix = (lmsSetup.lmsApiUrl.endsWith(Constants.URL_PATH_SEPARATOR))
|
||||
? lmsSetup.lmsApiUrl + MOODLE_QUIZ_START_URL_PATH
|
||||
: lmsSetup.lmsApiUrl + Constants.URL_PATH_SEPARATOR + MOODLE_QUIZ_START_URL_PATH;
|
||||
|
||||
final DateTime quizFromTime = (filterMap != null) ? filterMap.getQuizFromTime() : null;
|
||||
final long fromCutTime = (quizFromTime != null) ? Utils.toUnixTimeInSeconds(quizFromTime) : -1;
|
||||
|
@ -313,10 +318,10 @@ public class MoodleCourseAccess extends CourseAccess {
|
|||
|
||||
private List<QuizData> getCached() {
|
||||
final Collection<CourseDataShort> courseQuizData = this.moodleCourseDataAsyncLoader.getCachedCourseData();
|
||||
|
||||
final String urlPrefix = (this.lmsSetup.lmsApiUrl.endsWith(Constants.URL_PATH_SEPARATOR))
|
||||
? this.lmsSetup.lmsApiUrl + MOODLE_QUIZ_START_URL_PATH
|
||||
: this.lmsSetup.lmsApiUrl + Constants.URL_PATH_SEPARATOR + MOODLE_QUIZ_START_URL_PATH;
|
||||
final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup();
|
||||
final String urlPrefix = (lmsSetup.lmsApiUrl.endsWith(Constants.URL_PATH_SEPARATOR))
|
||||
? lmsSetup.lmsApiUrl + MOODLE_QUIZ_START_URL_PATH
|
||||
: lmsSetup.lmsApiUrl + Constants.URL_PATH_SEPARATOR + MOODLE_QUIZ_START_URL_PATH;
|
||||
|
||||
return reduceCoursesToQuizzes(urlPrefix, courseQuizData);
|
||||
}
|
||||
|
@ -325,13 +330,14 @@ public class MoodleCourseAccess extends CourseAccess {
|
|||
final String urlPrefix,
|
||||
final Collection<CourseDataShort> courseQuizData) {
|
||||
|
||||
final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup();
|
||||
return courseQuizData
|
||||
.stream()
|
||||
.reduce(
|
||||
new ArrayList<>(),
|
||||
(list, courseData) -> {
|
||||
list.addAll(quizDataOf(
|
||||
this.lmsSetup,
|
||||
lmsSetup,
|
||||
courseData,
|
||||
urlPrefix));
|
||||
return list;
|
||||
|
@ -380,16 +386,17 @@ public class MoodleCourseAccess extends CourseAccess {
|
|||
final CourseQuizData courseQuizData = this.jsonMapper.readValue(
|
||||
quizzesJSON,
|
||||
CourseQuizData.class);
|
||||
final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup();
|
||||
|
||||
if (courseQuizData == null) {
|
||||
log.error("No quizzes found for ids: {} on LMS; {}", quizIds, this.lmsSetup.name);
|
||||
log.error("No quizzes found for ids: {} on LMS; {}", quizIds, lmsSetup.name);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
logMoodleWarnings(courseQuizData.warnings);
|
||||
|
||||
if (courseQuizData.quizzes == null || courseQuizData.quizzes.isEmpty()) {
|
||||
log.error("No quizzes found for ids: {} on LMS; {}", quizIds, this.lmsSetup.name);
|
||||
log.error("No quizzes found for ids: {} on LMS; {}", quizIds, lmsSetup.name);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
|
@ -402,9 +409,9 @@ public class MoodleCourseAccess extends CourseAccess {
|
|||
}
|
||||
});
|
||||
|
||||
final String urlPrefix = (this.lmsSetup.lmsApiUrl.endsWith(Constants.URL_PATH_SEPARATOR))
|
||||
? this.lmsSetup.lmsApiUrl + MOODLE_QUIZ_START_URL_PATH
|
||||
: this.lmsSetup.lmsApiUrl + Constants.URL_PATH_SEPARATOR + MOODLE_QUIZ_START_URL_PATH;
|
||||
final String urlPrefix = (lmsSetup.lmsApiUrl.endsWith(Constants.URL_PATH_SEPARATOR))
|
||||
? lmsSetup.lmsApiUrl + MOODLE_QUIZ_START_URL_PATH
|
||||
: lmsSetup.lmsApiUrl + Constants.URL_PATH_SEPARATOR + MOODLE_QUIZ_START_URL_PATH;
|
||||
|
||||
return courseData.values()
|
||||
.stream()
|
||||
|
@ -413,7 +420,7 @@ public class MoodleCourseAccess extends CourseAccess {
|
|||
new ArrayList<>(),
|
||||
(list, cd) -> {
|
||||
list.addAll(quizDataOf(
|
||||
this.lmsSetup,
|
||||
lmsSetup,
|
||||
cd,
|
||||
urlPrefix));
|
||||
return list;
|
||||
|
@ -451,16 +458,17 @@ public class MoodleCourseAccess extends CourseAccess {
|
|||
final Courses courses = this.jsonMapper.readValue(
|
||||
coursePageJSON,
|
||||
Courses.class);
|
||||
final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup();
|
||||
|
||||
if (courses == null) {
|
||||
log.error("No courses found for ids: {} on LMS: {}", ids, this.lmsSetup.name);
|
||||
log.error("No courses found for ids: {} on LMS: {}", ids, lmsSetup.name);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
logMoodleWarnings(courses.warnings);
|
||||
|
||||
if (courses.courses == null || courses.courses.isEmpty()) {
|
||||
log.error("No courses found for ids: {} on LMS: {}", ids, this.lmsSetup.name);
|
||||
log.error("No courses found for ids: {} on LMS: {}", ids, lmsSetup.name);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
|
@ -630,9 +638,10 @@ public class MoodleCourseAccess extends CourseAccess {
|
|||
private void logMoodleWarnings(final Collection<Warning> warnings) {
|
||||
if (warnings != null && !warnings.isEmpty()) {
|
||||
if (log.isDebugEnabled()) {
|
||||
final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup();
|
||||
log.debug(
|
||||
"There are warnings from Moodle response: Moodle: {} request: {} warnings: {} warning sample: {}",
|
||||
this.lmsSetup,
|
||||
lmsSetup,
|
||||
MoodleCourseAccess.MOODLE_QUIZ_API_FUNCTION_NAME,
|
||||
warnings.size(),
|
||||
warnings.iterator().next().toString());
|
||||
|
|
|
@ -24,6 +24,7 @@ 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.SEBRestriction;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.ExamineeAccountDetails;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
|
@ -51,23 +52,27 @@ public class MoodleLmsAPITemplate implements LmsAPITemplate {
|
|||
|
||||
private static final Logger log = LoggerFactory.getLogger(MoodleLmsAPITemplate.class);
|
||||
|
||||
private final LmsSetup lmsSetup;
|
||||
private final MoodleCourseAccess moodleCourseAccess;
|
||||
private final MoodleCourseRestriction moodleCourseRestriction;
|
||||
|
||||
protected MoodleLmsAPITemplate(
|
||||
final LmsSetup lmsSetup,
|
||||
final MoodleCourseAccess moodleCourseAccess,
|
||||
final MoodleCourseRestriction moodleCourseRestriction) {
|
||||
|
||||
this.lmsSetup = lmsSetup;
|
||||
this.moodleCourseAccess = moodleCourseAccess;
|
||||
this.moodleCourseRestriction = moodleCourseRestriction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LmsType getType() {
|
||||
return LmsType.MOODLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LmsSetup lmsSetup() {
|
||||
return this.lmsSetup;
|
||||
return this.moodleCourseAccess
|
||||
.getApiTemplateDataSupplier()
|
||||
.getLmsSetup();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -20,12 +20,11 @@ import ch.ethz.seb.sebserver.gbl.Constants;
|
|||
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
|
||||
import ch.ethz.seb.sebserver.gbl.async.AsyncService;
|
||||
import ch.ethz.seb.sebserver.gbl.client.ClientCredentialService;
|
||||
import ch.ethz.seb.sebserver.gbl.client.ClientCredentials;
|
||||
import ch.ethz.seb.sebserver.gbl.client.ProxyData;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.APITemplateDataSupplier;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplateFactory;
|
||||
|
||||
|
@ -68,29 +67,25 @@ public class MoodleLmsAPITemplateFactory implements LmsAPITemplateFactory {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Result<LmsAPITemplate> create(
|
||||
final LmsSetup lmsSetup,
|
||||
final ClientCredentials credentials,
|
||||
final ProxyData proxyData) {
|
||||
public Result<LmsAPITemplate> create(final APITemplateDataSupplier apiTemplateDataSupplier) {
|
||||
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
final MoodleCourseDataAsyncLoader asyncLoaderPrototype =
|
||||
this.applicationContext.getBean(MoodleCourseDataAsyncLoader.class);
|
||||
asyncLoaderPrototype.init(lmsSetup.name);
|
||||
final LmsSetup lmsSetup = apiTemplateDataSupplier.getLmsSetup();
|
||||
|
||||
final MoodleCourseDataAsyncLoader asyncLoaderPrototype = this.applicationContext
|
||||
.getBean(MoodleCourseDataAsyncLoader.class);
|
||||
asyncLoaderPrototype.init(lmsSetup.getModelId());
|
||||
|
||||
final MoodleRestTemplateFactory moodleRestTemplateFactory = new MoodleRestTemplateFactory(
|
||||
this.jsonMapper,
|
||||
lmsSetup,
|
||||
credentials,
|
||||
proxyData,
|
||||
apiTemplateDataSupplier,
|
||||
this.clientCredentialService,
|
||||
this.clientHttpRequestFactoryService,
|
||||
this.alternativeTokenRequestPaths);
|
||||
|
||||
final MoodleCourseAccess moodleCourseAccess = new MoodleCourseAccess(
|
||||
this.jsonMapper,
|
||||
lmsSetup,
|
||||
moodleRestTemplateFactory,
|
||||
asyncLoaderPrototype,
|
||||
this.asyncService,
|
||||
|
@ -101,7 +96,6 @@ public class MoodleLmsAPITemplateFactory implements LmsAPITemplateFactory {
|
|||
moodleRestTemplateFactory);
|
||||
|
||||
return new MoodleLmsAPITemplate(
|
||||
lmsSetup,
|
||||
moodleCourseAccess,
|
||||
moodleCourseRestriction);
|
||||
});
|
||||
|
|
|
@ -50,33 +50,28 @@ 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;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.APITemplateDataSupplier;
|
||||
|
||||
class MoodleRestTemplateFactory {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(MoodleRestTemplateFactory.class);
|
||||
|
||||
final JSONMapper jsonMapper;
|
||||
final LmsSetup lmsSetup;
|
||||
final ClientCredentials credentials;
|
||||
final ProxyData proxyData;
|
||||
final APITemplateDataSupplier apiTemplateDataSupplier;
|
||||
final ClientHttpRequestFactoryService clientHttpRequestFactoryService;
|
||||
final ClientCredentialService clientCredentialService;
|
||||
final Set<String> knownTokenAccessPaths;
|
||||
|
||||
public MoodleRestTemplateFactory(
|
||||
final JSONMapper jsonMapper,
|
||||
final LmsSetup lmsSetup,
|
||||
final ClientCredentials credentials,
|
||||
final ProxyData proxyData,
|
||||
final APITemplateDataSupplier apiTemplateDataSupplier,
|
||||
final ClientCredentialService clientCredentialService,
|
||||
final ClientHttpRequestFactoryService clientHttpRequestFactoryService,
|
||||
final String[] alternativeTokenRequestPaths) {
|
||||
|
||||
this.jsonMapper = jsonMapper;
|
||||
this.lmsSetup = lmsSetup;
|
||||
this.apiTemplateDataSupplier = apiTemplateDataSupplier;
|
||||
this.clientCredentialService = clientCredentialService;
|
||||
this.credentials = credentials;
|
||||
this.proxyData = proxyData;
|
||||
this.clientHttpRequestFactoryService = clientHttpRequestFactoryService;
|
||||
|
||||
this.knownTokenAccessPaths = new HashSet<>();
|
||||
|
@ -86,28 +81,36 @@ class MoodleRestTemplateFactory {
|
|||
}
|
||||
}
|
||||
|
||||
APITemplateDataSupplier getApiTemplateDataSupplier() {
|
||||
return this.apiTemplateDataSupplier;
|
||||
}
|
||||
|
||||
public LmsSetupTestResult test() {
|
||||
|
||||
final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup();
|
||||
final ClientCredentials credentials = this.apiTemplateDataSupplier.getLmsClientCredentials();
|
||||
|
||||
final List<APIMessage> missingAttrs = new ArrayList<>();
|
||||
if (StringUtils.isBlank(this.lmsSetup.lmsApiUrl)) {
|
||||
if (StringUtils.isBlank(lmsSetup.lmsApiUrl)) {
|
||||
missingAttrs.add(APIMessage.fieldValidationError(
|
||||
LMS_SETUP.ATTR_LMS_URL,
|
||||
"lmsSetup:lmsUrl:notNull"));
|
||||
} else {
|
||||
// try to connect to the url
|
||||
if (!Utils.pingHost(this.lmsSetup.lmsApiUrl)) {
|
||||
if (!Utils.pingHost(lmsSetup.lmsApiUrl)) {
|
||||
missingAttrs.add(APIMessage.fieldValidationError(
|
||||
LMS_SETUP.ATTR_LMS_URL,
|
||||
"lmsSetup:lmsUrl:url.invalid"));
|
||||
}
|
||||
}
|
||||
|
||||
if (StringUtils.isBlank(this.lmsSetup.lmsRestApiToken)) {
|
||||
if (!this.credentials.hasClientId()) {
|
||||
if (StringUtils.isBlank(lmsSetup.lmsRestApiToken)) {
|
||||
if (!credentials.hasClientId()) {
|
||||
missingAttrs.add(APIMessage.fieldValidationError(
|
||||
LMS_SETUP.ATTR_LMS_CLIENTNAME,
|
||||
"lmsSetup:lmsClientname:notNull"));
|
||||
}
|
||||
if (!this.credentials.hasSecret()) {
|
||||
if (!credentials.hasSecret()) {
|
||||
missingAttrs.add(APIMessage.fieldValidationError(
|
||||
LMS_SETUP.ATTR_LMS_CLIENTSECRET,
|
||||
"lmsSetup:lmsClientsecret:notNull"));
|
||||
|
@ -122,14 +125,17 @@ class MoodleRestTemplateFactory {
|
|||
}
|
||||
|
||||
Result<MoodleAPIRestTemplate> createRestTemplate() {
|
||||
|
||||
final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup();
|
||||
|
||||
return this.knownTokenAccessPaths
|
||||
.stream()
|
||||
.map(this::createRestTemplate)
|
||||
.map(result -> {
|
||||
if (result.hasError()) {
|
||||
log.warn("Failed to get access token for LMS: {}({})",
|
||||
this.lmsSetup.name,
|
||||
this.lmsSetup.id,
|
||||
lmsSetup.name,
|
||||
lmsSetup.id,
|
||||
result.getError());
|
||||
}
|
||||
return result;
|
||||
|
@ -138,53 +144,48 @@ class MoodleRestTemplateFactory {
|
|||
.findFirst()
|
||||
.orElse(Result.ofRuntimeError(
|
||||
"Failed to gain any access for LMS " +
|
||||
this.lmsSetup.name + "(" + this.lmsSetup.id +
|
||||
lmsSetup.name + "(" + lmsSetup.id +
|
||||
") on paths: " + this.knownTokenAccessPaths));
|
||||
}
|
||||
|
||||
Result<MoodleAPIRestTemplate> createRestTemplate(final String accessTokenPath) {
|
||||
return Result.tryCatch(() -> {
|
||||
final MoodleAPIRestTemplate template = createRestTemplate(
|
||||
this.credentials,
|
||||
accessTokenPath);
|
||||
|
||||
final CharSequence accessToken = template.getAccessToken();
|
||||
final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup();
|
||||
|
||||
return Result.tryCatch(() -> {
|
||||
final ClientCredentials credentials = this.apiTemplateDataSupplier.getLmsClientCredentials();
|
||||
final ProxyData proxyData = this.apiTemplateDataSupplier.getProxyData();
|
||||
|
||||
final CharSequence plainClientId = credentials.clientId;
|
||||
final CharSequence plainClientSecret = this.clientCredentialService
|
||||
.getPlainClientSecret(credentials)
|
||||
.getOrThrow();
|
||||
|
||||
final MoodleAPIRestTemplate restTemplate = new MoodleAPIRestTemplate(
|
||||
this.jsonMapper,
|
||||
lmsSetup.lmsApiUrl,
|
||||
accessTokenPath,
|
||||
lmsSetup.lmsRestApiToken,
|
||||
plainClientId,
|
||||
plainClientSecret);
|
||||
|
||||
final ClientHttpRequestFactory clientHttpRequestFactory = this.clientHttpRequestFactoryService
|
||||
.getClientHttpRequestFactory(proxyData)
|
||||
.getOrThrow();
|
||||
|
||||
restTemplate.setRequestFactory(clientHttpRequestFactory);
|
||||
final CharSequence accessToken = restTemplate.getAccessToken();
|
||||
|
||||
if (accessToken == null) {
|
||||
throw new RuntimeException("Failed to get access token for LMS " +
|
||||
this.lmsSetup.name + "(" + this.lmsSetup.id +
|
||||
lmsSetup.name + "(" + lmsSetup.id +
|
||||
") on path: " + accessTokenPath);
|
||||
}
|
||||
|
||||
return template;
|
||||
return restTemplate;
|
||||
});
|
||||
}
|
||||
|
||||
protected MoodleAPIRestTemplate createRestTemplate(
|
||||
final ClientCredentials credentials,
|
||||
final String accessTokenRequestPath) {
|
||||
|
||||
final CharSequence plainClientId = credentials.clientId;
|
||||
final CharSequence plainClientSecret = this.clientCredentialService
|
||||
.getPlainClientSecret(credentials)
|
||||
.getOrThrow();
|
||||
|
||||
final MoodleAPIRestTemplate restTemplate = new MoodleAPIRestTemplate(
|
||||
this.jsonMapper,
|
||||
this.lmsSetup.lmsApiUrl,
|
||||
accessTokenRequestPath,
|
||||
this.lmsSetup.lmsRestApiToken,
|
||||
plainClientId,
|
||||
plainClientSecret);
|
||||
|
||||
final ClientHttpRequestFactory clientHttpRequestFactory = this.clientHttpRequestFactoryService
|
||||
.getClientHttpRequestFactory(this.proxyData)
|
||||
.getOrThrow();
|
||||
|
||||
restTemplate.setRequestFactory(clientHttpRequestFactory);
|
||||
|
||||
return restTemplate;
|
||||
}
|
||||
|
||||
public class MoodleAPIRestTemplate extends RestTemplate {
|
||||
|
||||
public static final String URI_VAR_USER_NAME = "username";
|
||||
|
@ -210,7 +211,6 @@ class MoodleRestTemplateFactory {
|
|||
private final HttpEntity<?> tokenReqEntity = new HttpEntity<>(new LinkedMultiValueMap<>());
|
||||
|
||||
protected MoodleAPIRestTemplate(
|
||||
|
||||
final JSONMapper jsonMapper,
|
||||
final String serverURL,
|
||||
final String tokenPath,
|
||||
|
@ -318,10 +318,13 @@ class MoodleRestTemplateFactory {
|
|||
functionReqEntity,
|
||||
String.class);
|
||||
|
||||
final LmsSetup lmsSetup = MoodleRestTemplateFactory.this.apiTemplateDataSupplier
|
||||
.getLmsSetup();
|
||||
|
||||
if (response.getStatusCode() != HttpStatus.OK) {
|
||||
throw new RuntimeException(
|
||||
"Failed to call Moodle webservice API function: " + functionName + " lms setup: " +
|
||||
MoodleRestTemplateFactory.this.lmsSetup + " response: " + response.getBody());
|
||||
lmsSetup + " response: " + response.getBody());
|
||||
}
|
||||
|
||||
final String body = response.getBody();
|
||||
|
@ -335,7 +338,7 @@ class MoodleRestTemplateFactory {
|
|||
this.accessToken = null;
|
||||
throw new RuntimeException(
|
||||
"Failed to call Moodle webservice API function: " + functionName + " lms setup: " +
|
||||
MoodleRestTemplateFactory.this.lmsSetup + " response: " + body);
|
||||
lmsSetup + " response: " + body);
|
||||
}
|
||||
|
||||
return body;
|
||||
|
@ -343,7 +346,11 @@ class MoodleRestTemplateFactory {
|
|||
|
||||
private void requestAccessToken() {
|
||||
|
||||
final LmsSetup lmsSetup = MoodleRestTemplateFactory.this.apiTemplateDataSupplier
|
||||
.getLmsSetup();
|
||||
|
||||
try {
|
||||
|
||||
final ResponseEntity<String> response = super.exchange(
|
||||
this.serverURL + this.tokenPath,
|
||||
HttpMethod.GET,
|
||||
|
@ -353,11 +360,11 @@ class MoodleRestTemplateFactory {
|
|||
|
||||
if (response.getStatusCode() != HttpStatus.OK) {
|
||||
log.error("Failed to gain access token for LMS (Moodle): lmsSetup: {} response: {} : {}",
|
||||
MoodleRestTemplateFactory.this.lmsSetup,
|
||||
lmsSetup,
|
||||
response.getStatusCode(),
|
||||
response.getBody());
|
||||
throw new RuntimeException("Failed to gain access token for LMS (Moodle): lmsSetup: " +
|
||||
MoodleRestTemplateFactory.this.lmsSetup + " response: " + response.getBody());
|
||||
lmsSetup + " response: " + response.getBody());
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -369,25 +376,25 @@ class MoodleRestTemplateFactory {
|
|||
throw new RuntimeException("Access Token request with 200 but no or invalid token body");
|
||||
} else {
|
||||
log.info("Successfully get access token from Moodle: {}",
|
||||
MoodleRestTemplateFactory.this.lmsSetup);
|
||||
lmsSetup);
|
||||
}
|
||||
|
||||
this.accessToken = moodleToken.token;
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to gain access token for LMS (Moodle): lmsSetup: {} response: {} : {}",
|
||||
MoodleRestTemplateFactory.this.lmsSetup,
|
||||
lmsSetup,
|
||||
response.getStatusCode(),
|
||||
response.getBody());
|
||||
throw new RuntimeException("Failed to gain access token for LMS (Moodle): lmsSetup: " +
|
||||
MoodleRestTemplateFactory.this.lmsSetup + " response: " + response.getBody(), e);
|
||||
lmsSetup + " response: " + response.getBody(), e);
|
||||
}
|
||||
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to gain access token for LMS (Moodle): lmsSetup: {} :",
|
||||
MoodleRestTemplateFactory.this.lmsSetup,
|
||||
lmsSetup,
|
||||
e);
|
||||
throw new RuntimeException("Failed to gain access token for LMS (Moodle): lmsSetup: " +
|
||||
MoodleRestTemplateFactory.this.lmsSetup + " cause: " + e.getMessage());
|
||||
lmsSetup + " cause: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -34,8 +34,6 @@ import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
|||
import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage;
|
||||
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.institution.LmsSetup;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.Features;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
|
@ -49,7 +47,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
|
|||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.IndicatorDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.NoSEBRestrictionException;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.SEBRestrictionService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator.IndicatorDistributedRequestCache;
|
||||
|
||||
|
@ -67,7 +65,7 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
|||
private final ExamDAO examDAO;
|
||||
private final ExamConfigurationMapDAO examConfigurationMapDAO;
|
||||
private final CacheManager cacheManager;
|
||||
private final LmsAPIService lmsAPIService;
|
||||
private final SEBRestrictionService sebRestrictionService;
|
||||
private final IndicatorDistributedRequestCache indicatorDistributedRequestCache;
|
||||
private final boolean distributedSetup;
|
||||
|
||||
|
@ -79,7 +77,7 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
|||
final ClientConnectionDAO clientConnectionDAO,
|
||||
final IndicatorDAO indicatorDAO,
|
||||
final CacheManager cacheManager,
|
||||
final LmsAPIService lmsAPIService,
|
||||
final SEBRestrictionService sebRestrictionService,
|
||||
final IndicatorDistributedRequestCache indicatorDistributedRequestCache,
|
||||
@Value("${sebserver.webservice.distributed:false}") final boolean distributedSetup) {
|
||||
|
||||
|
@ -90,7 +88,7 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
|||
this.clientConnectionDAO = clientConnectionDAO;
|
||||
this.cacheManager = cacheManager;
|
||||
this.indicatorDAO = indicatorDAO;
|
||||
this.lmsAPIService = lmsAPIService;
|
||||
this.sebRestrictionService = sebRestrictionService;
|
||||
this.indicatorDistributedRequestCache = indicatorDistributedRequestCache;
|
||||
this.distributedSetup = distributedSetup;
|
||||
}
|
||||
|
@ -117,7 +115,7 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
|||
|
||||
@Override
|
||||
public LmsAPIService getLmsAPIService() {
|
||||
return this.lmsAPIService;
|
||||
return this.sebRestrictionService.getLmsAPIService();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -149,29 +147,10 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
|||
return null;
|
||||
});
|
||||
|
||||
// check SEB restriction available and restricted
|
||||
// if SEB restriction is not available no consistency violation message is added
|
||||
final LmsSetup lmsSetup = this.lmsAPIService.getLmsSetup(exam.lmsSetupId)
|
||||
.getOr(null);
|
||||
if (lmsSetup != null && lmsSetup.lmsType.features.contains(Features.SEB_RESTRICTION)) {
|
||||
this.lmsAPIService.getLmsAPITemplate(exam.lmsSetupId)
|
||||
.map(t -> {
|
||||
if (t.testCourseRestrictionAPI().isOk()) {
|
||||
return t;
|
||||
} else {
|
||||
throw new NoSEBRestrictionException();
|
||||
}
|
||||
})
|
||||
.flatMap(t -> t.getSEBClientRestriction(exam))
|
||||
.onError(error -> {
|
||||
if (error instanceof NoSEBRestrictionException) {
|
||||
result.add(
|
||||
ErrorMessage.EXAM_CONSISTENCY_VALIDATION_SEB_RESTRICTION
|
||||
.of(exam.getModelId()));
|
||||
} else {
|
||||
throw new RuntimeException("Unexpected error: ", error);
|
||||
}
|
||||
});
|
||||
if (!this.sebRestrictionService.checkConsistency(exam.lmsSetupId, exam)) {
|
||||
result.add(
|
||||
ErrorMessage.EXAM_CONSISTENCY_VALIDATION_SEB_RESTRICTION
|
||||
.of(exam.getModelId()));
|
||||
}
|
||||
|
||||
// check indicator exists
|
||||
|
|
|
@ -246,12 +246,21 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
|||
@RequestParam(
|
||||
name = API.PARAM_INSTITUTION_ID,
|
||||
required = true,
|
||||
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId) {
|
||||
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
|
||||
@RequestParam(
|
||||
name = API.EXAM_ADMINISTRATION_CONSISTENCY_CHECK_INCLUDE_RESTRICTION,
|
||||
defaultValue = "false") final boolean includeRestriction) {
|
||||
|
||||
checkReadPrivilege(institutionId);
|
||||
return this.examSessionService
|
||||
final Collection<APIMessage> result = this.examSessionService
|
||||
.checkExamConsistency(modelId)
|
||||
.getOrThrow();
|
||||
|
||||
if (includeRestriction) {
|
||||
// TODO include seb restriction check and status
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// ****************************************************************************
|
||||
|
@ -524,37 +533,43 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
|||
}
|
||||
|
||||
private Result<Exam> applySEBRestriction(final Exam exam, final boolean restrict) {
|
||||
final LmsSetup lmsSetup = this.lmsAPIService.getLmsSetup(exam.lmsSetupId)
|
||||
.getOrThrow();
|
||||
|
||||
if (!lmsSetup.lmsType.features.contains(Features.SEB_RESTRICTION)) {
|
||||
return Result.ofError(new UnsupportedOperationException(
|
||||
"SEB Restriction feature not available for LMS type: " + lmsSetup.lmsType));
|
||||
}
|
||||
return Result.tryCatch(() -> {
|
||||
final LmsSetup lmsSetup = this.lmsAPIService.getLmsSetup(exam.lmsSetupId)
|
||||
.getOrThrow();
|
||||
|
||||
if (restrict) {
|
||||
if (!this.lmsAPIService
|
||||
.getLmsSetup(exam.lmsSetupId)
|
||||
.getOrThrow().lmsType.features.contains(Features.SEB_RESTRICTION)) {
|
||||
|
||||
return Result.ofError(new APIMessageException(
|
||||
APIMessage.ErrorMessage.ILLEGAL_API_ARGUMENT
|
||||
.of("The LMS for this Exam has no SEB restriction feature")));
|
||||
if (!lmsSetup.lmsType.features.contains(Features.SEB_RESTRICTION)) {
|
||||
throw new UnsupportedOperationException(
|
||||
"SEB Restriction feature not available for LMS type: " + lmsSetup.lmsType);
|
||||
}
|
||||
|
||||
if (this.examSessionService.hasActiveSEBClientConnections(exam.id)) {
|
||||
return Result.ofError(new APIMessageException(
|
||||
APIMessage.ErrorMessage.INTEGRITY_VALIDATION
|
||||
.of("Exam currently has active SEB Client connections.")));
|
||||
}
|
||||
if (restrict) {
|
||||
if (!this.lmsAPIService
|
||||
.getLmsSetup(exam.lmsSetupId)
|
||||
.getOrThrow().lmsType.features.contains(Features.SEB_RESTRICTION)) {
|
||||
|
||||
return this.checkNoActiveSEBClientConnections(exam)
|
||||
.flatMap(this.sebRestrictionService::applySEBClientRestriction)
|
||||
.flatMap(e -> this.examDAO.setSEBRestriction(exam.id, restrict));
|
||||
} else {
|
||||
return this.sebRestrictionService.releaseSEBClientRestriction(exam)
|
||||
.flatMap(e -> this.examDAO.setSEBRestriction(exam.id, restrict));
|
||||
}
|
||||
throw new APIMessageException(
|
||||
APIMessage.ErrorMessage.ILLEGAL_API_ARGUMENT
|
||||
.of("The LMS for this Exam has no SEB restriction feature"));
|
||||
}
|
||||
|
||||
if (this.examSessionService.hasActiveSEBClientConnections(exam.id)) {
|
||||
throw new APIMessageException(
|
||||
APIMessage.ErrorMessage.INTEGRITY_VALIDATION
|
||||
.of("Exam currently has active SEB Client connections."));
|
||||
}
|
||||
|
||||
// TODO double check before setSEBRestriction
|
||||
return this.checkNoActiveSEBClientConnections(exam)
|
||||
.flatMap(this.sebRestrictionService::applySEBClientRestriction)
|
||||
.flatMap(e -> this.examDAO.setSEBRestriction(exam.id, restrict))
|
||||
.getOrThrow();
|
||||
} else {
|
||||
return this.sebRestrictionService.releaseSEBClientRestriction(exam)
|
||||
.flatMap(e -> this.examDAO.setSEBRestriction(exam.id, restrict))
|
||||
.getOrThrow();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static Function<Collection<Exam>, List<Exam>> pageSort(final String sort) {
|
||||
|
|
|
@ -30,7 +30,6 @@ import ch.ethz.seb.sebserver.gbl.model.Entity;
|
|||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.LmsSetupRecordDynamicSqlSupport;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService;
|
||||
|
@ -39,7 +38,6 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionServic
|
|||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.LmsSetupDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.LmsSetupChangeEvent;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService;
|
||||
|
||||
@WebServiceProfile
|
||||
|
@ -134,10 +132,4 @@ public class LmsSetupController extends ActivatableEntityController<LmsSetup, Lm
|
|||
return new LmsSetup(null, postParams);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Result<LmsSetup> notifySaved(final LmsSetup entity) {
|
||||
this.applicationEventPublisher.publishEvent(new LmsSetupChangeEvent(entity));
|
||||
return super.notifySaved(entity);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -193,7 +193,7 @@ public class ModelObjectJSONGenerator {
|
|||
1L, 1L, 1L, "externalId", "name", "description", DateTime.now(), DateTime.now(),
|
||||
"startURL", ExamType.BYOD, "owner",
|
||||
Arrays.asList("user1", "user2"),
|
||||
ExamStatus.RUNNING, "browserExamKeys", true, null);
|
||||
ExamStatus.RUNNING, false, "browserExamKeys", true, null);
|
||||
System.out.println(domainObject.getClass().getSimpleName() + ":");
|
||||
System.out.println(writerWithDefaultPrettyPrinter.writeValueAsString(domainObject));
|
||||
|
||||
|
|
|
@ -850,6 +850,7 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
|
|||
null,
|
||||
Utils.immutableCollectionOf(userId),
|
||||
ExamStatus.RUNNING,
|
||||
false,
|
||||
null,
|
||||
true,
|
||||
null);
|
||||
|
|
|
@ -64,6 +64,7 @@ public class ExamAPITest extends AdministrationAPIIntegrationTester {
|
|||
exam.owner,
|
||||
Arrays.asList("user5"),
|
||||
null,
|
||||
false,
|
||||
null,
|
||||
true,
|
||||
null))
|
||||
|
@ -94,6 +95,7 @@ public class ExamAPITest extends AdministrationAPIIntegrationTester {
|
|||
exam.owner,
|
||||
Arrays.asList("user2"),
|
||||
null,
|
||||
false,
|
||||
null,
|
||||
true,
|
||||
null))
|
||||
|
|
|
@ -10,7 +10,10 @@ package ch.ethz.seb.sebserver.webservice.integration.api.admin;
|
|||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.test.context.jdbc.Sql;
|
||||
|
@ -23,10 +26,25 @@ import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
|||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
|
||||
|
||||
@Sql(scripts = { "classpath:schema-test.sql", "classpath:data-test.sql" })
|
||||
public class ExamImportTest extends AdministrationAPIIntegrationTester {
|
||||
|
||||
@Autowired
|
||||
private LmsAPIService lmsAPIService;
|
||||
|
||||
@Before
|
||||
@Sql(scripts = { "classpath:schema-test.sql", "classpath:data-test.sql" })
|
||||
public void init() {
|
||||
this.lmsAPIService.cleanup();
|
||||
}
|
||||
|
||||
@After
|
||||
@Sql(scripts = { "classpath:schema-test.sql", "classpath:data-test.sql" })
|
||||
public void cleanup() {
|
||||
this.lmsAPIService.cleanup();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImportFromQuiz() throws Exception {
|
||||
// create new active LmsSetup Mock with seb-admin
|
||||
|
@ -57,7 +75,7 @@ public class ExamImportTest extends AdministrationAPIIntegrationTester {
|
|||
this,
|
||||
getSebAdminAccess(),
|
||||
getSebAdminAccess(),
|
||||
"LmsSetupMock",
|
||||
"LmsSetupMock1",
|
||||
"quiz2",
|
||||
ExamType.MANAGED,
|
||||
"user5");
|
||||
|
@ -76,7 +94,7 @@ public class ExamImportTest extends AdministrationAPIIntegrationTester {
|
|||
this,
|
||||
getAdminInstitution2Access(),
|
||||
getAdminInstitution2Access(),
|
||||
"LmsSetupMock",
|
||||
"LmsSetupMock2",
|
||||
"quiz2",
|
||||
ExamType.MANAGED,
|
||||
"user7");
|
||||
|
@ -90,7 +108,7 @@ public class ExamImportTest extends AdministrationAPIIntegrationTester {
|
|||
this,
|
||||
getAdminInstitution2Access(),
|
||||
getExamAdmin1(), // this exam administrator is on Institution 2
|
||||
"LmsSetupMock2",
|
||||
"LmsSetupMock3",
|
||||
"quiz2",
|
||||
ExamType.MANAGED,
|
||||
"user7");
|
||||
|
@ -108,7 +126,7 @@ public class ExamImportTest extends AdministrationAPIIntegrationTester {
|
|||
this,
|
||||
getAdminInstitution1Access(),
|
||||
getExamAdmin1(), // this exam administrator is on Institution 2
|
||||
"LmsSetupMock",
|
||||
"LmsSetupMock4",
|
||||
"quiz2",
|
||||
ExamType.MANAGED,
|
||||
"user7");
|
||||
|
|
|
@ -71,7 +71,6 @@ public class MoodleCourseAccessTest {
|
|||
|
||||
final MoodleCourseAccess moodleCourseAccess = new MoodleCourseAccess(
|
||||
new JSONMapper(),
|
||||
null,
|
||||
moodleRestTemplateFactory,
|
||||
null,
|
||||
mock(AsyncService.class),
|
||||
|
@ -120,7 +119,6 @@ public class MoodleCourseAccessTest {
|
|||
|
||||
final MoodleCourseAccess moodleCourseAccess = new MoodleCourseAccess(
|
||||
new JSONMapper(),
|
||||
null,
|
||||
moodleRestTemplateFactory,
|
||||
null,
|
||||
mock(AsyncService.class),
|
||||
|
@ -143,7 +141,6 @@ public class MoodleCourseAccessTest {
|
|||
|
||||
final MoodleCourseAccess moodleCourseAccess = new MoodleCourseAccess(
|
||||
new JSONMapper(),
|
||||
null,
|
||||
moodleRestTemplateFactory,
|
||||
null,
|
||||
mock(AsyncService.class),
|
||||
|
@ -165,7 +162,6 @@ public class MoodleCourseAccessTest {
|
|||
|
||||
final MoodleCourseAccess moodleCourseAccess = new MoodleCourseAccess(
|
||||
new JSONMapper(),
|
||||
null,
|
||||
moodleRestTemplateFactory,
|
||||
null,
|
||||
mock(AsyncService.class),
|
||||
|
|
Loading…
Reference in a new issue