From 8155d155c0bcb182b637b6eb70efec7b53c9ab51 Mon Sep 17 00:00:00 2001 From: anhefti Date: Thu, 13 Jun 2024 13:11:08 +0200 Subject: [PATCH] SEBSERV-417 and SEBSP-111 --- .../ch/ethz/seb/sebserver/gbl/api/API.java | 6 +- .../OAuth2AuthorizationContextHolder.java | 2 + .../MonitoringProctoringService.java | 6 +- .../authorization/TeacherAccountService.java | 10 +- .../impl/TeacherAccountServiceImpl.java | 14 ++- .../lms/FullLmsIntegrationService.java | 4 - .../ScreenProctoringAPIBinding.java | 105 ++++++------------ 7 files changed, 58 insertions(+), 89 deletions(-) diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java index f6e0f993..f38a2d88 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java @@ -178,9 +178,9 @@ public final class API { public static final String LMS_FULL_INTEGRATION_EXAM_TEMPLATE_ID = "exam_template_id"; public static final String LMS_FULL_INTEGRATION_QUIT_PASSWORD = "quit_password"; public static final String LMS_FULL_INTEGRATION_QUIT_LINK = "quit_link"; - public static final String LMS_FULL_INTEGRATION_USER_ID = "userid_id"; - public static final String LMS_FULL_INTEGRATION_USER_NAME = "userid_username "; - public static final String LMS_FULL_INTEGRATION_USER_EMAIL = "userid_email"; + public static final String LMS_FULL_INTEGRATION_USER_ID = "user_id"; + public static final String LMS_FULL_INTEGRATION_USER_NAME = "user_username"; + public static final String LMS_FULL_INTEGRATION_USER_EMAIL = "user_email"; public static final String LMS_FULL_INTEGRATION_USER_FIRST_NAME = "user_firstname"; public static final String LMS_FULL_INTEGRATION_USER_LAST_NAME = "user_lastname"; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/OAuth2AuthorizationContextHolder.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/OAuth2AuthorizationContextHolder.java index 6e41ec61..17d06f96 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/OAuth2AuthorizationContextHolder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/OAuth2AuthorizationContextHolder.java @@ -288,6 +288,8 @@ public class OAuth2AuthorizationContextHolder implements AuthorizationContextHol } final TokenLoginInfo loginInfo = response.getBody(); + this.resource.setUsername(loginInfo.username); + this.resource.setPassword(loginInfo.userUUID); this.restTemplate.getOAuth2ClientContext().setAccessToken(loginInfo.login); loginForward = loginInfo.login_forward; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/MonitoringProctoringService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/MonitoringProctoringService.java index ce123d1c..900a09f1 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/MonitoringProctoringService.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/MonitoringProctoringService.java @@ -27,11 +27,7 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; import org.springframework.core.io.Resource; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; +import org.springframework.http.*; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/TeacherAccountService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/TeacherAccountService.java index 7fa302a4..e2d12976 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/TeacherAccountService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/TeacherAccountService.java @@ -18,8 +18,6 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationServi public interface TeacherAccountService { /** Creates an Ad-Hoc Teacher account for a given existing Exam. - * This also checks if such an account already exists and if so, - * it uses that and activates it if not already active * * @param exam The Exam instance * @param adHocAccountData The account data for new Ad-Hoc account @@ -38,7 +36,11 @@ public interface TeacherAccountService { default String getTeacherAccountIdentifier( final Exam exam, final FullLmsIntegrationService.AdHocAccountData adHocAccountData) { - return getTeacherAccountIdentifier(exam.getModelId(), adHocAccountData.userId); + + return getTeacherAccountIdentifier( + exam.getModelId(), + String.valueOf(exam.lmsSetupId), + adHocAccountData.userId); } /** Get the identifier for certain Teacher account for specified Exam. @@ -47,7 +49,7 @@ public interface TeacherAccountService { * @param userId the account id * @return account identifier */ - String getTeacherAccountIdentifier(String examId, String userId); + String getTeacherAccountIdentifier(String examId, String lmsId, String userId); /** Deactivates a certain ad-hoc Teacher account * Usually called when an exam is deleted. Checks if Teacher account for exam diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/impl/TeacherAccountServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/impl/TeacherAccountServiceImpl.java index a6a95184..6d26cabc 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/impl/TeacherAccountServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/impl/TeacherAccountServiceImpl.java @@ -77,12 +77,16 @@ public class TeacherAccountServiceImpl implements TeacherAccountService { } @Override - public String getTeacherAccountIdentifier(final String examId, final String userId) { - if (examId == null || userId == null) { + public String getTeacherAccountIdentifier( + final String examId, + final String lmsId, + final String userId) { + + if (examId == null || lmsId == null || userId == null) { throw new RuntimeException("examId and/or userId cannot be null"); } - return userId; + return examId + Constants.UNDERLINE + lmsId + Constants.UNDERLINE + userId; } @Override @@ -148,7 +152,6 @@ public class TeacherAccountServiceImpl implements TeacherAccountService { .byModelId(getTeacherAccountIdentifier(exam, adHocAccountData)) .onErrorDo(error -> handleAccountDoesNotExistYet(createIfNotExists, exam, adHocAccountData)) .map(account -> applySupporter(account, exam)) - .map(account -> synchronizeSPSUserForExam(account, exam.id)) .map(account -> this.createOneTimeToken(account, exam.id)); } @@ -191,7 +194,7 @@ public class TeacherAccountServiceImpl implements TeacherAccountService { key, "MONITOR_EXAM_FROM_LIST"); - return new TokenLoginInfo(user.username, user.uuid, loginForward, token); + return new TokenLoginInfo(user.username, claims.getSubject(), loginForward, token); }); } @@ -231,6 +234,7 @@ public class TeacherAccountServiceImpl implements TeacherAccountService { final String subjectClaim = UUID.randomUUID().toString(); userDAO.changePassword(account.uuid, subjectClaim); + synchronizeSPSUserForExam(account, examId); final Map claims = new HashMap<>(); claims.put(USER_CLAIM, account.uuid); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/FullLmsIntegrationService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/FullLmsIntegrationService.java index 065bdbfb..af038bd7 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/FullLmsIntegrationService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/FullLmsIntegrationService.java @@ -31,14 +31,10 @@ public interface FullLmsIntegrationService { @EventListener void notifyLmsSetupChange(final LmsSetupChangeEvent event); - - //Result applyLMSSetupDeactivation(LmsSetup lmsSetup); - @EventListener void notifyExamTemplateChange(final ExamTemplateChangeEvent event); @EventListener(ConnectionConfigurationChangeEvent.class) void notifyConnectionConfigurationChange(ConnectionConfigurationChangeEvent event); - @EventListener(ExamDeletionEvent.class) void notifyExamDeletion(ExamDeletionEvent event); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ScreenProctoringAPIBinding.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ScreenProctoringAPIBinding.java index 62d9c741..e1f231b7 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ScreenProctoringAPIBinding.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ScreenProctoringAPIBinding.java @@ -332,9 +332,9 @@ class ScreenProctoringAPIBinding { final ResponseEntity exchange = apiTemplate.exchange(uri, HttpMethod.GET); - if (exchange.getStatusCode() != HttpStatus.NOT_FOUND) { + if (exchange.getStatusCode() == HttpStatus.NOT_FOUND) { return false; - } else if (exchange.getStatusCode() != HttpStatus.OK) { + } else if (exchange.getStatusCode() == HttpStatus.OK) { return true; } else { log.warn("Failed to verify if Exam on SPS already exists: {}", exchange.getBody()); @@ -468,6 +468,10 @@ class ScreenProctoringAPIBinding { return Result.tryCatch(() -> { + if (!this.isSPSActive(exam)) { + return exam; + } + if (log.isDebugEnabled()) { log.debug("Deactivate active screen proctoring exam, groups and access on SPS for exam: {}", exam.name); } @@ -629,56 +633,6 @@ class ScreenProctoringAPIBinding { } -// /** This is called on exam delete and deletes the SEB Client Access and the ad-hoc User-Account -// * on Screen Proctoring Service side. -// * Also sends a exam delete request where Exam on SPS gets deleted if there are no session data for the exam -// * -// * @param exam The exam -// * @return Result refer to the exam or to an error when happened */ -// Exam deleteScreenProctoring(final Exam exam) { -// try { -// -// if (!BooleanUtils.toBoolean(exam.additionalAttributes.get(SPSData.ATTR_SPS_ACTIVE))) { -// return exam; -// } -// -// if (log.isDebugEnabled()) { -// log.debug("Deactivate exam and groups on SPS site and send deletion request for exam {}", exam); -// } -// -// final ScreenProctoringServiceOAuthTemplate apiTemplate = this.getAPITemplate(exam.id); -// final SPSData spsData = this.getSPSData(exam.id); -// deletion(SPS_API.SEB_ACCESS_ENDPOINT, spsData.spsSEBAccessUUID, apiTemplate); -// activation(exam, SPS_API.EXAM_ENDPOINT, spsData.spsExamUUID, false, apiTemplate); -// -// // exam delete request on SPS -// final String uri = UriComponentsBuilder -// .fromUriString(apiTemplate.spsAPIAccessData.getSpsServiceURL()) -// .path(SPS_API.EXAM_ENDPOINT) -// .pathSegment(spsData.spsExamUUID) -// .pathSegment(SPS_API.EXAM_DELETE_REQUEST_ENDPOINT) -// .build() -// .toUriString(); -// -// final ResponseEntity exchange = apiTemplate.exchange(uri, HttpMethod.DELETE); -// if (exchange.getStatusCode() != HttpStatus.OK) { -// log.error("Failed to request delete on SPS for Exam: {} with response: {}", exam, exchange); -// } -// -// // mark successfully dispose on SPS side -// this.additionalAttributesDAO.saveAdditionalAttribute( -// EntityType.EXAM, -// exam.id, -// SPSData.ATTR_SPS_ACTIVE, -// Constants.FALSE_STRING); -// -// -// } catch (final Exception e) { -// log.warn("Failed to apply SPS deletion of exam: {} error: {}", exam, e.getMessage()); -// } -// return exam; -// } - Result createGroup( final String spsExamUUID, final int groupNumber, @@ -741,23 +695,14 @@ class ScreenProctoringAPIBinding { return token; } -// void activateExamOnSPS(final Exam exam, final boolean activate) { -// try { -// final ScreenProctoringServiceOAuthTemplate apiTemplate = this.getAPITemplate(exam.id); -// final SPSData spsData = this.getSPSData(exam.id); -// -// activation(exam, SPS_API.EXAM_ENDPOINT, spsData.spsSEBAccessUUID, activate, apiTemplate); -// activation(exam, SPS_API.SEB_ACCESS_ENDPOINT, spsData.spsSEBAccessUUID, activate, apiTemplate); -// -// } catch (final Exception e) { -// log.error("Failed to de/activate SEB Access on SPS for exam: {}", exam); -// } -// } - private void synchronizeUserAccount( final String userUUID, final ScreenProctoringServiceOAuthTemplate apiTemplate) { + if (UserService.LMS_INTEGRATION_CLIENT_UUID.equals(userUUID)) { + return; + } + try { final UserInfo userInfo = this.userDAO @@ -919,7 +864,6 @@ class ScreenProctoringAPIBinding { }); final String spsGroupUUID = groupAttributes.get(SPS_API.GROUP.ATTR_UUID); - return new ScreenProctoringGroup(null, examId, spsGroupUUID, name, size, exchange.getBody()); } @@ -1007,7 +951,33 @@ class ScreenProctoringAPIBinding { final String name = SEB_SERVER_SCREEN_PROCTORING_SEB_ACCESS_PREFIX + exam.externalId; final String description = "This SEB access was auto-generated by SEB Server"; - final String uri = UriComponentsBuilder + // first try to get existing one by name and link it if available + String uri = UriComponentsBuilder + .fromUriString(apiTemplate.spsAPIAccessData.getSpsServiceURL()) + .path(SPS_API.SEB_ACCESS_ENDPOINT) + .queryParam(SPS_API.SEB_ACCESS.ATTR_NAME, name) + .build() + .toUriString(); + + final ResponseEntity getResponse = apiTemplate.exchange(uri, HttpMethod.GET); + if (getResponse.getStatusCode() == HttpStatus.OK) { + try { + final JsonNode requestJSON = this.jsonMapper.readTree(getResponse.getBody()); + final JsonNode content = requestJSON.get("content"); + if (content.isArray()) { + final JsonNode sebConnection = content.get(0); + spsData.spsSEBAccessUUID = sebConnection.get(SPS_API.SEB_ACCESS.ATTR_UUID).textValue(); + spsData.spsSEBAccessName = sebConnection.get(SPS_API.SEB_ACCESS.ATTR_CLIENT_NAME).textValue(); + spsData.spsSEBAccessPWD = sebConnection.get(SPS_API.SEB_ACCESS.ATTR_CLIENT_SECRET).textValue(); + return; + } + } catch (final Exception e) { + log.warn("Failed to extract existing SEB Account from JSON: {}", e.getMessage()); + } + } + + // otherwise create new one and link it + uri = UriComponentsBuilder .fromUriString(apiTemplate.spsAPIAccessData.getSpsServiceURL()) .path(SPS_API.SEB_ACCESS_ENDPOINT) .build() @@ -1022,7 +992,6 @@ class ScreenProctoringAPIBinding { if (exchange.getStatusCode() != HttpStatus.OK) { throw new RuntimeException("Failed to create SPS SEB access for exam: " + exam.externalId); } - ; // store SEB access data for proctoring along with the exam final JsonNode requestJSON = this.jsonMapper.readTree(exchange.getBody());