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…
	
	Add table
		
		Reference in a new issue
	
	 anhefti
						anhefti