Merge branch 'SEBSERV-417' into development
This commit is contained in:
commit
845e29ed17
26 changed files with 487 additions and 72 deletions
|
@ -157,6 +157,9 @@ public final class API {
|
||||||
+ LMS_SETUP_TEST_PATH_SEGMENT
|
+ LMS_SETUP_TEST_PATH_SEGMENT
|
||||||
+ LMS_SETUP_TEST_AD_HOC_PATH_SEGMENT;
|
+ LMS_SETUP_TEST_AD_HOC_PATH_SEGMENT;
|
||||||
|
|
||||||
|
public static final String LMS_FULL_INTEGRATION_REFRESH_TOKEN_ENDPOINT = "/refresh-access-token";
|
||||||
|
public static final String LMS_FULL_INTEGRATION_LMS_UUID = "lms_uuid";
|
||||||
|
|
||||||
public static final String USER_ACCOUNT_ENDPOINT = "/useraccount";
|
public static final String USER_ACCOUNT_ENDPOINT = "/useraccount";
|
||||||
|
|
||||||
public static final String QUIZ_DISCOVERY_ENDPOINT = "/quiz";
|
public static final String QUIZ_DISCOVERY_ENDPOINT = "/quiz";
|
||||||
|
|
|
@ -51,7 +51,10 @@ public final class LmsSetup implements GrantEntity, Activatable {
|
||||||
SEB_RESTRICTION,
|
SEB_RESTRICTION,
|
||||||
/** Indicates if the LMS integration has some process for course recovery
|
/** Indicates if the LMS integration has some process for course recovery
|
||||||
* after backup-restore process for example. */
|
* after backup-restore process for example. */
|
||||||
COURSE_RECOVERY
|
COURSE_RECOVERY,
|
||||||
|
|
||||||
|
/** Indicates if the LMS integration has some deeper integration that involves LMS calls to SEB Server*/
|
||||||
|
LMS_FULL_INTEGRATION
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Defines the supported types if LMS bindings.
|
/** Defines the supported types if LMS bindings.
|
||||||
|
@ -64,7 +67,7 @@ public final class LmsSetup implements GrantEntity, Activatable {
|
||||||
/** The Moodle binding features only the course access API so far */
|
/** The Moodle binding features only the course access API so far */
|
||||||
MOODLE(Features.COURSE_API, Features.COURSE_RECOVERY /* , Features.SEB_RESTRICTION */),
|
MOODLE(Features.COURSE_API, Features.COURSE_RECOVERY /* , Features.SEB_RESTRICTION */),
|
||||||
/** The Moodle binding features with SEB Server integration plugin for fully featured */
|
/** The Moodle binding features with SEB Server integration plugin for fully featured */
|
||||||
MOODLE_PLUGIN(Features.COURSE_API, Features.COURSE_RECOVERY, Features.SEB_RESTRICTION),
|
MOODLE_PLUGIN(Features.COURSE_API, Features.COURSE_RECOVERY, Features.SEB_RESTRICTION, Features.LMS_FULL_INTEGRATION),
|
||||||
/** The Ans Delft binding is on the way */
|
/** The Ans Delft binding is on the way */
|
||||||
ANS_DELFT(Features.COURSE_API, Features.SEB_RESTRICTION),
|
ANS_DELFT(Features.COURSE_API, Features.SEB_RESTRICTION),
|
||||||
/** The OpenOLAT binding is on the way */
|
/** The OpenOLAT binding is on the way */
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019 ETH Zürich, IT Services
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.ethz.seb.sebserver.webservice.servicelayer.lms;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
|
|
||||||
|
public interface FullLmsIntegrationAPI {
|
||||||
|
|
||||||
|
Result<Void> createConnectionDetails();
|
||||||
|
|
||||||
|
Result<Void> updateConnectionDetails();
|
||||||
|
|
||||||
|
Result<Void> deleteConnectionDetails();
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019 ETH Zürich, IT Services
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.ethz.seb.sebserver.webservice.servicelayer.lms;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
|
|
||||||
|
public interface FullLmsIntegrationService {
|
||||||
|
|
||||||
|
Result<LmsAPITemplate> getLmsAPITemplate(String lmsUUID);
|
||||||
|
|
||||||
|
Result<Void> refreshAccessToken(String lmsUUID);
|
||||||
|
|
||||||
|
Result<Void> applyFullLmsIntegration(Long lmsSetupId, boolean refreshToken);
|
||||||
|
|
||||||
|
Result<Void> deleteFullLmsIntegration(Long lmsSetupId);
|
||||||
|
|
||||||
|
Result<Map<String, String>> getExamTemplateSelection();
|
||||||
|
|
||||||
|
Result<Exam> importExam(String lmsUUID, String courseId, String quizId, String examTemplateId);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -26,7 +26,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
||||||
|
|
||||||
/** Defines the LMS API access service interface with all functionality needed to access
|
/** Defines the LMS API access service interface with all functionality needed to access
|
||||||
* a LMS API within a given LmsSetup configuration.
|
* a LMS API within a given LmsSetup configuration.
|
||||||
*
|
* <p>
|
||||||
* There are LmsAPITemplate implementations for each type of supported LMS that are managed
|
* There are LmsAPITemplate implementations for each type of supported LMS that are managed
|
||||||
* in reference to a LmsSetup configuration within this service. This means actually that
|
* in reference to a LmsSetup configuration within this service. This means actually that
|
||||||
* this service caches requested LmsAPITemplate (that holds the LMS API connection) as long
|
* this service caches requested LmsAPITemplate (that holds the LMS API connection) as long
|
||||||
|
@ -100,7 +100,7 @@ public interface LmsAPIService {
|
||||||
* Now supports name and startTime filtering
|
* Now supports name and startTime filtering
|
||||||
*
|
*
|
||||||
* @param filterMap the FilterMap containing the filter criteria
|
* @param filterMap the FilterMap containing the filter criteria
|
||||||
* @return true if the given QuizzData passes the filter */
|
* @return filter predicate */
|
||||||
static Predicate<QuizData> quizFilterPredicate(final FilterMap filterMap) {
|
static Predicate<QuizData> quizFilterPredicate(final FilterMap filterMap) {
|
||||||
if (filterMap == null) {
|
if (filterMap == null) {
|
||||||
return q -> true;
|
return q -> true;
|
||||||
|
|
|
@ -63,7 +63,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.AbstractCachedCour
|
||||||
* or partial API Access and can flag missing or wrong {@link LmsSetup } attributes with the resulting
|
* or partial API Access and can flag missing or wrong {@link LmsSetup } attributes with the resulting
|
||||||
* {@link LmsSetupTestResult }.</br>
|
* {@link LmsSetupTestResult }.</br>
|
||||||
* SEB Server than uses an instance of this template to communicate with the an LMS. */
|
* SEB Server than uses an instance of this template to communicate with the an LMS. */
|
||||||
public interface LmsAPITemplate extends CourseAccessAPI, SEBRestrictionAPI {
|
public interface LmsAPITemplate extends CourseAccessAPI, SEBRestrictionAPI, FullLmsIntegrationAPI {
|
||||||
|
|
||||||
/** Get the LMS type of the concrete template implementation
|
/** Get the LMS type of the concrete template implementation
|
||||||
*
|
*
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019 ETH Zürich, IT Services
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
@Service
|
||||||
|
@WebServiceProfile
|
||||||
|
public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService {
|
||||||
|
@Override
|
||||||
|
public Result<LmsAPITemplate> getLmsAPITemplate(final String lmsUUID) {
|
||||||
|
return Result.ofRuntimeError("TODO");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Void> refreshAccessToken(final String lmsUUID) {
|
||||||
|
return Result.ofRuntimeError("TODO");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Void> applyFullLmsIntegration(final Long lmsSetupId, final boolean refreshToken) {
|
||||||
|
return Result.ofRuntimeError("TODO");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Void> deleteFullLmsIntegration(final Long lmsSetupId) {
|
||||||
|
return Result.ofRuntimeError("TODO");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Map<String, String>> getExamTemplateSelection() {
|
||||||
|
return Result.ofRuntimeError("TODO");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Exam> importExam(
|
||||||
|
final String lmsUUID,
|
||||||
|
final String courseId,
|
||||||
|
final String quizId,
|
||||||
|
final String examTemplateId) {
|
||||||
|
return Result.ofRuntimeError("TODO");
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.*;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.core.env.Environment;
|
import org.springframework.core.env.Environment;
|
||||||
|
@ -28,10 +29,6 @@ import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.user.ExamineeAccountDetails;
|
import ch.ethz.seb.sebserver.gbl.model.user.ExamineeAccountDetails;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
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.FilterMap;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.APITemplateDataSupplier;
|
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.CourseAccessAPI;
|
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate;
|
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.SEBRestrictionAPI;
|
|
||||||
|
|
||||||
public class LmsAPITemplateAdapter implements LmsAPITemplate {
|
public class LmsAPITemplateAdapter implements LmsAPITemplate {
|
||||||
|
|
||||||
|
@ -39,6 +36,8 @@ public class LmsAPITemplateAdapter implements LmsAPITemplate {
|
||||||
|
|
||||||
private final CourseAccessAPI courseAccessAPI;
|
private final CourseAccessAPI courseAccessAPI;
|
||||||
private final SEBRestrictionAPI sebRestrictionAPI;
|
private final SEBRestrictionAPI sebRestrictionAPI;
|
||||||
|
|
||||||
|
private final FullLmsIntegrationAPI lmsIntegrationAPI;
|
||||||
private final APITemplateDataSupplier apiTemplateDataSupplier;
|
private final APITemplateDataSupplier apiTemplateDataSupplier;
|
||||||
|
|
||||||
/** CircuitBreaker for protected lmsTestRequest */
|
/** CircuitBreaker for protected lmsTestRequest */
|
||||||
|
@ -57,16 +56,20 @@ public class LmsAPITemplateAdapter implements LmsAPITemplate {
|
||||||
private final CircuitBreaker<SEBRestriction> restrictionRequest;
|
private final CircuitBreaker<SEBRestriction> restrictionRequest;
|
||||||
private final CircuitBreaker<Exam> releaseRestrictionRequest;
|
private final CircuitBreaker<Exam> releaseRestrictionRequest;
|
||||||
|
|
||||||
|
private final CircuitBreaker<Void> lmsAccessRequest;
|
||||||
|
|
||||||
public LmsAPITemplateAdapter(
|
public LmsAPITemplateAdapter(
|
||||||
final AsyncService asyncService,
|
final AsyncService asyncService,
|
||||||
final Environment environment,
|
final Environment environment,
|
||||||
final APITemplateDataSupplier apiTemplateDataSupplier,
|
final APITemplateDataSupplier apiTemplateDataSupplier,
|
||||||
final CourseAccessAPI courseAccessAPI,
|
final CourseAccessAPI courseAccessAPI,
|
||||||
final SEBRestrictionAPI sebRestrictionAPI) {
|
final SEBRestrictionAPI sebRestrictionAPI,
|
||||||
|
final FullLmsIntegrationAPI lmsIntegrationAPI) {
|
||||||
|
|
||||||
this.courseAccessAPI = courseAccessAPI;
|
this.courseAccessAPI = courseAccessAPI;
|
||||||
this.sebRestrictionAPI = sebRestrictionAPI;
|
this.sebRestrictionAPI = sebRestrictionAPI;
|
||||||
this.apiTemplateDataSupplier = apiTemplateDataSupplier;
|
this.apiTemplateDataSupplier = apiTemplateDataSupplier;
|
||||||
|
this.lmsIntegrationAPI = lmsIntegrationAPI;
|
||||||
|
|
||||||
this.lmsTestRequest = asyncService.createCircuitBreaker(
|
this.lmsTestRequest = asyncService.createCircuitBreaker(
|
||||||
environment.getProperty(
|
environment.getProperty(
|
||||||
|
@ -82,6 +85,20 @@ public class LmsAPITemplateAdapter implements LmsAPITemplate {
|
||||||
Long.class,
|
Long.class,
|
||||||
0L));
|
0L));
|
||||||
|
|
||||||
|
lmsAccessRequest = asyncService.createCircuitBreaker(
|
||||||
|
environment.getProperty(
|
||||||
|
"sebserver.webservice.circuitbreaker.lmsTestRequest.attempts",
|
||||||
|
Integer.class,
|
||||||
|
2),
|
||||||
|
environment.getProperty(
|
||||||
|
"sebserver.webservice.circuitbreaker.lmsTestRequest.blockingTime",
|
||||||
|
Long.class,
|
||||||
|
Constants.SECOND_IN_MILLIS * 20),
|
||||||
|
environment.getProperty(
|
||||||
|
"sebserver.webservice.circuitbreaker.lmsTestRequest.timeToRecover",
|
||||||
|
Long.class,
|
||||||
|
0L));
|
||||||
|
|
||||||
this.quizzesRequest = asyncService.createCircuitBreaker(
|
this.quizzesRequest = asyncService.createCircuitBreaker(
|
||||||
environment.getProperty(
|
environment.getProperty(
|
||||||
"sebserver.webservice.circuitbreaker.quizzesRequest.attempts",
|
"sebserver.webservice.circuitbreaker.quizzesRequest.attempts",
|
||||||
|
@ -210,7 +227,7 @@ public class LmsAPITemplateAdapter implements LmsAPITemplate {
|
||||||
log.debug("Test Course Access API for LMSSetup: {}", lmsSetup());
|
log.debug("Test Course Access API for LMSSetup: {}", lmsSetup());
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.lmsTestRequest.protectedRun(() -> this.courseAccessAPI.testCourseAccessAPI())
|
return this.lmsTestRequest.protectedRun(this.courseAccessAPI::testCourseAccessAPI)
|
||||||
.onError(error -> log.error(
|
.onError(error -> log.error(
|
||||||
"Failed to run protectedQuizzesRequest: {}",
|
"Failed to run protectedQuizzesRequest: {}",
|
||||||
error.getMessage()))
|
error.getMessage()))
|
||||||
|
@ -408,14 +425,12 @@ public class LmsAPITemplateAdapter implements LmsAPITemplate {
|
||||||
log.debug("Apply course restriction: {} for LMSSetup: {}", exam, lmsSetup());
|
log.debug("Apply course restriction: {} for LMSSetup: {}", exam, lmsSetup());
|
||||||
}
|
}
|
||||||
|
|
||||||
final Result<SEBRestriction> protectedRun = this.restrictionRequest.protectedRun(() -> this.sebRestrictionAPI
|
return this.restrictionRequest.protectedRun(() -> this.sebRestrictionAPI
|
||||||
.applySEBClientRestriction(exam, sebRestrictionData)
|
.applySEBClientRestriction(exam, sebRestrictionData)
|
||||||
.onError(error -> log.error(
|
.onError(error -> log.error(
|
||||||
"Failed to apply SEB restrictions: {}",
|
"Failed to apply SEB restrictions: {}",
|
||||||
error.getMessage()))
|
error.getMessage()))
|
||||||
.getOrThrow());
|
.getOrThrow());
|
||||||
|
|
||||||
return protectedRun;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -446,4 +461,57 @@ public class LmsAPITemplateAdapter implements LmsAPITemplate {
|
||||||
return protectedRun;
|
return protectedRun;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Void> createConnectionDetails() {
|
||||||
|
if (this.lmsIntegrationAPI == null) {
|
||||||
|
return Result.ofError(
|
||||||
|
new UnsupportedOperationException("LMS Integration API Not Supported For: " + getType().name()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("Create LMS connection details for LMSSetup: {}", lmsSetup());
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.lmsAccessRequest.protectedRun(() -> this.lmsIntegrationAPI.createConnectionDetails()
|
||||||
|
.onError(error -> log.error(
|
||||||
|
"Failed to run protected createConnectionDetails: {}",
|
||||||
|
error.getMessage()))
|
||||||
|
.getOrThrow());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Void> updateConnectionDetails() {
|
||||||
|
if (this.lmsIntegrationAPI == null) {
|
||||||
|
return Result.ofError(
|
||||||
|
new UnsupportedOperationException("LMS Integration API Not Supported For: " + getType().name()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("Update LMS connection details for LMSSetup: {}", lmsSetup());
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.lmsAccessRequest.protectedRun(() -> this.lmsIntegrationAPI.updateConnectionDetails()
|
||||||
|
.onError(error -> log.error(
|
||||||
|
"Failed to run protected updateConnectionDetails: {}",
|
||||||
|
error.getMessage()))
|
||||||
|
.getOrThrow());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Void> deleteConnectionDetails() {
|
||||||
|
if (this.lmsIntegrationAPI == null) {
|
||||||
|
return Result.ofError(
|
||||||
|
new UnsupportedOperationException("LMS Integration API Not Supported For: " + getType().name()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("Delete LMS connection details for LMSSetup: {}", lmsSetup());
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.lmsAccessRequest.protectedRun(() -> this.lmsIntegrationAPI.deleteConnectionDetails()
|
||||||
|
.onError(error -> log.error(
|
||||||
|
"Failed to run protected deleteConnectionDetails: {}",
|
||||||
|
error.getMessage()))
|
||||||
|
.getOrThrow());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -420,6 +420,21 @@ public class AnsLmsAPITemplate extends AbstractCachedCourseAccess implements Lms
|
||||||
.map(x -> exam);
|
.map(x -> exam);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Void> createConnectionDetails() {
|
||||||
|
return Result.ofRuntimeError("Not Supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Void> updateConnectionDetails() {
|
||||||
|
return Result.ofRuntimeError("Not Supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Void> deleteConnectionDetails() {
|
||||||
|
return Result.ofRuntimeError("Not Supported");
|
||||||
|
}
|
||||||
|
|
||||||
private enum LinkRel {
|
private enum LinkRel {
|
||||||
FIRST, LAST, PREV, NEXT
|
FIRST, LAST, PREV, NEXT
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,7 +74,8 @@ public class AnsLmsAPITemplateFactory implements LmsAPITemplateFactory {
|
||||||
this.environment,
|
this.environment,
|
||||||
apiTemplateDataSupplier,
|
apiTemplateDataSupplier,
|
||||||
ansLmsAPITemplate,
|
ansLmsAPITemplate,
|
||||||
ansLmsAPITemplate);
|
ansLmsAPITemplate,
|
||||||
|
null);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -100,7 +100,8 @@ public class OpenEdxLmsAPITemplateFactory implements LmsAPITemplateFactory {
|
||||||
this.environment,
|
this.environment,
|
||||||
apiTemplateDataSupplier,
|
apiTemplateDataSupplier,
|
||||||
openEdxCourseAccess,
|
openEdxCourseAccess,
|
||||||
openEdxCourseRestriction);
|
openEdxCourseRestriction,
|
||||||
|
null);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,14 +53,13 @@ public class MockLmsAPITemplateFactory implements LmsAPITemplateFactory {
|
||||||
apiTemplateDataSupplier,
|
apiTemplateDataSupplier,
|
||||||
this.webserviceInfo);
|
this.webserviceInfo);
|
||||||
|
|
||||||
final MockSEBRestrictionAPI mockSEBRestrictionAPI = new MockSEBRestrictionAPI();
|
|
||||||
|
|
||||||
return Result.tryCatch(() -> new LmsAPITemplateAdapter(
|
return Result.tryCatch(() -> new LmsAPITemplateAdapter(
|
||||||
this.asyncService,
|
this.asyncService,
|
||||||
this.environment,
|
this.environment,
|
||||||
apiTemplateDataSupplier,
|
apiTemplateDataSupplier,
|
||||||
mockCourseAccessAPI,
|
mockCourseAccessAPI,
|
||||||
mockSEBRestrictionAPI));
|
new MockSEBRestrictionAPI(),
|
||||||
|
new MockupFullIntegration()));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019 ETH Zürich, IT Services
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.mockup;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationAPI;
|
||||||
|
|
||||||
|
public class MockupFullIntegration implements FullLmsIntegrationAPI {
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Void> createConnectionDetails() {
|
||||||
|
return Result.ofRuntimeError("TODO");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Void> updateConnectionDetails() {
|
||||||
|
return Result.ofRuntimeError("TODO");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Void> deleteConnectionDetails() {
|
||||||
|
return Result.ofRuntimeError("TODO");
|
||||||
|
}
|
||||||
|
}
|
|
@ -91,7 +91,8 @@ public class MoodleLmsAPITemplateFactory implements LmsAPITemplateFactory {
|
||||||
this.environment,
|
this.environment,
|
||||||
apiTemplateDataSupplier,
|
apiTemplateDataSupplier,
|
||||||
moodleCourseAccess,
|
moodleCourseAccess,
|
||||||
new MoodleCourseRestriction());
|
new MoodleCourseRestriction(),
|
||||||
|
null);
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019 ETH Zürich, IT Services
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.plugin;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationAPI;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestTemplateFactory;
|
||||||
|
|
||||||
|
public class MoodlePluginFullIntegration implements FullLmsIntegrationAPI {
|
||||||
|
|
||||||
|
private final JSONMapper jsonMapper;
|
||||||
|
private final MoodleRestTemplateFactory restTemplateFactory;
|
||||||
|
|
||||||
|
public MoodlePluginFullIntegration(
|
||||||
|
final JSONMapper jsonMapper,
|
||||||
|
final MoodleRestTemplateFactory restTemplateFactory) {
|
||||||
|
|
||||||
|
this.jsonMapper = jsonMapper;
|
||||||
|
this.restTemplateFactory = restTemplateFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Void> createConnectionDetails() {
|
||||||
|
return Result.ofRuntimeError("TODO");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Void> updateConnectionDetails() {
|
||||||
|
return Result.ofRuntimeError("TODO");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Void> deleteConnectionDetails() {
|
||||||
|
return Result.ofRuntimeError("TODO");
|
||||||
|
}
|
||||||
|
}
|
|
@ -101,12 +101,18 @@ public class MooldePluginLmsAPITemplateFactory implements LmsAPITemplateFactory
|
||||||
moodleRestTemplateFactory,
|
moodleRestTemplateFactory,
|
||||||
this.examConfigurationValueService);
|
this.examConfigurationValueService);
|
||||||
|
|
||||||
|
final MoodlePluginFullIntegration moodlePluginFullIntegration = new MoodlePluginFullIntegration(
|
||||||
|
this.jsonMapper,
|
||||||
|
moodleRestTemplateFactory
|
||||||
|
);
|
||||||
|
|
||||||
return new LmsAPITemplateAdapter(
|
return new LmsAPITemplateAdapter(
|
||||||
this.asyncService,
|
this.asyncService,
|
||||||
this.environment,
|
this.environment,
|
||||||
apiTemplateDataSupplier,
|
apiTemplateDataSupplier,
|
||||||
moodlePluginCourseAccess,
|
moodlePluginCourseAccess,
|
||||||
moodlePluginCourseRestriction);
|
moodlePluginCourseRestriction,
|
||||||
|
moodlePluginFullIntegration);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -407,6 +407,21 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm
|
||||||
.map(x -> exam);
|
.map(x -> exam);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Void> createConnectionDetails() {
|
||||||
|
return Result.ofRuntimeError("Not Supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Void> updateConnectionDetails() {
|
||||||
|
return Result.ofRuntimeError("Not Supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Void> deleteConnectionDetails() {
|
||||||
|
return Result.ofRuntimeError("Not Supported");
|
||||||
|
}
|
||||||
|
|
||||||
private <T> T apiGet(final RestTemplate restTemplate, final String url, final Class<T> type) {
|
private <T> T apiGet(final RestTemplate restTemplate, final String url, final Class<T> type) {
|
||||||
final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup();
|
final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup();
|
||||||
final ResponseEntity<T> res = restTemplate.exchange(
|
final ResponseEntity<T> res = restTemplate.exchange(
|
||||||
|
@ -489,4 +504,5 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,7 +87,8 @@ public class OlatLmsAPITemplateFactory implements LmsAPITemplateFactory {
|
||||||
this.environment,
|
this.environment,
|
||||||
apiTemplateDataSupplier,
|
apiTemplateDataSupplier,
|
||||||
olatLmsAPITemplate,
|
olatLmsAPITemplate,
|
||||||
olatLmsAPITemplate);
|
olatLmsAPITemplate,
|
||||||
|
null);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,6 @@ import java.io.IOException;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
|
||||||
import org.apache.catalina.filters.RemoteIpFilter;
|
import org.apache.catalina.filters.RemoteIpFilter;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -44,7 +43,6 @@ import org.springframework.security.oauth2.provider.token.UserAuthenticationConv
|
||||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.WebSecurityConfig;
|
import ch.ethz.seb.sebserver.WebSecurityConfig;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
|
||||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||||
import ch.ethz.seb.sebserver.webservice.weblayer.oauth.PreAuthProvider;
|
import ch.ethz.seb.sebserver.webservice.weblayer.oauth.PreAuthProvider;
|
||||||
import ch.ethz.seb.sebserver.webservice.weblayer.oauth.WebClientDetailsService;
|
import ch.ethz.seb.sebserver.webservice.weblayer.oauth.WebClientDetailsService;
|
||||||
|
@ -85,7 +83,7 @@ public class WebServiceSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
@Autowired
|
@Autowired
|
||||||
private TokenStore tokenStore;
|
private TokenStore tokenStore;
|
||||||
@Autowired
|
@Autowired
|
||||||
private WebClientDetailsService webServiceClientDetails;
|
private WebClientDetailsService webClientDetailsService;
|
||||||
@Autowired
|
@Autowired
|
||||||
private PreAuthProvider preAuthProvider;
|
private PreAuthProvider preAuthProvider;
|
||||||
|
|
||||||
|
@ -93,6 +91,8 @@ public class WebServiceSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
private String adminAPIEndpoint;
|
private String adminAPIEndpoint;
|
||||||
@Value("${sebserver.webservice.api.exam.endpoint}")
|
@Value("${sebserver.webservice.api.exam.endpoint}")
|
||||||
private String examAPIEndpoint;
|
private String examAPIEndpoint;
|
||||||
|
@Value("${sebserver.webservice.lms.api.endpoint}")
|
||||||
|
private String lmsAPIEndpoint;
|
||||||
@Value("${management.endpoints.web.base-path:NONE}")
|
@Value("${management.endpoints.web.base-path:NONE}")
|
||||||
private String actuatorEndpoint;
|
private String actuatorEndpoint;
|
||||||
@Value("${sebserver.webservice.http.redirect.gui}")
|
@Value("${sebserver.webservice.http.redirect.gui}")
|
||||||
|
@ -104,9 +104,12 @@ public class WebServiceSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
private Integer adminRefreshTokenValSec;
|
private Integer adminRefreshTokenValSec;
|
||||||
@Value("${sebserver.webservice.api.exam.accessTokenValiditySeconds:43200}")
|
@Value("${sebserver.webservice.api.exam.accessTokenValiditySeconds:43200}")
|
||||||
private Integer examAccessTokenValSec;
|
private Integer examAccessTokenValSec;
|
||||||
|
@Value("${sebserver.webservice.lms.api.accessTokenValiditySeconds:-1}")
|
||||||
|
private Integer lmsAccessTokenValSec;
|
||||||
|
|
||||||
|
|
||||||
/** Used to get real remote IP address by using "X-Forwarded-For" and "X-Forwarded-Proto" header.
|
/** Used to get real remote IP address by using "X-Forwarded-For" and "X-Forwarded-Proto" header.
|
||||||
* https://tomcat.apache.org/tomcat-7.0-doc/api/org/apache/catalina/filters/RemoteIpFilter.html
|
* <a href="https://tomcat.apache.org/tomcat-7.0-doc/api/org/apache/catalina/filters/RemoteIpFilter.html">see</a>
|
||||||
*
|
*
|
||||||
* @return RemoteIpFilter instance */
|
* @return RemoteIpFilter instance */
|
||||||
@Bean
|
@Bean
|
||||||
|
@ -132,8 +135,7 @@ public class WebServiceSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
@Override
|
@Override
|
||||||
@Bean(AUTHENTICATION_MANAGER)
|
@Bean(AUTHENTICATION_MANAGER)
|
||||||
public AuthenticationManager authenticationManagerBean() throws Exception {
|
public AuthenticationManager authenticationManagerBean() throws Exception {
|
||||||
final AuthenticationManager authenticationManagerBean = super.authenticationManagerBean();
|
return super.authenticationManagerBean();
|
||||||
return authenticationManagerBean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -162,7 +164,7 @@ public class WebServiceSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
protected ResourceServerConfiguration sebServerAdminAPIResources() throws Exception {
|
protected ResourceServerConfiguration sebServerAdminAPIResources() throws Exception {
|
||||||
return new AdminAPIResourceServerConfiguration(
|
return new AdminAPIResourceServerConfiguration(
|
||||||
this.tokenStore,
|
this.tokenStore,
|
||||||
this.webServiceClientDetails,
|
this.webClientDetailsService,
|
||||||
authenticationManagerBean(),
|
authenticationManagerBean(),
|
||||||
this.adminAPIEndpoint,
|
this.adminAPIEndpoint,
|
||||||
this.unauthorizedRedirect,
|
this.unauthorizedRedirect,
|
||||||
|
@ -174,30 +176,24 @@ public class WebServiceSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
protected ResourceServerConfiguration sebServerExamAPIResources() throws Exception {
|
protected ResourceServerConfiguration sebServerExamAPIResources() throws Exception {
|
||||||
return new ExamAPIClientResourceServerConfiguration(
|
return new ExamAPIClientResourceServerConfiguration(
|
||||||
this.tokenStore,
|
this.tokenStore,
|
||||||
this.webServiceClientDetails,
|
this.webClientDetailsService,
|
||||||
authenticationManagerBean(),
|
authenticationManagerBean(),
|
||||||
this.examAPIEndpoint,
|
this.examAPIEndpoint,
|
||||||
this.examAccessTokenValSec);
|
this.examAccessTokenValSec);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
protected ResourceServerConfiguration sebServerActuatorResources() throws Exception {
|
protected ResourceServerConfiguration sebServerLMSAPIResources() throws Exception {
|
||||||
if ("NONE".equals(this.actuatorEndpoint)) {
|
return new LMSAPIClientResourceServerConfiguration(
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ActuatorResourceServerConfiguration(
|
|
||||||
this.tokenStore,
|
this.tokenStore,
|
||||||
this.webServiceClientDetails,
|
this.webClientDetailsService,
|
||||||
authenticationManagerBean(),
|
authenticationManagerBean(),
|
||||||
this.actuatorEndpoint,
|
this.lmsAPIEndpoint,
|
||||||
this.unauthorizedRedirect,
|
this.lmsAccessTokenValSec);
|
||||||
this.adminAccessTokenValSec,
|
|
||||||
this.adminRefreshTokenValSec);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: We need two different class types here to support Spring configuration for different
|
|
||||||
// ResourceServerConfiguration. There is a class type now for the Admin API as well as for the Exam API
|
// NOTE: We need different class types here to support Spring configuration for different
|
||||||
private static final class AdminAPIResourceServerConfiguration extends WebserviceResourceConfiguration {
|
private static final class AdminAPIResourceServerConfiguration extends WebserviceResourceConfiguration {
|
||||||
|
|
||||||
public AdminAPIResourceServerConfiguration(
|
public AdminAPIResourceServerConfiguration(
|
||||||
|
@ -223,8 +219,7 @@ public class WebServiceSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: We need two different class types here to support Spring configuration for different
|
// NOTE: We need different class types here to support Spring configuration for different
|
||||||
// ResourceServerConfiguration. There is a class type now for the Admin API as well as for the Exam API
|
|
||||||
private static final class ExamAPIClientResourceServerConfiguration extends WebserviceResourceConfiguration {
|
private static final class ExamAPIClientResourceServerConfiguration extends WebserviceResourceConfiguration {
|
||||||
|
|
||||||
public ExamAPIClientResourceServerConfiguration(
|
public ExamAPIClientResourceServerConfiguration(
|
||||||
|
@ -254,40 +249,33 @@ public class WebServiceSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class ActuatorResourceServerConfiguration extends WebserviceResourceConfiguration {
|
// NOTE: We need different class types here to support Spring configuration for different
|
||||||
|
private static final class LMSAPIClientResourceServerConfiguration extends WebserviceResourceConfiguration {
|
||||||
|
|
||||||
public ActuatorResourceServerConfiguration(
|
public LMSAPIClientResourceServerConfiguration(
|
||||||
final TokenStore tokenStore,
|
final TokenStore tokenStore,
|
||||||
final WebClientDetailsService webServiceClientDetails,
|
final WebClientDetailsService webServiceClientDetails,
|
||||||
final AuthenticationManager authenticationManager,
|
final AuthenticationManager authenticationManager,
|
||||||
final String apiEndpoint,
|
final String apiEndpoint,
|
||||||
final String redirect,
|
final int accessTokenValSec) {
|
||||||
final int adminAccessTokenValSec,
|
|
||||||
final int adminRefreshTokenValSec) {
|
|
||||||
|
|
||||||
super(
|
super(
|
||||||
tokenStore,
|
tokenStore,
|
||||||
webServiceClientDetails,
|
webServiceClientDetails,
|
||||||
authenticationManager,
|
authenticationManager,
|
||||||
new LoginRedirectOnUnauthorized(redirect),
|
(request, response, exception) -> {
|
||||||
ADMIN_API_RESOURCE_ID,
|
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||||
|
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||||
|
log.warn("Unauthorized Request: {}", request, exception);
|
||||||
|
log.info("Redirect to login after unauthorized request");
|
||||||
|
response.getOutputStream().println("{ \"error\": \"" + exception.getMessage() + "\" }");
|
||||||
|
},
|
||||||
|
EXAM_API_RESOURCE_ID,
|
||||||
apiEndpoint,
|
apiEndpoint,
|
||||||
true,
|
true,
|
||||||
4,
|
4,
|
||||||
adminAccessTokenValSec,
|
accessTokenValSec,
|
||||||
adminRefreshTokenValSec);
|
1);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void addConfiguration(
|
|
||||||
final ConfigurerAdapter configurerAdapter,
|
|
||||||
final HttpSecurity http) throws Exception {
|
|
||||||
|
|
||||||
http.antMatcher(configurerAdapter.apiEndpoint + "/**")
|
|
||||||
.authorizeRequests()
|
|
||||||
|
|
||||||
.anyRequest()
|
|
||||||
.hasAuthority(UserRole.SEB_SERVER_ADMIN.name());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019 ETH Zürich, IT Services
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.ethz.seb.sebserver.webservice.weblayer.api;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.Entity;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@WebServiceProfile
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("${sebserver.webservice.lms.api.endpoint}")
|
||||||
|
public class LmsIntegrationController {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(LmsIntegrationController.class);
|
||||||
|
|
||||||
|
private final FullLmsIntegrationService fullLmsIntegrationService;
|
||||||
|
|
||||||
|
public LmsIntegrationController(final FullLmsIntegrationService fullLmsIntegrationService) {
|
||||||
|
this.fullLmsIntegrationService = fullLmsIntegrationService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping(
|
||||||
|
path = API.LMS_FULL_INTEGRATION_REFRESH_TOKEN_ENDPOINT,
|
||||||
|
method = RequestMethod.POST,
|
||||||
|
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
|
||||||
|
public void refreshAccessToken(
|
||||||
|
@RequestParam(name = API.LMS_FULL_INTEGRATION_LMS_UUID, required = true) final String lmsUUID,
|
||||||
|
final HttpServletResponse response) {
|
||||||
|
|
||||||
|
final Result<Void> result = fullLmsIntegrationService.refreshAccessToken(lmsUUID)
|
||||||
|
.onError(e -> log.error("Failed to refresh access token for LMS Setup: {}", lmsUUID, e));
|
||||||
|
|
||||||
|
if (result.hasError()) {
|
||||||
|
response.setStatus(HttpStatus.NOT_FOUND.value());
|
||||||
|
} else {
|
||||||
|
response.setStatus(HttpStatus.OK.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019 ETH Zürich, IT Services
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.ethz.seb.sebserver.webservice.weblayer.oauth;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.WebSecurityConfig;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
@Component
|
||||||
|
public class LmsAPIClientDetails extends BaseClientDetails {
|
||||||
|
|
||||||
|
public LmsAPIClientDetails(
|
||||||
|
@Qualifier(WebSecurityConfig.CLIENT_PASSWORD_ENCODER_BEAN_NAME) final PasswordEncoder clientPasswordEncoder,
|
||||||
|
@Value("${sebserver.webservice.lms.api.clientId}") final String clientId,
|
||||||
|
@Value("${sebserver.webservice.api.admin.clientSecret}") final String clientSecret,
|
||||||
|
@Value("${sebserver.webservice.lms.api.accessTokenValiditySeconds:-1}") final Integer accessTokenValiditySeconds
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
clientId,
|
||||||
|
WebserviceResourceConfiguration.LMS_API_RESOURCE_ID,
|
||||||
|
StringUtils.joinWith(
|
||||||
|
Constants.LIST_SEPARATOR,
|
||||||
|
Constants.OAUTH2_SCOPE_READ,
|
||||||
|
Constants.OAUTH2_SCOPE_WRITE),
|
||||||
|
Constants.OAUTH2_GRANT_TYPE_CLIENT_CREDENTIALS,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
super.setClientSecret(clientPasswordEncoder.encode(clientSecret));
|
||||||
|
super.setAccessTokenValiditySeconds(accessTokenValiditySeconds);
|
||||||
|
}
|
||||||
|
}
|
|
@ -34,13 +34,16 @@ public class WebClientDetailsService implements ClientDetailsService {
|
||||||
|
|
||||||
private final ClientConfigService sebClientConfigService;
|
private final ClientConfigService sebClientConfigService;
|
||||||
private final AdminAPIClientDetails adminClientDetails;
|
private final AdminAPIClientDetails adminClientDetails;
|
||||||
|
private final LmsAPIClientDetails lmsAPIClientDetails;
|
||||||
|
|
||||||
public WebClientDetailsService(
|
public WebClientDetailsService(
|
||||||
final AdminAPIClientDetails adminClientDetails,
|
final AdminAPIClientDetails adminClientDetails,
|
||||||
final ClientConfigService sebClientConfigService) {
|
final ClientConfigService sebClientConfigService,
|
||||||
|
final LmsAPIClientDetails lmsAPIClientDetails) {
|
||||||
|
|
||||||
this.adminClientDetails = adminClientDetails;
|
this.adminClientDetails = adminClientDetails;
|
||||||
this.sebClientConfigService = sebClientConfigService;
|
this.sebClientConfigService = sebClientConfigService;
|
||||||
|
this.lmsAPIClientDetails = lmsAPIClientDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Load a client by the client id. This method must not return null.
|
/** Load a client by the client id. This method must not return null.
|
||||||
|
@ -64,6 +67,10 @@ public class WebClientDetailsService implements ClientDetailsService {
|
||||||
return this.adminClientDetails;
|
return this.adminClientDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (clientId.equals(this.lmsAPIClientDetails.getClientId())) {
|
||||||
|
return this.lmsAPIClientDetails;
|
||||||
|
}
|
||||||
|
|
||||||
return getForExamClientAPI(clientId)
|
return getForExamClientAPI(clientId)
|
||||||
.get(t -> {
|
.get(t -> {
|
||||||
if (log.isDebugEnabled()) {
|
if (log.isDebugEnabled()) {
|
||||||
|
|
|
@ -32,8 +32,12 @@ public abstract class WebserviceResourceConfiguration extends ResourceServerConf
|
||||||
public static final String ADMIN_API_RESOURCE_ID = "seb-server-administration-api";
|
public static final String ADMIN_API_RESOURCE_ID = "seb-server-administration-api";
|
||||||
/** The resource identifier of the Exam API resources */
|
/** The resource identifier of the Exam API resources */
|
||||||
public static final String EXAM_API_RESOURCE_ID = "seb-server-exam-api";
|
public static final String EXAM_API_RESOURCE_ID = "seb-server-exam-api";
|
||||||
|
public static final String LMS_API_RESOURCE_ID = "seb-server-lms-api";
|
||||||
@Value("${sebserver.webservice.api.exam.endpoint.discovery}")
|
@Value("${sebserver.webservice.api.exam.endpoint.discovery}")
|
||||||
private String examAPIDiscoveryEndpoint;
|
private String examAPIDiscoveryEndpoint;
|
||||||
|
@Value("${sebserver.webservice.lms.api.endpoint}")
|
||||||
|
private String lmsAPIEndpoint;
|
||||||
|
|
||||||
|
|
||||||
public WebserviceResourceConfiguration(
|
public WebserviceResourceConfiguration(
|
||||||
final TokenStore tokenStore,
|
final TokenStore tokenStore,
|
||||||
|
@ -87,6 +91,8 @@ public abstract class WebserviceResourceConfiguration extends ResourceServerConf
|
||||||
.antMatchers(configurerAdapter.apiEndpoint + API.INFO_ENDPOINT + API.LOGO_PATH_SEGMENT + "/**").permitAll()
|
.antMatchers(configurerAdapter.apiEndpoint + API.INFO_ENDPOINT + API.LOGO_PATH_SEGMENT + "/**").permitAll()
|
||||||
.antMatchers(configurerAdapter.apiEndpoint + API.INFO_ENDPOINT + API.INFO_INST_PATH_SEGMENT + "/**").permitAll()
|
.antMatchers(configurerAdapter.apiEndpoint + API.INFO_ENDPOINT + API.INFO_INST_PATH_SEGMENT + "/**").permitAll()
|
||||||
.antMatchers(configurerAdapter.apiEndpoint + API.REGISTER_ENDPOINT).permitAll()
|
.antMatchers(configurerAdapter.apiEndpoint + API.REGISTER_ENDPOINT).permitAll()
|
||||||
|
.antMatchers(this.lmsAPIEndpoint + API.LMS_FULL_INTEGRATION_REFRESH_TOKEN_ENDPOINT).permitAll()
|
||||||
|
|
||||||
.and()
|
.and()
|
||||||
.antMatcher(configurerAdapter.apiEndpoint + "/**")
|
.antMatcher(configurerAdapter.apiEndpoint + "/**")
|
||||||
.authorizeRequests()
|
.authorizeRequests()
|
||||||
|
|
|
@ -27,7 +27,7 @@ sebserver.init.database.integrity.try-fix=true
|
||||||
|
|
||||||
# webservice setup configuration
|
# webservice setup configuration
|
||||||
sebserver.init.adminaccount.gen-on-init=false
|
sebserver.init.adminaccount.gen-on-init=false
|
||||||
sebserver.webservice.light.setup=true
|
sebserver.webservice.light.setup=false
|
||||||
sebserver.webservice.distributed=false
|
sebserver.webservice.distributed=false
|
||||||
#sebserver.webservice.master.delay.threshold=10000
|
#sebserver.webservice.master.delay.threshold=10000
|
||||||
sebserver.webservice.http.external.scheme=http
|
sebserver.webservice.http.external.scheme=http
|
||||||
|
|
|
@ -60,6 +60,9 @@ sebserver.webservice.api.admin.request.limit.refill=2
|
||||||
sebserver.webservice.api.admin.create.limit=10
|
sebserver.webservice.api.admin.create.limit=10
|
||||||
sebserver.webservice.api.admin.create.limit.interval.min=3600
|
sebserver.webservice.api.admin.create.limit.interval.min=3600
|
||||||
sebserver.webservice.api.admin.create.limit.refill=10
|
sebserver.webservice.api.admin.create.limit.refill=10
|
||||||
|
|
||||||
|
|
||||||
|
### SEB exam API
|
||||||
sebserver.webservice.api.admin.exam.app.signature.key.enabled=false
|
sebserver.webservice.api.admin.exam.app.signature.key.enabled=false
|
||||||
sebserver.webservice.api.exam.config.init.permittedProcesses=config/initialPermittedProcesses.xml
|
sebserver.webservice.api.exam.config.init.permittedProcesses=config/initialPermittedProcesses.xml
|
||||||
sebserver.webservice.api.exam.config.init.prohibitedProcesses=config/initialProhibitedProcesses.xml
|
sebserver.webservice.api.exam.config.init.prohibitedProcesses=config/initialProhibitedProcesses.xml
|
||||||
|
@ -69,6 +72,17 @@ sebserver.webservice.api.exam.endpoint.v1=${sebserver.webservice.api.exam.endpoi
|
||||||
sebserver.webservice.api.exam.accessTokenValiditySeconds=43200
|
sebserver.webservice.api.exam.accessTokenValiditySeconds=43200
|
||||||
sebserver.webservice.api.exam.enable-indicator-cache=true
|
sebserver.webservice.api.exam.enable-indicator-cache=true
|
||||||
sebserver.webservice.api.pagination.maxPageSize=500
|
sebserver.webservice.api.pagination.maxPageSize=500
|
||||||
|
|
||||||
|
|
||||||
|
sebserver.webservice.proctoring.resetBroadcastOnLeave=true
|
||||||
|
sebserver.webservice.proctoring.zoom.enableWaitingRoom=false
|
||||||
|
sebserver.webservice.proctoring.zoom.sendRejoinForCollectingRoom=false
|
||||||
|
|
||||||
|
### LMS integration API
|
||||||
|
sebserver.webservice.lms.api.endpoint=/lms-api/v1
|
||||||
|
sebserver.webservice.lms.api.clientId=lmsClient
|
||||||
|
sebserver.webservice.lms.api.accessTokenValiditySeconds=-1
|
||||||
|
|
||||||
# comma separated list of known possible OpenEdX API access token request endpoints
|
# comma separated list of known possible OpenEdX API access token request endpoints
|
||||||
sebserver.webservice.lms.openedx.api.token.request.paths=/oauth2/access_token
|
sebserver.webservice.lms.openedx.api.token.request.paths=/oauth2/access_token
|
||||||
sebserver.webservice.lms.moodle.api.token.request.paths=/login/token.php
|
sebserver.webservice.lms.moodle.api.token.request.paths=/login/token.php
|
||||||
|
@ -79,10 +93,6 @@ sebserver.webservice.lms.olat.sendAdditionalAttributesWithRestriction=false
|
||||||
sebserver.webservice.lms.address.alias=
|
sebserver.webservice.lms.address.alias=
|
||||||
sebserver.webservice.lms.datafetch.validity.seconds=600
|
sebserver.webservice.lms.datafetch.validity.seconds=600
|
||||||
|
|
||||||
sebserver.webservice.proctoring.resetBroadcastOnLeave=true
|
|
||||||
sebserver.webservice.proctoring.zoom.enableWaitingRoom=false
|
|
||||||
sebserver.webservice.proctoring.zoom.sendRejoinForCollectingRoom=false
|
|
||||||
|
|
||||||
# Default Ping indicator:
|
# Default Ping indicator:
|
||||||
sebserver.webservice.api.exam.indicator.name=Ping
|
sebserver.webservice.api.exam.indicator.name=Ping
|
||||||
sebserver.webservice.api.exam.indicator.type=LAST_PING
|
sebserver.webservice.api.exam.indicator.type=LAST_PING
|
||||||
|
|
|
@ -33,6 +33,10 @@ sebserver.webservice.api.admin.endpoint=/admin-api
|
||||||
sebserver.webservice.api.admin.accessTokenValiditySeconds=1800
|
sebserver.webservice.api.admin.accessTokenValiditySeconds=1800
|
||||||
sebserver.webservice.api.admin.refreshTokenValiditySeconds=-1
|
sebserver.webservice.api.admin.refreshTokenValiditySeconds=-1
|
||||||
sebserver.webservice.api.exam.endpoint=/exam-api
|
sebserver.webservice.api.exam.endpoint=/exam-api
|
||||||
|
### LMS integration API
|
||||||
|
sebserver.webservice.lms.api.endpoint=/lms-api/v1
|
||||||
|
sebserver.webservice.lms.api.clientId=lmsClient
|
||||||
|
sebserver.webservice.lms.api.accessTokenValiditySeconds=-1
|
||||||
sebserver.webservice.api.exam.endpoint.discovery=${sebserver.webservice.api.exam.endpoint}/discovery
|
sebserver.webservice.api.exam.endpoint.discovery=${sebserver.webservice.api.exam.endpoint}/discovery
|
||||||
sebserver.webservice.api.exam.endpoint.v1=${sebserver.webservice.api.exam.endpoint}/v1
|
sebserver.webservice.api.exam.endpoint.v1=${sebserver.webservice.api.exam.endpoint}/v1
|
||||||
sebserver.webservice.api.redirect.unauthorized=none
|
sebserver.webservice.api.redirect.unauthorized=none
|
||||||
|
|
Loading…
Reference in a new issue