minor improvements, re-use OlatLmsRestTemplate with existing token

This commit is contained in:
Carol Alexandru 2021-07-30 13:23:25 +02:00
parent ae5149226a
commit 59da4bcf4e
3 changed files with 80 additions and 73 deletions

View file

@ -75,6 +75,8 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm
private final APITemplateDataSupplier apiTemplateDataSupplier; private final APITemplateDataSupplier apiTemplateDataSupplier;
private final Long lmsSetupId; private final Long lmsSetupId;
private OlatLmsRestTemplate cachedRestTemplate;
protected OlatLmsAPITemplate( protected OlatLmsAPITemplate(
final ClientHttpRequestFactoryService clientHttpRequestFactoryService, final ClientHttpRequestFactoryService clientHttpRequestFactoryService,
final ClientCredentialService clientCredentialService, final ClientCredentialService clientCredentialService,
@ -112,9 +114,8 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm
if (testLmsSetupSettings.hasAnyError()) { if (testLmsSetupSettings.hasAnyError()) {
return testLmsSetupSettings; return testLmsSetupSettings;
} }
// TODO: improve error handling
try { try {
final RestTemplate restTemplate = this.getRestTemplate().get(); this.getRestTemplate().get();
} }
catch (Exception e) { catch (Exception e) {
return LmsSetupTestResult.ofQuizAccessAPIError(LmsType.OPEN_OLAT, "Unspecific error connecting to OLAT API"); return LmsSetupTestResult.ofQuizAccessAPIError(LmsType.OPEN_OLAT, "Unspecific error connecting to OLAT API");
@ -124,20 +125,7 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm
@Override @Override
public LmsSetupTestResult testCourseRestrictionAPI() { public LmsSetupTestResult testCourseRestrictionAPI() {
// TODO: Any reason to implement a separate check or is this good enough?
return testCourseAccessAPI(); return testCourseAccessAPI();
/*final LmsSetupTestResult testLmsSetupSettings = testLmsSetupSettings();
if (testLmsSetupSettings.hasAnyError()) {
return testLmsSetupSettings;
}
if (LmsType.OPEN_OLAT.features.contains(Features.SEB_RESTRICTION)) {
}
return LmsSetupTestResult.ofOkay(LmsType.OPEN_OLAT);
*/
} }
private LmsSetupTestResult testLmsSetupSettings() { private LmsSetupTestResult testLmsSetupSettings() {
@ -233,10 +221,9 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm
}; };
} }
private String examUrl(long olatTestId) { private String examUrl(long olatRepositoryId) {
final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup(); final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup();
// TODO: at the moment, we don't know olatTestId because we get the assessment mode id (a.key), not the test id. return lmsSetup.lmsApiUrl + "/auth/RepositoryEntry/" + olatRepositoryId;
return lmsSetup.lmsApiUrl + "/auth/RepositoryEntry/" + olatTestId;
} }
private List<QuizData> collectAllQuizzes(final OlatLmsRestTemplate restTemplate, final FilterMap filterMap) { private List<QuizData> collectAllQuizzes(final OlatLmsRestTemplate restTemplate, final FilterMap filterMap) {
@ -251,8 +238,9 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm
final List<AssessmentData> as = this.apiGetList(restTemplate, url, new ParameterizedTypeReference<List<AssessmentData>>(){}); final List<AssessmentData> as = this.apiGetList(restTemplate, url, new ParameterizedTypeReference<List<AssessmentData>>(){});
return as.stream() return as.stream()
.map(a -> new QuizData( .map(a -> {
String.format("%d", a.assessmentModeKey), return new QuizData(
String.format("%d", a.key),
lmsSetup.getInstitutionId(), lmsSetup.getInstitutionId(),
lmsSetup.id, lmsSetup.id,
lmsSetup.getLmsType(), lmsSetup.getLmsType(),
@ -261,7 +249,7 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm
Utils.toDateTimeUTC(a.dateFrom), Utils.toDateTimeUTC(a.dateFrom),
Utils.toDateTimeUTC(a.dateTo), Utils.toDateTimeUTC(a.dateTo),
examUrl(a.repositoryEntryKey), examUrl(a.repositoryEntryKey),
new HashMap<String, String>())) new HashMap<String, String>());})
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
@ -283,7 +271,7 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm
final String url = String.format("/restapi/assessment_modes/%s", id); final String url = String.format("/restapi/assessment_modes/%s", id);
final AssessmentData a = this.apiGet(restTemplate, url, AssessmentData.class); final AssessmentData a = this.apiGet(restTemplate, url, AssessmentData.class);
return new QuizData( return new QuizData(
String.format("%d", a.assessmentModeKey), String.format("%d", a.key),
lmsSetup.getInstitutionId(), lmsSetup.getInstitutionId(),
lmsSetup.id, lmsSetup.id,
lmsSetup.getLmsType(), lmsSetup.getLmsType(),
@ -303,8 +291,7 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm
String.valueOf(u.key), String.valueOf(u.key),
u.lastName + ", " + u.firstName, u.lastName + ", " + u.firstName,
u.username, u.username,
// TODO: other placeholder value? null? "OLAT API does not provide email addresses",
"OLAT does not provide email addresses",
attrs); attrs);
} }
@ -348,18 +335,16 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm
@Override @Override
public Result<SEBRestriction> getSEBClientRestriction(final Exam exam) { public Result<SEBRestriction> getSEBClientRestriction(final Exam exam) {
return Result.of(getRestTemplate() return getRestTemplate()
.map(t -> this.getRestrictionForAssignmentId(t, exam.externalId)) .map(t -> this.getRestrictionForAssignmentId(t, exam.externalId));
.getOrThrow());
} }
@Override @Override
public Result<SEBRestriction> applySEBClientRestriction( public Result<SEBRestriction> applySEBClientRestriction(
final String externalExamId, final String externalExamId,
final SEBRestriction sebRestrictionData) { final SEBRestriction sebRestrictionData) {
return Result.of(getRestTemplate() return getRestTemplate()
.map(t -> this.setRestrictionForAssignmentId(t, externalExamId, sebRestrictionData)) .map(t -> this.setRestrictionForAssignmentId(t, externalExamId, sebRestrictionData));
.getOrThrow());
} }
@Override @Override
@ -367,10 +352,9 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm
@SuppressWarnings("unused") @SuppressWarnings("unused")
final String quizId = exam.externalId; final String quizId = exam.externalId;
return Result.of(getRestTemplate() return getRestTemplate()
.map(t -> this.deleteRestrictionForAssignmentId(t, exam.externalId)) .map(t -> this.deleteRestrictionForAssignmentId(t, exam.externalId))
.map(x -> exam) .map(x -> exam);
.getOrThrow());
} }
private <T> T apiGet(final RestTemplate restTemplate, String url, Class<T> type) { private <T> T apiGet(final RestTemplate restTemplate, String url, Class<T> type) {
@ -417,29 +401,33 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm
} }
private Result<OlatLmsRestTemplate> getRestTemplate() { private Result<OlatLmsRestTemplate> getRestTemplate() {
// TODO: cache/reuse authenticated template for more than 1 request? return Result.tryCatch(() -> {
final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup(); if (this.cachedRestTemplate != null) { return this.cachedRestTemplate; }
final ClientCredentials credentials = this.apiTemplateDataSupplier.getLmsClientCredentials(); final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup();
final ProxyData proxyData = this.apiTemplateDataSupplier.getProxyData(); final ClientCredentials credentials = this.apiTemplateDataSupplier.getLmsClientCredentials();
final ProxyData proxyData = this.apiTemplateDataSupplier.getProxyData();
final CharSequence plainClientId = credentials.clientId; final CharSequence plainClientId = credentials.clientId;
final CharSequence plainClientSecret = this.clientCredentialService final CharSequence plainClientSecret = this.clientCredentialService
.getPlainClientSecret(credentials) .getPlainClientSecret(credentials)
.getOrThrow(); .getOrThrow();
final ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails(); final ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails();
details.setAccessTokenUri(lmsSetup.lmsApiUrl + "/restapi/auth/"); details.setAccessTokenUri(lmsSetup.lmsApiUrl + "/restapi/auth/");
details.setClientId(plainClientId.toString()); details.setClientId(plainClientId.toString());
details.setClientSecret(plainClientSecret.toString()); details.setClientSecret(plainClientSecret.toString());
final ClientHttpRequestFactory clientHttpRequestFactory = this.clientHttpRequestFactoryService final ClientHttpRequestFactory clientHttpRequestFactory = this.clientHttpRequestFactoryService
.getClientHttpRequestFactory(proxyData) .getClientHttpRequestFactory(proxyData)
.getOrThrow(); .getOrThrow();
final OlatLmsRestTemplate template = new OlatLmsRestTemplate(details); final OlatLmsRestTemplate template = new OlatLmsRestTemplate(details);
template.setRequestFactory(clientHttpRequestFactory); template.setRequestFactory(clientHttpRequestFactory);
return Result.of(template); this.cachedRestTemplate = template;
return this.cachedRestTemplate;
});
} }
} }

