SEBSERV-417 access token for Moodle
This commit is contained in:
		
							parent
							
								
									0f27ef0f38
								
							
						
					
					
						commit
						6e2feafc5a
					
				
					 23 changed files with 241 additions and 86 deletions
				
			
		|  | @ -286,7 +286,7 @@ public class WebserviceInfo { | |||
|     } | ||||
| 
 | ||||
|     public String getOAuthTokenURI() { | ||||
|         return getExternalServerURL() + API.OAUTH_ENDPOINT + API.OAUTH_TOKEN_ENDPOINT; | ||||
|         return getExternalServerURL() + API.OAUTH_TOKEN_ENDPOINT; | ||||
|     } | ||||
| 
 | ||||
|     public boolean isLightSetup() { | ||||
|  |  | |||
|  | @ -53,4 +53,6 @@ public interface LmsSetupDAO extends ActivatableEntityDAO<LmsSetup, LmsSetup>, B | |||
|     Result<LmsSetup> setIntegrationActive(Long lmsSetupId, boolean active); | ||||
| 
 | ||||
|     Result<Collection<Long>> idsOfActiveWithFullIntegration(Long institutionId); | ||||
| 
 | ||||
|     Result<Collection<Long>> allIdsFullIntegration(); | ||||
| } | ||||
|  |  | |||
|  | @ -133,6 +133,27 @@ public class LmsSetupDAOImpl implements LmsSetupDAO { | |||
|                 .execute()); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Transactional(readOnly = true) | ||||
|     public Result<Collection<Long>> allIdsFullIntegration() { | ||||
|         return Result.tryCatch(() -> { | ||||
|             final List<String> types = Arrays.stream(LmsType.values()) | ||||
|                     .filter(type -> type.features.contains(LmsSetup.Features.LMS_FULL_INTEGRATION)) | ||||
|                     .map(LmsType::name) | ||||
|                     .collect(Collectors.toList()); | ||||
| 
 | ||||
|             return this.lmsSetupRecordMapper.selectIdsByExample() | ||||
|                     .where( | ||||
|                             LmsSetupRecordDynamicSqlSupport.active, | ||||
|                             isEqualTo(1)) | ||||
|                     .and( | ||||
|                             lmsType, | ||||
|                             isIn(types)) | ||||
|                     .build() | ||||
|                     .execute(); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Transactional(readOnly = true) | ||||
|     public Result<Collection<LmsSetup>> allMatching( | ||||
|  |  | |||
|  | @ -8,11 +8,20 @@ | |||
| 
 | ||||
| package ch.ethz.seb.sebserver.webservice.servicelayer.lms; | ||||
| 
 | ||||
| import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup; | ||||
| import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult; | ||||
| import ch.ethz.seb.sebserver.gbl.util.Result; | ||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService.IntegrationData; | ||||
| 
 | ||||
| public interface FullLmsIntegrationAPI { | ||||
| 
 | ||||
|     /** Performs a test for the underling {@link LmsSetup } configuration and checks if the | ||||
|      * LMS and the full LMS integration API of the LMS can be accessed or if there are some difficulties, | ||||
|      * missing API functions | ||||
|      * | ||||
|      * @return {@link LmsSetupTestResult } instance with the test result report */ | ||||
|     LmsSetupTestResult testFullIntegrationAPI(); | ||||
| 
 | ||||
|     Result<IntegrationData> applyConnectionDetails(IntegrationData data); | ||||
| 
 | ||||
|     Result<Void> deleteConnectionDetails(); | ||||
|  |  | |||
|  | @ -10,6 +10,8 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl; | |||
| 
 | ||||
| import java.io.OutputStream; | ||||
| import java.nio.charset.StandardCharsets; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Arrays; | ||||
| import java.util.Collection; | ||||
| import java.util.function.Function; | ||||
| import java.util.stream.Collectors; | ||||
|  | @ -61,7 +63,7 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService | |||
|     private final ExamTemplateDAO examTemplateDAO; | ||||
|     private final WebserviceInfo webserviceInfo; | ||||
| 
 | ||||
|     //private final ClientCredentialsResourceDetails resource; | ||||
|     private final ClientCredentialsResourceDetails resource; | ||||
| 
 | ||||
|     private final OAuth2RestTemplate restTemplate; | ||||
| 
 | ||||
|  | @ -83,7 +85,7 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService | |||
|         this.examTemplateDAO = examTemplateDAO; | ||||
|         this.webserviceInfo = webserviceInfo; | ||||
| 
 | ||||
|         final ClientCredentialsResourceDetails resource = new ClientCredentialsResourceDetails(); | ||||
|         resource = new ClientCredentialsResourceDetails(); | ||||
|         resource.setAccessTokenUri(webserviceInfo.getOAuthTokenURI()); | ||||
|         resource.setClientId(clientId); | ||||
|         resource.setClientSecret(clientSecret); | ||||
|  | @ -104,10 +106,18 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService | |||
|     @Override | ||||
|     public void notifyLmsSetupChange(final LmsSetupChangeEvent event) { | ||||
|         final LmsSetup lmsSetup = event.getLmsSetup(); | ||||
|         if (lmsSetup.integrationActive) { | ||||
|         if (!lmsSetup.getLmsType().features.contains(LmsSetup.Features.LMS_FULL_INTEGRATION)) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         if (lmsSetup.active) { | ||||
|             applyFullLmsIntegration(lmsSetup.id) | ||||
|                     .onError(error -> log.warn("Failed to update LMS integration for: {}", lmsSetup, error)) | ||||
|                     .onSuccess( data -> log.debug("Successfully updated LMS integration for: {} data: {}", lmsSetup, data)); | ||||
|                     .onSuccess(data -> log.debug("Successfully updated LMS integration for: {} data: {}", lmsSetup, data)); | ||||
|         } else if (lmsSetup.integrationActive) { | ||||
|             deleteFullLmsIntegration(lmsSetup.id) | ||||
|                     .onError(error -> log.warn("Failed to delete LMS integration for: {}", lmsSetup, error)) | ||||
|                     .onSuccess(data -> log.debug("Successfully deleted LMS integration for: {} data: {}", lmsSetup, data)); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -144,11 +154,16 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService | |||
|                         throw new IllegalStateException("No LMS Setup connectionId available for: " + lmsSetup); | ||||
|                     } | ||||
| 
 | ||||
|                     // reset old token to get actual one | ||||
|                     resource.setScope(Arrays.asList(String.valueOf(lmsSetupId))); | ||||
|                     restTemplate.getOAuth2ClientContext().setAccessToken(null); | ||||
|                     final String accessToken = restTemplate.getAccessToken().getValue(); | ||||
| 
 | ||||
|                     final IntegrationData data = new IntegrationData( | ||||
|                             connectionId, | ||||
|                             lmsSetup.name, | ||||
|                             webserviceInfo.getExternalServerURL(), | ||||
|                             restTemplate.getAccessToken().getValue(), | ||||
|                             accessToken, | ||||
|                             this.getIntegrationTemplates(lmsSetup.institutionId) | ||||
|                     ); | ||||
| 
 | ||||
|  |  | |||
|  | @ -156,10 +156,19 @@ public class LmsAPIServiceImpl implements LmsAPIService { | |||
|             final LmsSetupTestResult lmsSetupTestResult = template.testCourseRestrictionAPI(); | ||||
|             if (!lmsSetupTestResult.isOk()) { | ||||
|                 this.cache.remove(new CacheKey(template.lmsSetup().getModelId(), 0)); | ||||
|             } | ||||
|                 return lmsSetupTestResult; | ||||
|             } | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         if (template.lmsSetup().getLmsType().features.contains(LmsSetup.Features.LMS_FULL_INTEGRATION)) { | ||||
|             final LmsSetupTestResult lmsSetupTestResult = template.testFullIntegrationAPI(); | ||||
|             if (!lmsSetupTestResult.isOk()) { | ||||
|                 this.cache.remove(new CacheKey(template.lmsSetup().getModelId(), 0)); | ||||
|                 return lmsSetupTestResult; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return LmsSetupTestResult.ofOkay(template.lmsSetup().getLmsType()); | ||||
|     } | ||||
| 
 | ||||
|  | @ -184,8 +193,19 @@ public class LmsAPIServiceImpl implements LmsAPIService { | |||
|             return testCourseAccessAPI; | ||||
|         } | ||||
| 
 | ||||
|         if (lmsSetupTemplate.lmsSetup().getLmsType().features.contains(LmsSetup.Features.SEB_RESTRICTION)) { | ||||
|             return lmsSetupTemplate.testCourseRestrictionAPI(); | ||||
|         final LmsType lmsType = lmsSetupTemplate.lmsSetup().getLmsType(); | ||||
|         if (lmsType.features.contains(LmsSetup.Features.SEB_RESTRICTION)) { | ||||
|             final LmsSetupTestResult lmsSetupTestResult = lmsSetupTemplate.testCourseRestrictionAPI(); | ||||
|             if (!lmsSetupTestResult.isOk()) { | ||||
|                 return lmsSetupTestResult; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (lmsType.features.contains(LmsSetup.Features.LMS_FULL_INTEGRATION)) { | ||||
|             final LmsSetupTestResult lmsSetupTestResult = lmsSetupTemplate.testFullIntegrationAPI(); | ||||
|             if (!lmsSetupTestResult.isOk()) { | ||||
|                 return lmsSetupTestResult; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return LmsSetupTestResult.ofOkay(lmsSetupTemplate.lmsSetup().getLmsType()); | ||||
|  |  | |||
|  | @ -397,10 +397,6 @@ public class LmsAPITemplateAdapter implements LmsAPITemplate { | |||
|             return this.sebRestrictionAPI.testCourseRestrictionAPI(); | ||||
|         } | ||||
| 
 | ||||
|         if (log.isDebugEnabled()) { | ||||
|             log.debug("Test course restriction API for LMSSetup: {}", lmsSetup()); | ||||
|         } | ||||
| 
 | ||||
|         return LmsSetupTestResult.ofAPINotSupported(getType()); | ||||
|     } | ||||
| 
 | ||||
|  | @ -476,6 +472,16 @@ public class LmsAPITemplateAdapter implements LmsAPITemplate { | |||
|         return protectedRun; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     @Override | ||||
|     public LmsSetupTestResult testFullIntegrationAPI() { | ||||
|         if (this.lmsIntegrationAPI != null) { | ||||
|             return this.lmsIntegrationAPI.testFullIntegrationAPI(); | ||||
|         } | ||||
| 
 | ||||
|         return LmsSetupTestResult.ofAPINotSupported(getType()); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Result<IntegrationData> applyConnectionDetails(final IntegrationData data) { | ||||
|         if (this.lmsIntegrationAPI == null) { | ||||
|  |  | |||
|  | @ -421,6 +421,11 @@ public class AnsLmsAPITemplate extends AbstractCachedCourseAccess implements Lms | |||
|                 .map(x -> exam); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public LmsSetupTestResult testFullIntegrationAPI() { | ||||
|         return LmsSetupTestResult.ofAPINotSupported(LmsType.ANS_DELFT); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Result<IntegrationData> applyConnectionDetails(final IntegrationData data) { | ||||
|         return Result.ofRuntimeError("Not Supported"); | ||||
|  |  | |||
|  | @ -8,12 +8,18 @@ | |||
| 
 | ||||
| package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.mockup; | ||||
| 
 | ||||
| import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup; | ||||
| import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult; | ||||
| import ch.ethz.seb.sebserver.gbl.util.Result; | ||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationAPI; | ||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService.IntegrationData; | ||||
| 
 | ||||
| public class MockupFullIntegration implements FullLmsIntegrationAPI { | ||||
| 
 | ||||
|     @Override | ||||
|     public LmsSetupTestResult testFullIntegrationAPI() { | ||||
|         return LmsSetupTestResult.ofAPINotSupported(LmsSetup.LmsType.MOODLE_PLUGIN); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Result<IntegrationData> applyConnectionDetails(final IntegrationData data) { | ||||
|  |  | |||
|  | @ -32,6 +32,8 @@ public interface MoodleRestTemplateFactory { | |||
|      * @return Set of known and configured API access token paths */ | ||||
|     Set<String> getKnownTokenAccessPaths(); | ||||
| 
 | ||||
|     Result<MoodleAPIRestTemplate> getRestTemplate(); | ||||
| 
 | ||||
|     /** Creates a MoodleAPIRestTemplate for the bundled LMSSetup of this factory. | ||||
|      * | ||||
|      * @param service The moodle web service name to within requesting an access token for | ||||
|  |  | |||
|  | @ -63,6 +63,8 @@ public class MoodleRestTemplateFactoryImpl implements MoodleRestTemplateFactory | |||
|     public final ClientCredentialService clientCredentialService; | ||||
|     public final Set<String> knownTokenAccessPaths; | ||||
| 
 | ||||
|     private Result<MoodleAPIRestTemplate> activeRestTemplate = Result.ofRuntimeError("Not Initialized"); | ||||
| 
 | ||||
|     public MoodleRestTemplateFactoryImpl( | ||||
|             final JSONMapper jsonMapper, | ||||
|             final APITemplateDataSupplier apiTemplateDataSupplier, | ||||
|  | @ -88,6 +90,11 @@ public class MoodleRestTemplateFactoryImpl implements MoodleRestTemplateFactory | |||
|         return this.knownTokenAccessPaths; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Result<MoodleAPIRestTemplate> getRestTemplate() { | ||||
|         return activeRestTemplate; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public APITemplateDataSupplier getApiTemplateDataSupplier() { | ||||
|         return this.apiTemplateDataSupplier; | ||||
|  | @ -139,13 +146,12 @@ public class MoodleRestTemplateFactoryImpl implements MoodleRestTemplateFactory | |||
|     public Result<MoodleAPIRestTemplate> createRestTemplate(final String service) { | ||||
| 
 | ||||
|         final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup(); | ||||
| 
 | ||||
|         return this.knownTokenAccessPaths | ||||
|         this. activeRestTemplate = this.knownTokenAccessPaths | ||||
|                 .stream() | ||||
|                 .map(path -> this.createRestTemplate(service, path)) | ||||
|                 .map(result -> { | ||||
|                     if (result.hasError()) { | ||||
|                         log.warn("Failed to get access token for LMS: {}({})", | ||||
|                         log.warn("Failed to get access token for LMS: {}({}), error {}", | ||||
|                                 lmsSetup.name, | ||||
|                                 lmsSetup.id, | ||||
|                                 result.getError().getMessage()); | ||||
|  | @ -158,6 +164,10 @@ public class MoodleRestTemplateFactoryImpl implements MoodleRestTemplateFactory | |||
|                         "Failed to gain any access for LMS " + | ||||
|                                 lmsSetup.name + "(" + lmsSetup.id + | ||||
|                                 ") on paths: " + this.knownTokenAccessPaths)); | ||||
| 
 | ||||
|         log.info("Created new MoodleAPIRestTemplate for service: {} factory: {}", service, this.hashCode()); | ||||
| 
 | ||||
|         return activeRestTemplate; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|  | @ -199,6 +209,7 @@ public class MoodleRestTemplateFactoryImpl implements MoodleRestTemplateFactory | |||
|                         ") on path: " + accessTokenPath); | ||||
|             } | ||||
| 
 | ||||
|             activeRestTemplate = Result.of(restTemplate); | ||||
|             return restTemplate; | ||||
|         }); | ||||
|     } | ||||
|  | @ -266,7 +277,7 @@ public class MoodleRestTemplateFactoryImpl implements MoodleRestTemplateFactory | |||
|                         WebserviceInfo.class); | ||||
| 
 | ||||
|                 if (StringUtils.isBlank(webserviceInfo.username) || StringUtils.isBlank(webserviceInfo.userid)) { | ||||
|                     throw new RuntimeException("Invalid WebserviceInfo: " + webserviceInfo); | ||||
|                     throw new RuntimeException("Invalid WebserviceInfo Response"); | ||||
|                 } | ||||
| 
 | ||||
|                 if (functions != null) { | ||||
|  | @ -281,8 +292,10 @@ public class MoodleRestTemplateFactoryImpl implements MoodleRestTemplateFactory | |||
|                 } | ||||
| 
 | ||||
|             } catch (final RuntimeException re) { | ||||
|                 log.warn("Failed to Moodle API access: {}", re.getMessage()); | ||||
|                 throw re; | ||||
|             } catch (final Exception e) { | ||||
|                 log.warn("Failed to Moodle API access: {}", e.getMessage()); | ||||
|                 throw new RuntimeException("Failed to test Moodle rest API: ", e); | ||||
|             } | ||||
|         } | ||||
|  |  | |||
|  | @ -118,7 +118,7 @@ public abstract class MoodleUtils { | |||
|         return idNumber.equals(Constants.EMPTY_NOTE) ? null : idNumber; | ||||
|     } | ||||
| 
 | ||||
|     public static final void logMoodleWarning( | ||||
|     public static void logMoodleWarning( | ||||
|             final Collection<Warning> warnings, | ||||
|             final String lmsSetupName, | ||||
|             final String function) { | ||||
|  | @ -148,7 +148,7 @@ public abstract class MoodleUtils { | |||
|     private static final Pattern ACCESS_DENIED_PATTERN_2 = | ||||
|             Pattern.compile(Pattern.quote("access denied"), Pattern.CASE_INSENSITIVE); | ||||
| 
 | ||||
|     public static final boolean checkAccessDeniedError(final String courseKeyPageJSON) { | ||||
|     public static boolean checkAccessDeniedError(final String courseKeyPageJSON) { | ||||
|         return ACCESS_DENIED_PATTERN_1 | ||||
|                 .matcher(courseKeyPageJSON) | ||||
|                 .find() || | ||||
|  |  | |||
|  | @ -20,6 +20,7 @@ import java.util.function.Function; | |||
| import java.util.stream.Collectors; | ||||
| import java.util.stream.Stream; | ||||
| 
 | ||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.plugin.MooldePluginLmsAPITemplateFactory; | ||||
| import org.apache.commons.lang3.BooleanUtils; | ||||
| import org.apache.commons.lang3.StringUtils; | ||||
| import org.slf4j.Logger; | ||||
|  | @ -103,7 +104,6 @@ public class MoodleCourseAccess implements CourseAccessAPI { | |||
|     private final int pageSize; | ||||
|     private final int maxSize; | ||||
| 
 | ||||
|     private MoodleAPIRestTemplate restTemplate; | ||||
| 
 | ||||
|     public MoodleCourseAccess( | ||||
|             final JSONMapper jsonMapper, | ||||
|  | @ -583,17 +583,12 @@ public class MoodleCourseAccess implements CourseAccessAPI { | |||
|     } | ||||
| 
 | ||||
|     private Result<MoodleAPIRestTemplate> getRestTemplate() { | ||||
|         if (this.restTemplate == null) { | ||||
|             final Result<MoodleAPIRestTemplate> templateRequest = this.restTemplateFactory | ||||
|                     .createRestTemplate(MoodleLmsAPITemplateFactory.MOODLE_MOBILE_APP_SERVICE); | ||||
|             if (templateRequest.hasError()) { | ||||
|                 return templateRequest; | ||||
|             } else { | ||||
|                 this.restTemplate = templateRequest.get(); | ||||
|             } | ||||
|         final Result<MoodleAPIRestTemplate> result = this.restTemplateFactory.getRestTemplate(); | ||||
|         if (!result.hasError()) { | ||||
|             return result; | ||||
|         } | ||||
| 
 | ||||
|         return Result.of(this.restTemplate); | ||||
|         return this.restTemplateFactory.createRestTemplate(MoodleLmsAPITemplateFactory.MOODLE_MOBILE_APP_SERVICE); | ||||
|     } | ||||
| 
 | ||||
|     private Collection<CourseData> getCoursesPage( | ||||
|  |  | |||
|  | @ -98,8 +98,6 @@ public class MoodlePluginCourseAccess extends AbstractCachedCourseAccess impleme | |||
|     private final int cutoffTimeOffset; | ||||
|     private final boolean applyNameCriteria; | ||||
| 
 | ||||
|     private MoodleAPIRestTemplate restTemplate; | ||||
| 
 | ||||
|     public MoodlePluginCourseAccess( | ||||
|             final JSONMapper jsonMapper, | ||||
|             final AsyncService asyncService, | ||||
|  | @ -595,19 +593,12 @@ public class MoodlePluginCourseAccess extends AbstractCachedCourseAccess impleme | |||
|     } | ||||
| 
 | ||||
|     private Result<MoodleAPIRestTemplate> getRestTemplate() { | ||||
| 
 | ||||
|         if (this.restTemplate == null) { | ||||
| 
 | ||||
|             final Result<MoodleAPIRestTemplate> templateRequest = this.restTemplateFactory | ||||
|                     .createRestTemplate(MooldePluginLmsAPITemplateFactory.SEB_SERVER_SERVICE_NAME); | ||||
|             if (templateRequest.hasError()) { | ||||
|                 return templateRequest; | ||||
|             } else { | ||||
|                 this.restTemplate = templateRequest.get(); | ||||
|             } | ||||
|         final Result<MoodleAPIRestTemplate> result = this.restTemplateFactory.getRestTemplate(); | ||||
|         if (!result.hasError()) { | ||||
|             return result; | ||||
|         } | ||||
| 
 | ||||
|         return Result.of(this.restTemplate); | ||||
|         return this.restTemplateFactory.createRestTemplate(MooldePluginLmsAPITemplateFactory.SEB_SERVER_SERVICE_NAME); | ||||
|     } | ||||
| 
 | ||||
|     protected String toTestString() { | ||||
|  | @ -619,7 +610,7 @@ public class MoodlePluginCourseAccess extends AbstractCachedCourseAccess impleme | |||
|         builder.append(", cutoffTimeOffset="); | ||||
|         builder.append(this.cutoffTimeOffset); | ||||
|         builder.append(", restTemplate="); | ||||
|         builder.append(this.restTemplate); | ||||
|         builder.append(this.getRestTemplate().getOr(null)); | ||||
|         builder.append("]"); | ||||
|         return builder.toString(); | ||||
|     } | ||||
|  |  | |||
|  | @ -55,8 +55,6 @@ public class MoodlePluginCourseRestriction implements SEBRestrictionAPI { | |||
|     private final MoodleRestTemplateFactory restTemplateFactory; | ||||
|     private final ExamConfigurationValueService examConfigurationValueService; | ||||
| 
 | ||||
|     private MoodleAPIRestTemplate restTemplate; | ||||
| 
 | ||||
|     public MoodlePluginCourseRestriction( | ||||
|             final JSONMapper jsonMapper, | ||||
|             final MoodleRestTemplateFactory restTemplateFactory, | ||||
|  | @ -90,7 +88,6 @@ public class MoodlePluginCourseRestriction implements SEBRestrictionAPI { | |||
|                     RESTRICTION_SET_FUNCTION_NAME); | ||||
| 
 | ||||
|         } catch (final RuntimeException e) { | ||||
|             log.error("Failed to access Moodle course API: ", e); | ||||
|             return LmsSetupTestResult.ofQuizAccessAPIError(LmsType.MOODLE_PLUGIN, e.getMessage()); | ||||
|         } | ||||
| 
 | ||||
|  | @ -285,24 +282,18 @@ public class MoodlePluginCourseRestriction implements SEBRestrictionAPI { | |||
|     } | ||||
| 
 | ||||
|     private Result<MoodleAPIRestTemplate> getRestTemplate() { | ||||
|         if (this.restTemplate == null) { | ||||
| 
 | ||||
|             final Result<MoodleAPIRestTemplate> templateRequest = this.restTemplateFactory | ||||
|                     .createRestTemplate(MooldePluginLmsAPITemplateFactory.SEB_SERVER_SERVICE_NAME); | ||||
|             if (templateRequest.hasError()) { | ||||
|                 return templateRequest; | ||||
|             } else { | ||||
|                 this.restTemplate = templateRequest.get(); | ||||
|             } | ||||
|         final Result<MoodleAPIRestTemplate> result = this.restTemplateFactory.getRestTemplate(); | ||||
|         if (!result.hasError()) { | ||||
|             return result; | ||||
|         } | ||||
| 
 | ||||
|         return Result.of(this.restTemplate); | ||||
|         return this.restTemplateFactory.createRestTemplate(MooldePluginLmsAPITemplateFactory.SEB_SERVER_SERVICE_NAME); | ||||
|     } | ||||
| 
 | ||||
|     public String toTestString() { | ||||
|         final StringBuilder builder = new StringBuilder(); | ||||
|         builder.append("MoodlePluginCourseRestriction [restTemplate="); | ||||
|         builder.append(this.restTemplate); | ||||
|         builder.append(this.getRestTemplate().getOr(null)); | ||||
|         builder.append("]"); | ||||
|         return builder.toString(); | ||||
|     } | ||||
|  |  | |||
|  | @ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.plugin; | |||
| import ch.ethz.seb.sebserver.gbl.api.APIMessage; | ||||
| import ch.ethz.seb.sebserver.gbl.api.JSONMapper; | ||||
| import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup; | ||||
| import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult; | ||||
| import ch.ethz.seb.sebserver.gbl.util.Result; | ||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService.IntegrationData; | ||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationAPI; | ||||
|  | @ -32,8 +33,6 @@ public class MoodlePluginFullIntegration implements FullLmsIntegrationAPI { | |||
|     private final JSONMapper jsonMapper; | ||||
|     private final MoodleRestTemplateFactory restTemplateFactory; | ||||
| 
 | ||||
|     private MoodleAPIRestTemplate restTemplate; | ||||
| 
 | ||||
|     public MoodlePluginFullIntegration( | ||||
|             final JSONMapper jsonMapper, | ||||
|             final MoodleRestTemplateFactory restTemplateFactory) { | ||||
|  | @ -42,6 +41,34 @@ public class MoodlePluginFullIntegration implements FullLmsIntegrationAPI { | |||
|         this.restTemplateFactory = restTemplateFactory; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public LmsSetupTestResult testFullIntegrationAPI() { | ||||
|         final LmsSetupTestResult attributesCheck = this.restTemplateFactory.test(); | ||||
|         if (!attributesCheck.isOk()) { | ||||
|             return attributesCheck; | ||||
|         } | ||||
| 
 | ||||
|         final Result<MoodleAPIRestTemplate> restTemplateRequest = getRestTemplate(); | ||||
|         if (restTemplateRequest.hasError()) { | ||||
|             final String message = "Failed to gain access token from Moodle Rest API:\n tried token endpoints: " + | ||||
|                     this.restTemplateFactory.getKnownTokenAccessPaths(); | ||||
|             log.error(message + " cause: {}", restTemplateRequest.getError().getMessage()); | ||||
|             return LmsSetupTestResult.ofTokenRequestError(LmsSetup.LmsType.MOODLE_PLUGIN, message); | ||||
|         } | ||||
| 
 | ||||
|         try { | ||||
|             final MoodleAPIRestTemplate restTemplate = restTemplateRequest.get(); | ||||
|             restTemplate.testAPIConnection( | ||||
|                     FUNCTION_NAME_SEBSERVER_CONNECTION, | ||||
|                     FUNCTION_NAME_SEBSERVER_CONNECTION_DELETE); | ||||
| 
 | ||||
|         } catch (final RuntimeException e) { | ||||
|             return LmsSetupTestResult.ofQuizAccessAPIError(LmsSetup.LmsType.MOODLE_PLUGIN, e.getMessage()); | ||||
|         } | ||||
| 
 | ||||
|         return LmsSetupTestResult.ofOkay(LmsSetup.LmsType.MOODLE_PLUGIN); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Result<IntegrationData> applyConnectionDetails(final IntegrationData data) { | ||||
|         return Result.tryCatch(() -> { | ||||
|  | @ -116,17 +143,11 @@ public class MoodlePluginFullIntegration implements FullLmsIntegrationAPI { | |||
| 
 | ||||
|     private Result<MoodleAPIRestTemplate> getRestTemplate() { | ||||
| 
 | ||||
|         if (this.restTemplate == null) { | ||||
| 
 | ||||
|             final Result<MoodleAPIRestTemplate> templateRequest = this.restTemplateFactory | ||||
|                     .createRestTemplate(MooldePluginLmsAPITemplateFactory.SEB_SERVER_SERVICE_NAME); | ||||
|             if (templateRequest.hasError()) { | ||||
|                 return templateRequest; | ||||
|             } else { | ||||
|                 this.restTemplate = templateRequest.get(); | ||||
|             } | ||||
|         final Result<MoodleAPIRestTemplate> result = this.restTemplateFactory.getRestTemplate(); | ||||
|         if (!result.hasError()) { | ||||
|             return result; | ||||
|         } | ||||
| 
 | ||||
|         return Result.of(this.restTemplate); | ||||
|         return this.restTemplateFactory.createRestTemplate(MooldePluginLmsAPITemplateFactory.SEB_SERVER_SERVICE_NAME); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -408,6 +408,11 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm | |||
|                 .map(x -> exam); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public LmsSetupTestResult testFullIntegrationAPI() { | ||||
|         return LmsSetupTestResult.ofAPINotSupported(LmsType.OPEN_OLAT); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Result<IntegrationData> applyConnectionDetails(final IntegrationData data) { | ||||
|         return Result.ofRuntimeError("Not Supported"); | ||||
|  |  | |||
|  | @ -10,35 +10,65 @@ package ch.ethz.seb.sebserver.webservice.weblayer.oauth; | |||
| 
 | ||||
| import ch.ethz.seb.sebserver.WebSecurityConfig; | ||||
| import ch.ethz.seb.sebserver.gbl.Constants; | ||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.dao.LmsSetupDAO; | ||||
| 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.ClientDetails; | ||||
| import org.springframework.security.oauth2.provider.client.BaseClientDetails; | ||||
| import org.springframework.stereotype.Component; | ||||
| 
 | ||||
| @Lazy | ||||
| @Component | ||||
| public class LmsAPIClientDetails extends BaseClientDetails  { | ||||
| public class LmsAPIClientDetails  { | ||||
| 
 | ||||
|     private final PasswordEncoder clientPasswordEncoder; | ||||
|     private final String clientId; | ||||
|     private final String clientSecret; | ||||
|     private final Integer accessTokenValiditySeconds; | ||||
|     private final LmsSetupDAO lmsSetupDAO; | ||||
| 
 | ||||
|     public LmsAPIClientDetails( | ||||
|             @Qualifier(WebSecurityConfig.CLIENT_PASSWORD_ENCODER_BEAN_NAME) final PasswordEncoder clientPasswordEncoder, | ||||
|             final LmsSetupDAO lmsSetupDAO, | ||||
|             @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( | ||||
|         this.clientPasswordEncoder = clientPasswordEncoder; | ||||
|         this.lmsSetupDAO = lmsSetupDAO; | ||||
|         this.clientId = clientId; | ||||
|         this.clientSecret = clientSecret; | ||||
|         this.accessTokenValiditySeconds = accessTokenValiditySeconds; | ||||
|     } | ||||
| 
 | ||||
|     public String getClientId() { | ||||
|         return clientId; | ||||
|     } | ||||
| 
 | ||||
|     // It seems this get called multiple times per token request | ||||
|     // TODO do we need very short time caching here? | ||||
| 
 | ||||
|     public ClientDetails getClientDetails() { | ||||
| 
 | ||||
|         final String joinIds = StringUtils.join( | ||||
|                 lmsSetupDAO.allIdsFullIntegration().getOrThrow(), | ||||
|                 Constants.LIST_SEPARATOR | ||||
|         ); | ||||
| 
 | ||||
|         final BaseClientDetails clientDetails = new BaseClientDetails( | ||||
|                 clientId, | ||||
|                 WebserviceResourceConfiguration.LMS_API_RESOURCE_ID, | ||||
|                 StringUtils.joinWith( | ||||
|                         Constants.LIST_SEPARATOR, | ||||
|                         Constants.OAUTH2_SCOPE_READ, | ||||
|                         Constants.OAUTH2_SCOPE_WRITE), | ||||
|                 joinIds, | ||||
|                 Constants.OAUTH2_GRANT_TYPE_CLIENT_CREDENTIALS, | ||||
|                 null | ||||
|         ); | ||||
|         super.setClientSecret(clientPasswordEncoder.encode(clientSecret)); | ||||
|         super.setAccessTokenValiditySeconds(accessTokenValiditySeconds); | ||||
|         clientDetails.setClientSecret(clientPasswordEncoder.encode(clientSecret)); | ||||
|         clientDetails.setAccessTokenValiditySeconds(accessTokenValiditySeconds); | ||||
|         return clientDetails; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -68,7 +68,7 @@ public class WebClientDetailsService implements ClientDetailsService { | |||
|         } | ||||
| 
 | ||||
|         if (clientId.equals(this.lmsAPIClientDetails.getClientId())) { | ||||
|             return this.lmsAPIClientDetails; | ||||
|             return this.lmsAPIClientDetails.getClientDetails(); | ||||
|         } | ||||
| 
 | ||||
|         return getForExamClientAPI(clientId) | ||||
|  |  | |||
|  | @ -46,6 +46,8 @@ public class MoodleMockupRestTemplateFactory implements MoodleRestTemplateFactor | |||
| 
 | ||||
|     private final APITemplateDataSupplier apiTemplateDataSupplier; | ||||
| 
 | ||||
|     private Result<MoodleAPIRestTemplate> template = null; | ||||
| 
 | ||||
|     public MoodleMockupRestTemplateFactory(final APITemplateDataSupplier apiTemplateDataSupplier) { | ||||
|         this.apiTemplateDataSupplier = apiTemplateDataSupplier; | ||||
|     } | ||||
|  | @ -67,14 +69,25 @@ public class MoodleMockupRestTemplateFactory implements MoodleRestTemplateFactor | |||
|         return paths; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Result<MoodleAPIRestTemplate> getRestTemplate() { | ||||
|         return createRestTemplate(""); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Result<MoodleAPIRestTemplate> createRestTemplate(final String service) { | ||||
|         return Result.of(new MockupMoodleRestTemplate(this.apiTemplateDataSupplier.getLmsSetup().lmsApiUrl)); | ||||
|         if (template == null) { | ||||
|             template = Result.of(new MockupMoodleRestTemplate(this.apiTemplateDataSupplier.getLmsSetup().lmsApiUrl)); | ||||
|         } | ||||
|         return template; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Result<MoodleAPIRestTemplate> createRestTemplate(final String service, final String accessTokenPath) { | ||||
|         return Result.of(new MockupMoodleRestTemplate(this.apiTemplateDataSupplier.getLmsSetup().lmsApiUrl)); | ||||
|         if (template == null) { | ||||
|             template = Result.of(new MockupMoodleRestTemplate(this.apiTemplateDataSupplier.getLmsSetup().lmsApiUrl)); | ||||
|         } | ||||
|         return template; | ||||
|     } | ||||
| 
 | ||||
|     public static final class MockupMoodleRestTemplate implements MoodleAPIRestTemplate { | ||||
|  |  | |||
|  | @ -47,6 +47,8 @@ public class MoodleCourseAccessTest { | |||
|         final MoodleAPIRestTemplateImpl moodleAPIRestTemplate = mock(MoodleAPIRestTemplateImpl.class); | ||||
|         when(moodleRestTemplateFactory.createRestTemplate(Mockito.anyString())) | ||||
|                 .thenReturn(Result.of(moodleAPIRestTemplate)); | ||||
|         when(moodleRestTemplateFactory.getRestTemplate()) | ||||
|                 .thenReturn(Result.of(moodleAPIRestTemplate)); | ||||
|         when(moodleAPIRestTemplate.callMoodleAPIFunction( | ||||
|                 anyString(), | ||||
|                 any())).thenReturn("[\r\n" + | ||||
|  | @ -122,6 +124,8 @@ public class MoodleCourseAccessTest { | |||
|         final MoodleRestTemplateFactoryImpl moodleRestTemplateFactory = mock(MoodleRestTemplateFactoryImpl.class); | ||||
|         when(moodleRestTemplateFactory.createRestTemplate(Mockito.anyString())) | ||||
|                 .thenReturn(Result.ofRuntimeError("Error1")); | ||||
|         when(moodleRestTemplateFactory.getRestTemplate()) | ||||
|                 .thenReturn(Result.ofRuntimeError("Error1")); | ||||
|         when(moodleRestTemplateFactory.test()).thenReturn(LmsSetupTestResult.ofOkay(LmsType.MOODLE)); | ||||
| 
 | ||||
|         final MoodleCourseAccess moodleCourseAccess = new MoodleCourseAccess( | ||||
|  | @ -143,6 +147,8 @@ public class MoodleCourseAccessTest { | |||
|         final MoodleAPIRestTemplateImpl moodleAPIRestTemplate = mock(MoodleAPIRestTemplateImpl.class); | ||||
|         when(moodleRestTemplateFactory.createRestTemplate(Mockito.anyString())) | ||||
|                 .thenReturn(Result.of(moodleAPIRestTemplate)); | ||||
|         when(moodleRestTemplateFactory.getRestTemplate()) | ||||
|                 .thenReturn(Result.of(moodleAPIRestTemplate)); | ||||
|         doThrow(RuntimeException.class).when(moodleAPIRestTemplate).testAPIConnection(any()); | ||||
|         when(moodleRestTemplateFactory.test()).thenReturn(LmsSetupTestResult.ofOkay(LmsType.MOODLE)); | ||||
| 
 | ||||
|  | @ -165,6 +171,8 @@ public class MoodleCourseAccessTest { | |||
|         final MoodleAPIRestTemplateImpl moodleAPIRestTemplate = mock(MoodleAPIRestTemplateImpl.class); | ||||
|         when(moodleRestTemplateFactory.createRestTemplate(Mockito.anyString())) | ||||
|                 .thenReturn(Result.of(moodleAPIRestTemplate)); | ||||
|         when(moodleRestTemplateFactory.getRestTemplate()) | ||||
|                 .thenReturn(Result.of(moodleAPIRestTemplate)); | ||||
|         when(moodleRestTemplateFactory.test()).thenReturn(LmsSetupTestResult.ofOkay(LmsType.MOODLE)); | ||||
| 
 | ||||
|         final MoodleCourseAccess moodleCourseAccess = new MoodleCourseAccess( | ||||
|  |  | |||
|  | @ -47,11 +47,13 @@ public class MoodlePluginCourseAccessTest { | |||
|     public void testSetup() { | ||||
|         final MoodlePluginCourseAccess candidate = crateMockup(); | ||||
| 
 | ||||
|         assertEquals("MoodlePluginCourseAccess [" | ||||
|                 + "pageSize=500, " | ||||
|                 + "maxSize=10000, " | ||||
|                 + "cutoffTimeOffset=3, " | ||||
|                 + "restTemplate=null]", candidate.toTestString()); | ||||
|         assertEquals( | ||||
|                 "MoodlePluginCourseAccess [" + | ||||
|                 "pageSize=500, " + | ||||
|                 "maxSize=10000, " + | ||||
|                 "cutoffTimeOffset=3, " + | ||||
|                 "restTemplate=MockupMoodleRestTemplate [accessToken=MockupMoodleRestTemplate-Test-Token, url=https://test.org/, testLog=[], callLog=[]]]", | ||||
|                 candidate.toTestString()); | ||||
| 
 | ||||
|         final LmsSetupTestResult testCourseAccessAPI = candidate.testCourseAccessAPI(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -36,7 +36,7 @@ public class MoodlePluginCourseRestrictionTest { | |||
| 
 | ||||
|         final MoodlePluginCourseRestriction candidate = crateMockup(); | ||||
| 
 | ||||
|         assertEquals("MoodlePluginCourseRestriction [restTemplate=null]", candidate.toTestString()); | ||||
|         assertEquals("MoodlePluginCourseRestriction [restTemplate=MockupMoodleRestTemplate [accessToken=MockupMoodleRestTemplate-Test-Token, url=https://test.org/, testLog=[], callLog=[]]]", candidate.toTestString()); | ||||
| 
 | ||||
|         final LmsSetupTestResult testCourseRestrictionAPI = candidate.testCourseRestrictionAPI(); | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 anhefti
						anhefti