View file

@ -23,12 +23,12 @@ public final class OlatLmsData {
"dateFrom": 1624420800000, "dateFrom": 1624420800000,
"dateTo": 1624658400000, "dateTo": 1624658400000,
"description": "", "description": "",
"assessmentModeKey": 6356992, "key": 6356992,
repositoryEntryKey: 462324, repositoryEntryKey: 462324,
"name": "SEB test" "name": "SEB test"
} }
*/ */
public long assessmentModeKey; public long key;
public long repositoryEntryKey; public long repositoryEntryKey;
public String name; public String name;
public String description; public String description;

View file

@ -16,6 +16,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails; import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;
import org.springframework.http.HttpEntity; import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpRequest; import org.springframework.http.HttpRequest;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
@ -25,35 +26,53 @@ import org.springframework.http.client.ClientHttpResponse;
public class OlatLmsRestTemplate extends RestTemplate { public class OlatLmsRestTemplate extends RestTemplate {
private static final Logger log = LoggerFactory.getLogger(OlatLmsRestTemplate.class); private static final Logger log = LoggerFactory.getLogger(OlatLmsRestTemplate.class);
public String token; public String token;
private ClientCredentialsResourceDetails details;
public OlatLmsRestTemplate(ClientCredentialsResourceDetails details) { public OlatLmsRestTemplate(ClientCredentialsResourceDetails details) {
super(); super();
this.details = details;
// Authenticate with OLAT and store the received X-OLAT-TOKEN authenticate();
final String authUrl = String.format("%s%s?password=%s",
details.getAccessTokenUri(),
details.getClientId(),
details.getClientSecret());
final HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.set("accept", "application/json");
ResponseEntity<String> response = this.getForEntity(authUrl, String.class);
HttpHeaders responseHeaders = response.getHeaders();
log.debug("OLAT Auth Response Headers: {}", responseHeaders);
token = responseHeaders.getFirst("X-OLAT-TOKEN");
// Add X-OLAT-TOKEN request header to every request done using this RestTemplate // Add X-OLAT-TOKEN request header to every request done using this RestTemplate
this.getInterceptors().add(new ClientHttpRequestInterceptor(){ this.getInterceptors().add(new ClientHttpRequestInterceptor(){
@Override @Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
request.getHeaders().set("X-OLAT-TOKEN", token); request.getHeaders().set("accept", "application/json");
request.getHeaders().set("accept", "application/json"); // if we don't have a token (this is normal during authentication), just do the call
HttpHeaders responseHeaders = response.getHeaders(); if (token == null) { return execution.execute(request, body); }
return execution.execute(request, body); // otherwise, add the X-OLAT-TOKEN
request.getHeaders().set("X-OLAT-TOKEN", token);
ClientHttpResponse response = execution.execute(request, body);
log.debug("OLAT [regular API call] Response Headers: {}", response.getHeaders());
// If we get a 401, re-authenticate and try once more
if (response.getStatusCode() == HttpStatus.UNAUTHORIZED) {
authenticate();
request.getHeaders().set("X-OLAT-TOKEN", token);
response = execution.execute(request, body);
log.debug("OLAT [retry API call] Response Headers: {}", response.getHeaders());
}
return response;
} }
}); });
} }
private void authenticate() {
// Authenticate with OLAT and store the received X-OLAT-TOKEN
token = null;
final String authUrl = String.format("%s%s?password=%s",
details.getAccessTokenUri(),
details.getClientId(),
details.getClientSecret());
final HttpHeaders httpHeaders = new HttpHeaders();
ResponseEntity<String> response = this.getForEntity(authUrl, String.class);
HttpHeaders responseHeaders = response.getHeaders();
log.debug("OLAT [authenticate] Response Headers: {}", responseHeaders);
token = responseHeaders.getFirst("X-OLAT-TOKEN");
}
} }