From 6a0d53c8c41792064c92a4116b1ba204d41857ad Mon Sep 17 00:00:00 2001 From: anhefti Date: Tue, 28 Nov 2023 16:59:18 +0100 Subject: [PATCH] SEBSERV-435 improved SEB Server SPS user account sync --- .../gbl/async/AsyncServiceSpringConfig.java | 6 +- .../gbl/model/exam/SPSAPIAccessData.java | 16 ++ .../model/exam/ScreenProctoringSettings.java | 2 +- .../sebserver/webservice/WebserviceInfo.java | 33 ++- .../exam/impl/ProctoringAdminServiceImpl.java | 2 +- .../session/ScreenProctoringService.java | 13 +- .../session/impl/ExamUpdateHandler.java | 2 +- .../ScreenProctoringAPIBinding.java | 266 ++++++++++-------- .../ScreenProctoringServiceImpl.java | 67 +++-- .../weblayer/api/UserAccountController.java | 16 ++ 10 files changed, 267 insertions(+), 156 deletions(-) create mode 100644 src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/SPSAPIAccessData.java diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/async/AsyncServiceSpringConfig.java b/src/main/java/ch/ethz/seb/sebserver/gbl/async/AsyncServiceSpringConfig.java index 84ea54f3..86da5886 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/async/AsyncServiceSpringConfig.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/async/AsyncServiceSpringConfig.java @@ -26,7 +26,7 @@ public class AsyncServiceSpringConfig implements AsyncConfigurer { public static final String EXECUTOR_BEAN_NAME = "AsyncServiceExecutorBean"; - /** This ThreadPool is used for internal long running background tasks */ + /** This ThreadPool is used for internal long-running background tasks */ @Bean(name = EXECUTOR_BEAN_NAME) public Executor threadPoolTaskExecutor() { final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); @@ -61,8 +61,8 @@ public class AsyncServiceSpringConfig implements AsyncConfigurer { public static final String EXAM_API_PING_SERVICE_EXECUTOR_BEAN_NAME = "examAPIPingThreadPoolTaskExecutor"; /** This ThreadPool is used for ping handling in a distributed setup and shall reject - * incoming ping requests as fast as possible if there is to much load on the DB. - * We prefer to loose a shared ping update and respond to the client in time over a client request timeout */ + * incoming ping requests as fast as possible if there is too much load on the DB. + * We prefer to lose a shared ping update and respond to the client in time over a client request timeout */ @Bean(name = EXAM_API_PING_SERVICE_EXECUTOR_BEAN_NAME) public Executor examAPIPingThreadPoolTaskExecutor() { final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/SPSAPIAccessData.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/SPSAPIAccessData.java new file mode 100644 index 00000000..a9593aba --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/SPSAPIAccessData.java @@ -0,0 +1,16 @@ +package ch.ethz.seb.sebserver.gbl.model.exam; + +public interface SPSAPIAccessData { + + Long getExamId(); + + String getSpsServiceURL(); + + String getSpsAPIKey(); + + CharSequence getSpsAPISecret(); + + String getSpsAccountId(); + + CharSequence getSpsAccountPassword(); +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ScreenProctoringSettings.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ScreenProctoringSettings.java index 9d5a3ef5..9d0af395 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ScreenProctoringSettings.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ScreenProctoringSettings.java @@ -21,7 +21,7 @@ import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.model.Domain; @JsonIgnoreProperties(ignoreUnknown = true) -public class ScreenProctoringSettings { +public class ScreenProctoringSettings implements SPSAPIAccessData { public static final String ATTR_ENABLE_SCREEN_PROCTORING = "enableScreenProctoring"; public static final String ATTR_SPS_SERVICE_URL = "spsServiceURL"; diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInfo.java b/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInfo.java index 65cdc4e2..d05cdb7f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInfo.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInfo.java @@ -18,6 +18,7 @@ import java.util.Map; import java.util.Set; import java.util.UUID; +import ch.ethz.seb.sebserver.gbl.model.exam.SPSAPIAccessData; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; @@ -329,7 +330,7 @@ public class WebserviceInfo { return builder.toString(); } - public static final class ScreenProctoringServiceBundle { + public static final class ScreenProctoringServiceBundle implements SPSAPIAccessData { public final boolean bundled; public final String serviceURL; @@ -362,6 +363,36 @@ public class WebserviceInfo { this.apiAccountPassword = null; } + @Override + public Long getExamId() { + return null; + } + + @Override + public String getSpsServiceURL() { + return serviceURL; + } + + @Override + public String getSpsAPIKey() { + return clientId; + } + + @Override + public CharSequence getSpsAPISecret() { + return clientSecret; + } + + @Override + public String getSpsAccountId() { + return apiAccountName; + } + + @Override + public CharSequence getSpsAccountPassword() { + return apiAccountPassword; + } + @Override public String toString() { final StringBuilder builder = new StringBuilder(); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ProctoringAdminServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ProctoringAdminServiceImpl.java index 7ea720de..72ed249a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ProctoringAdminServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ProctoringAdminServiceImpl.java @@ -154,7 +154,7 @@ public class ProctoringAdminServiceImpl implements ProctoringAdminService { if (parentEntityKey.entityType == EntityType.EXAM) { this.screenProctoringService - .applyScreenProctoingForExam(settings.examId) + .applyScreenProctoringForExam(settings.examId) .onError(error -> this.proctoringSettingsDAO .disableScreenProctoring(screenProctoringSettings.examId)) .getOrThrow(); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ScreenProctoringService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ScreenProctoringService.java index f86d642f..f6427c55 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ScreenProctoringService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ScreenProctoringService.java @@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session; import java.util.Collection; +import ch.ethz.seb.sebserver.gbl.async.AsyncServiceSpringConfig; import org.springframework.context.event.EventListener; import ch.ethz.seb.sebserver.gbl.model.exam.Exam; @@ -17,6 +18,7 @@ import ch.ethz.seb.sebserver.gbl.model.exam.ScreenProctoringSettings; import ch.ethz.seb.sebserver.gbl.model.session.ScreenProctoringGroup; import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.impl.ExamDeletionEvent; +import org.springframework.scheduling.annotation.Async; public interface ScreenProctoringService extends SessionUpdateTask { @@ -45,7 +47,7 @@ public interface ScreenProctoringService extends SessionUpdateTask { * * @param examId use the screen proctoring settings of the exam with the given exam id * @return Result refer to the given Exam or to an error when happened */ - Result applyScreenProctoingForExam(Long examId); + Result applyScreenProctoringForExam(Long examId); /** Get list of all screen proctoring collecting groups for a particular exam. * @@ -64,7 +66,7 @@ public interface ScreenProctoringService extends SessionUpdateTask { @EventListener(ExamFinishedEvent.class) void notifyExamFinished(ExamFinishedEvent event); - /** This is been called just before an Exam gets deleted on the permanent storage. + /** This is being called just before an Exam gets deleted on the permanent storage. * This deactivates and dispose or deletes all exam relevant domain entities on the SPS service side. * * @param event The ExamDeletionEvent reference all PKs of Exams that are going to be deleted. */ @@ -75,8 +77,8 @@ public interface ScreenProctoringService extends SessionUpdateTask { * if screen proctoring is enabled for the specified exam. * * @param examId The SEB Server exam identifier - * @return Result refer to the the given exam data or to an error when happened */ - Result updateExamOnScreenProctoingService(Long examId); + * @return Result refer to the given exam data or to an error when happened */ + Result updateExamOnScreenProctoringService(Long examId); /** This is internally used to update client connections that are active but has no groups assignment yet. * This attaches SEB client connections to proctoring group of an exam in one batch by checking for @@ -85,4 +87,7 @@ public interface ScreenProctoringService extends SessionUpdateTask { * SPS connection instruction to SEB client to connect and start sending screenshots. */ void updateClientConnections(); + @Async(AsyncServiceSpringConfig.EXECUTOR_BEAN_NAME) + void synchronizeSPSUser(final String userUUID); + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamUpdateHandler.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamUpdateHandler.java index 9dc10f61..8c365608 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamUpdateHandler.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamUpdateHandler.java @@ -200,7 +200,7 @@ class ExamUpdateHandler implements ExamUpdateTask { // also update the exam on screen proctoring service if exam has screen proctoring enabled this.screenProctoringService - .updateExamOnScreenProctoingService(exam.id) + .updateExamOnScreenProctoringService(exam.id) .onError(error -> log .error("Failed to update exam changes for screen proctoring")); 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 3c57d397..0b8bbf69 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 @@ -8,13 +8,11 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.proctoring; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.util.*; +import ch.ethz.seb.sebserver.gbl.model.exam.SPSAPIAccessData; +import ch.ethz.seb.sebserver.gbl.model.user.UserRole; +import ch.ethz.seb.sebserver.webservice.WebserviceInfo; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; @@ -70,19 +68,21 @@ class ScreenProctoringAPIBinding { private static final Logger log = LoggerFactory.getLogger(ScreenProctoringAPIBinding.class); - //private static final String SEB_SERVER_SCREEN_PROCTORING_USER_PREFIX = "SEBServer_User_"; private static final String SEB_SERVER_SCREEN_PROCTORING_SEB_ACCESS_PREFIX = "SEBServer_SEB_Access_"; static interface SPS_API { - String PROCTOR_ROLE = "PROCTOR"; + enum SPSUserRole { + ADMIN, + PROCTOR + } String TOKEN_ENDPOINT = "/oauth/token"; String TEST_ENDPOINT = "/admin-api/v1/proctoring/group"; - //String USER_ENDPOINT = "/admin-api/v1/useraccount"; - public static final String USERSYNC_SEBSERVER_ENDPOINT = "/admin-api/v1/useraccount/usersync/sebserver"; - String ENTIY_PRIVILEGES_ENDPOINT = "/admin-api/v1/useraccount/entityprivilege"; + String USER_ACCOUNT_ENDPOINT = "/admin-api/v1/useraccount/"; + String USERSYNC_SEBSERVER_ENDPOINT = USER_ACCOUNT_ENDPOINT + "usersync/sebserver"; + String ENTITY_PRIVILEGES_ENDPOINT = USER_ACCOUNT_ENDPOINT + "entityprivilege"; String EXAM_ENDPOINT = "/admin-api/v1/exam"; String SEB_ACCESS_ENDPOINT = "/admin-api/v1/clientaccess"; String GROUP_ENDPOINT = "/admin-api/v1/group"; @@ -195,6 +195,7 @@ class ScreenProctoringAPIBinding { private final JSONMapper jsonMapper; private final ProctoringSettingsDAO proctoringSettingsDAO; private final AdditionalAttributesDAO additionalAttributesDAO; + private final WebserviceInfo webserviceInfo; ScreenProctoringAPIBinding( final UserDAO userDAO, @@ -202,7 +203,8 @@ class ScreenProctoringAPIBinding { final AsyncService asyncService, final JSONMapper jsonMapper, final ProctoringSettingsDAO proctoringSettingsDAO, - final AdditionalAttributesDAO additionalAttributesDAO) { + final AdditionalAttributesDAO additionalAttributesDAO, + final WebserviceInfo webserviceInfo) { this.userDAO = userDAO; this.cryptor = cryptor; @@ -210,14 +212,15 @@ class ScreenProctoringAPIBinding { this.jsonMapper = jsonMapper; this.proctoringSettingsDAO = proctoringSettingsDAO; this.additionalAttributesDAO = additionalAttributesDAO; + this.webserviceInfo = webserviceInfo; } - public Result testConnection(final ScreenProctoringSettings screenProctoringSettings) { + Result testConnection(final SPSAPIAccessData spsAPIAccessData) { return Result.tryCatch(() -> { try { final ScreenProctoringServiceOAuthTemplate newRestTemplate = - new ScreenProctoringServiceOAuthTemplate(this, screenProctoringSettings); + new ScreenProctoringServiceOAuthTemplate(this, spsAPIAccessData); final ResponseEntity result = newRestTemplate.testServiceConnection(); @@ -225,7 +228,7 @@ class ScreenProctoringAPIBinding { if (result.getStatusCode().is4xxClientError()) { log.warn( "Failed to establish REST connection to: {}. status: {}", - screenProctoringSettings.spsServiceURL, result.getStatusCode()); + spsAPIAccessData.getSpsServiceURL(), result.getStatusCode()); throw new FieldValidationException( "serverURL", @@ -238,7 +241,7 @@ class ScreenProctoringAPIBinding { } catch (final Exception e) { log.error("Failed to access SEB Screen Proctoring service at: {}", - screenProctoringSettings.spsServiceURL, e); + spsAPIAccessData.getSpsServiceURL(), e); throw new FieldValidationException( "serverURL", "proctoringSettings:serverURL:url.noservice"); @@ -246,7 +249,7 @@ class ScreenProctoringAPIBinding { }); } - public boolean isSPSActive(final Exam exam) { + boolean isSPSActive(final Exam exam) { try { final String active = this.additionalAttributesDAO .getAdditionalAttribute( @@ -261,7 +264,7 @@ class ScreenProctoringAPIBinding { } } - private SPSData getSPSData(final Long examId) { + SPSData getSPSData(final Long examId) { try { final String dataEncrypted = this.additionalAttributesDAO @@ -290,7 +293,7 @@ class ScreenProctoringAPIBinding { * * @param exam The exam * @return Result refer to the exam or to an error when happened */ - public Result> startScreenProctoring(final Exam exam) { + Result> startScreenProctoring(final Exam exam) { return Result.tryCatch(() -> { if (log.isDebugEnabled()) { @@ -298,14 +301,14 @@ class ScreenProctoringAPIBinding { } final ScreenProctoringServiceOAuthTemplate apiTemplate = this.getAPITemplate(exam.id); - + if (exam.additionalAttributes.containsKey(SPSData.ATTR_SPS_ACTIVE)) { log.info("SPS Exam for SEB Server Exam: {} already exists. Try to re-activate", exam.externalId); final SPSData spsData = this.getSPSData(exam.id); // re-activate all needed entities on SPS side - activation(exam, SPS_API.SEB_ACCESS_ENDPOINT, spsData.spsSEBAccesUUID, true, apiTemplate); + activation(exam, SPS_API.SEB_ACCESS_ENDPOINT, spsData.spsSEBAccessUUID, true, apiTemplate); // mark successfully activated on SPS side this.additionalAttributesDAO.saveAdditionalAttribute( @@ -323,7 +326,7 @@ class ScreenProctoringAPIBinding { "SPS Exam for SEB Server Exam: {} don't exists yet, create necessary structures on SPS", exam.externalId); - exam.supporter.forEach(userUUID -> synchronizeUserAccount(userUUID, apiTemplate, spsData)); + exam.supporter.forEach(userUUID -> synchronizeUserAccount(userUUID, apiTemplate)); createSEBAccess(exam, apiTemplate, spsData); createExam(exam, apiTemplate, spsData); exam.supporter.forEach(userUUID -> createExamReadPrivilege(userUUID, spsData.spsExamUUID, apiTemplate)); @@ -348,12 +351,37 @@ class ScreenProctoringAPIBinding { }); } - public void synchronizeUserAccounts(final Exam exam) { + void synchronizeUserAccount(final String userUUID) { + try { + + final ScreenProctoringServiceOAuthTemplate apiTemplate = this.getAPITemplate(null); + // check if user exists on SPS + final String uri = UriComponentsBuilder + .fromUriString(apiTemplate.spsAPIAccessData.getSpsServiceURL()) + .path(SPS_API.USER_ACCOUNT_ENDPOINT + userUUID) + .build() + .toUriString(); + + final ResponseEntity exchange = apiTemplate.exchange( + uri, HttpMethod.POST, null, apiTemplate.getHeaders()); + + if (exchange.getStatusCode() == HttpStatus.OK) { + log.info("Synchronize SPS user account for SEB Server user account with id: {} ", userUUID); + this.synchronizeUserAccount(userUUID, apiTemplate); + } + + } catch (final Exception e) { + log.error("Failed to synchronize user account with SPS for user: {}", userUUID); + } + } + + void synchronizeUserAccounts(final Exam exam) { try { final ScreenProctoringServiceOAuthTemplate apiTemplate = this.getAPITemplate(exam.id); final SPSData spsData = this.getSPSData(exam.id); - exam.supporter.forEach(userUUID -> synchronizeUserAccount(userUUID, apiTemplate, spsData)); + exam.supporter.forEach(userUUID -> synchronizeUserAccount(userUUID, apiTemplate)); + } catch (final Exception e) { log.error("Failed to synchronize user accounts with SPS for exam: {}", exam); } @@ -363,14 +391,14 @@ class ScreenProctoringAPIBinding { * * @param exam The exam * @return Result refer to the exam or to an error when happened */ - public Result updateExam(final Exam exam) { + Result updateExam(final Exam exam) { return Result.tryCatch(() -> { final SPSData spsData = this.getSPSData(exam.id); final ScreenProctoringServiceOAuthTemplate apiTemplate = this.getAPITemplate(exam.id); final String uri = UriComponentsBuilder - .fromUriString(apiTemplate.screenProctoringSettings.spsServiceURL) + .fromUriString(apiTemplate.spsAPIAccessData.getSpsServiceURL()) .path(SPS_API.EXAM_ENDPOINT) .pathSegment(spsData.spsExamUUID) .build() @@ -381,8 +409,8 @@ class ScreenProctoringAPIBinding { exam.getDescription(), exam.getStartURL(), exam.getType().name(), - exam.startTime.getMillis(), - exam.endTime.getMillis()); + exam.startTime != null ? exam.startTime.getMillis() : null, + exam.endTime != null ? exam.endTime.getMillis() : null); final String jsonExamUpdate = this.jsonMapper.writeValueAsString(examUpdate); @@ -404,7 +432,7 @@ class ScreenProctoringAPIBinding { * * @param exam The exam * @return Result refer to the exam or to an error when happened */ - public Result dispsoseScreenProctoring(final Exam exam) { + Result disposeScreenProctoring(final Exam exam) { return Result.tryCatch(() -> { @@ -414,7 +442,7 @@ class ScreenProctoringAPIBinding { final SPSData spsData = this.getSPSData(exam.id); final ScreenProctoringServiceOAuthTemplate apiTemplate = this.getAPITemplate(exam.id); - activation(exam, SPS_API.SEB_ACCESS_ENDPOINT, spsData.spsSEBAccesUUID, false, apiTemplate); + activation(exam, SPS_API.SEB_ACCESS_ENDPOINT, spsData.spsSEBAccessUUID, false, apiTemplate); // mark successfully dispose on SPS side this.additionalAttributesDAO.saveAdditionalAttribute( @@ -432,7 +460,7 @@ class ScreenProctoringAPIBinding { * * @param exam The exam * @return Result refer to the exam or to an error when happened */ - public Result deleteScreenProctoring(final Exam exam) { + Result deleteScreenProctoring(final Exam exam) { return Result.tryCatch(() -> { @@ -446,7 +474,7 @@ class ScreenProctoringAPIBinding { final ScreenProctoringServiceOAuthTemplate apiTemplate = this.getAPITemplate(exam.id); final SPSData spsData = this.getSPSData(exam.id); - deletion(SPS_API.SEB_ACCESS_ENDPOINT, spsData.spsSEBAccesUUID, apiTemplate); + deletion(SPS_API.SEB_ACCESS_ENDPOINT, spsData.spsSEBAccessUUID, apiTemplate); // mark successfully dispose on SPS side this.additionalAttributesDAO.saveAdditionalAttribute( @@ -459,7 +487,7 @@ class ScreenProctoringAPIBinding { }); } - public Result createGroup( + Result createGroup( final String spsExamUUID, final int groupNumber, final String description, @@ -467,14 +495,17 @@ class ScreenProctoringAPIBinding { return Result.tryCatch(() -> { final ScreenProctoringServiceOAuthTemplate apiTemplate = this.getAPITemplate(exam.id); + final ScreenProctoringSettings settings = this.proctoringSettingsDAO + .getScreenProctoringSettings(new EntityKey(exam.id, EntityType.EXAM)) + .getOrThrow(); - if (apiTemplate.screenProctoringSettings.collectingStrategy != CollectingStrategy.FIX_SIZE) { + if (settings.collectingStrategy != CollectingStrategy.FIX_SIZE) { throw new IllegalStateException( "Only FIX_SIZE collecting strategy is supposed to create additional rooms"); } return createGroupOnSPS( - apiTemplate.screenProctoringSettings.collectingGroupSize, + settings.collectingGroupSize, exam.id, "Proctoring Group " + groupNumber + " : " + exam.getName(), description, @@ -483,7 +514,7 @@ class ScreenProctoringAPIBinding { }); } - public String createSEBSession( + String createSEBSession( final Long examId, final ScreenProctoringGroup localGroup, final ClientConnectionRecord clientConnection) { @@ -493,7 +524,7 @@ class ScreenProctoringAPIBinding { final String token = clientConnection.getConnectionToken(); final ScreenProctoringServiceOAuthTemplate apiTemplate = this.getAPITemplate(examId); final String uri = UriComponentsBuilder - .fromUriString(apiTemplate.screenProctoringSettings.spsServiceURL) + .fromUriString(apiTemplate.spsAPIAccessData.getSpsServiceURL()) .path(SPS_API.SESSION_ENDPOINT) .build() @@ -518,19 +549,19 @@ class ScreenProctoringAPIBinding { return token; } - public void activateSEBAccessOnSPS(final Exam exam, final boolean activate) { + void activateSEBAccessOnSPS(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.SEB_ACCESS_ENDPOINT, spsData.spsSEBAccesUUID, 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); } } - public void createExamReadPrivileges(final Exam exam) { + void createExamReadPrivileges(final Exam exam) { try { final ScreenProctoringServiceOAuthTemplate apiTemplate = this.getAPITemplate(exam.id); final SPSData spsData = this.getSPSData(exam.id); @@ -544,8 +575,7 @@ class ScreenProctoringAPIBinding { private void synchronizeUserAccount( final String userUUID, - final ScreenProctoringServiceOAuthTemplate apiTemplate, - final SPSData spsData) { + final ScreenProctoringServiceOAuthTemplate apiTemplate) { try { @@ -556,32 +586,17 @@ class ScreenProctoringAPIBinding { .sebServerUserByUsername(userInfo.name) .getOrThrow(); - final UserMod userMod = new UserMod( - userInfo.uuid, - -1L, - userInfo.name, - userInfo.surname, - userInfo.username, - accountInfo.getPassword(), - accountInfo.getPassword(), - userInfo.email, - userInfo.language, - userInfo.timeZone, - userInfo.roles); - + final UserMod userMod = getUserModifications(userInfo, accountInfo); final String uri = UriComponentsBuilder - .fromUriString(apiTemplate.screenProctoringSettings.spsServiceURL) + .fromUriString(apiTemplate.spsAPIAccessData.getSpsServiceURL()) .path(SPS_API.USERSYNC_SEBSERVER_ENDPOINT) .build() .toUriString(); final String jsonBody = this.jsonMapper.writeValueAsString(userMod); - final ResponseEntity exchange = apiTemplate.exchange( - uri, - HttpMethod.POST, - jsonBody, - apiTemplate.getHeadersJSONRequest()); + uri, HttpMethod.POST, jsonBody, apiTemplate.getHeadersJSONRequest()); + if (exchange.getStatusCode() != HttpStatus.OK) { log.warn("Failed to synchronize user account on SPS: {}", exchange); } else { @@ -594,6 +609,28 @@ class ScreenProctoringAPIBinding { } } + private static UserMod getUserModifications(final UserInfo userInfo, final SEBServerUser accountInfo) { + final Set spsUserRoles = new HashSet<>(); + spsUserRoles.add(SPS_API.SPSUserRole.PROCTOR.name()); + if (userInfo.roles.contains(UserRole.SEB_SERVER_ADMIN.name()) || + userInfo.roles.contains(UserRole.INSTITUTIONAL_ADMIN.name())) { + spsUserRoles.add(SPS_API.SPSUserRole.ADMIN.name()); + } + + return new UserMod( + userInfo.uuid, + -1L, + userInfo.name, + userInfo.surname, + userInfo.username, + accountInfo.getPassword(), + accountInfo.getPassword(), + userInfo.email, + userInfo.language, + userInfo.timeZone, + spsUserRoles); + } + private void createExamReadPrivilege( final String userUUID, final String examUUID, @@ -606,8 +643,8 @@ class ScreenProctoringAPIBinding { .getOrThrow(); final String uri = UriComponentsBuilder - .fromUriString(apiTemplate.screenProctoringSettings.spsServiceURL) - .path(SPS_API.ENTIY_PRIVILEGES_ENDPOINT) + .fromUriString(apiTemplate.spsAPIAccessData.getSpsServiceURL()) + .path(SPS_API.ENTITY_PRIVILEGES_ENDPOINT) .build() .toUriString(); @@ -646,13 +683,16 @@ class ScreenProctoringAPIBinding { try { + final ScreenProctoringSettings settings = this.proctoringSettingsDAO + .getScreenProctoringSettings(new EntityKey(exam.id, EntityType.EXAM)) + .getOrThrow(); final List result = new ArrayList<>(); - switch (apiTemplate.screenProctoringSettings.collectingStrategy) { + switch (settings.collectingStrategy) { case FIX_SIZE: { result.add(createGroupOnSPS( - apiTemplate.screenProctoringSettings.collectingGroupSize, + settings.collectingGroupSize, exam.id, "Group 1 : " + exam.getName(), "Created by SEB Server", @@ -699,7 +739,7 @@ class ScreenProctoringAPIBinding { throws JsonMappingException, JsonProcessingException { final String uri = UriComponentsBuilder - .fromUriString(apiTemplate.screenProctoringSettings.spsServiceURL) + .fromUriString(apiTemplate.spsAPIAccessData.getSpsServiceURL()) .path(SPS_API.GROUP_ENDPOINT) .build() .toUriString(); @@ -732,7 +772,7 @@ class ScreenProctoringAPIBinding { try { final String uri = UriComponentsBuilder - .fromUriString(apiTemplate.screenProctoringSettings.spsServiceURL) + .fromUriString(apiTemplate.spsAPIAccessData.getSpsServiceURL()) .path(SPS_API.EXAM_ENDPOINT) .build().toUriString(); @@ -776,7 +816,7 @@ class ScreenProctoringAPIBinding { final String description = "This SEB access was auto-generated by SEB Server"; final String uri = UriComponentsBuilder - .fromUriString(apiTemplate.screenProctoringSettings.spsServiceURL) + .fromUriString(apiTemplate.spsAPIAccessData.getSpsServiceURL()) .path(SPS_API.SEB_ACCESS_ENDPOINT) .build() .toUriString(); @@ -794,7 +834,7 @@ class ScreenProctoringAPIBinding { // store SEB access data for proctoring along with the exam final JsonNode requestJSON = this.jsonMapper.readTree(exchange.getBody()); - spsData.spsSEBAccesUUID = requestJSON.get(SPS_API.SEB_ACCESS.ATTR_UUID).textValue(); + spsData.spsSEBAccessUUID = requestJSON.get(SPS_API.SEB_ACCESS.ATTR_UUID).textValue(); spsData.spsSEBAccessName = requestJSON.get(SPS_API.SEB_ACCESS.ATTR_CLIENT_NAME).textValue(); spsData.spsSEBAccessPWD = requestJSON.get(SPS_API.SEB_ACCESS.ATTR_CLIENT_SECRET).textValue(); @@ -819,7 +859,7 @@ class ScreenProctoringAPIBinding { try { final String uri = UriComponentsBuilder - .fromUriString(apiTemplate.screenProctoringSettings.spsServiceURL) + .fromUriString(apiTemplate.spsAPIAccessData.getSpsServiceURL()) .path(domainPath) .pathSegment(uuid) .pathSegment(activate ? SPS_API.ACTIVE_PATH_SEGMENT : SPS_API.INACTIVE_PATH_SEGMENT) @@ -843,7 +883,7 @@ class ScreenProctoringAPIBinding { try { final String uri = UriComponentsBuilder - .fromUriString(apiTemplate.screenProctoringSettings.spsServiceURL) + .fromUriString(apiTemplate.spsAPIAccessData.getSpsServiceURL()) .path(domainPath) .pathSegment(uuid) .build() @@ -851,10 +891,10 @@ class ScreenProctoringAPIBinding { final ResponseEntity exchange = apiTemplate.exchange(uri, HttpMethod.DELETE); if (exchange.getStatusCode() != HttpStatus.OK) { - log.error("Failed to delete on SPS: {} with response: ", uri, exchange); + log.error("Failed to delete on SPS: {} with response: {}", uri, exchange); } } catch (final Exception e) { - log.error("Failed to delete on SPS: {}, {}, {}", domainPath, uuid, e); + log.error("Failed to delete on SPS: {}, {}, ", domainPath, uuid, e); } } @@ -878,13 +918,13 @@ class ScreenProctoringAPIBinding { } - if (StringUtils.isNotBlank(spsData.spsSEBAccesUUID)) { + if (StringUtils.isNotBlank(spsData.spsSEBAccessUUID)) { log.info( "Try to rollback SPS SEB Access with UUID: {} for exam: {}", - spsData.spsSEBAccesUUID, + spsData.spsSEBAccessUUID, exam.externalId); - deletion(SPS_API.SEB_ACCESS_ENDPOINT, spsData.spsSEBAccesUUID, apiTemplate); + deletion(SPS_API.SEB_ACCESS_ENDPOINT, spsData.spsSEBAccessUUID, apiTemplate); } } @@ -892,16 +932,34 @@ class ScreenProctoringAPIBinding { private ScreenProctoringServiceOAuthTemplate getAPITemplate(final Long examId) { if (this.apiTemplate == null || !this.apiTemplate.isValid(examId)) { + if (examId != null) { - log.debug("Create new ScreenProctoringServiceOAuthTemplate for exam: {}", examId); + if (log.isDebugEnabled()) { + log.debug("Create new ScreenProctoringServiceOAuthTemplate for exam: {}", examId); + } - final ScreenProctoringSettings settings = this.proctoringSettingsDAO - .getScreenProctoringSettings(new EntityKey(examId, EntityType.EXAM)) - .getOrThrow(); + final ScreenProctoringSettings settings = this.proctoringSettingsDAO + .getScreenProctoringSettings(new EntityKey(examId, EntityType.EXAM)) + .getOrThrow(); + this.testConnection(settings).getOrThrow(); + this.apiTemplate = new ScreenProctoringServiceOAuthTemplate(this, settings); - this.testConnection(settings).getOrThrow(); + } else if (this.webserviceInfo.getScreenProctoringServiceBundle().bundled) { - this.apiTemplate = new ScreenProctoringServiceOAuthTemplate(this, settings); + if (log.isDebugEnabled()) { + log.debug("Create new ScreenProctoringServiceOAuthTemplate for exam: {}", examId); + } + + WebserviceInfo.ScreenProctoringServiceBundle bundle = this.webserviceInfo + .getScreenProctoringServiceBundle(); + + this.testConnection(bundle).getOrThrow(); + this.apiTemplate = new ScreenProctoringServiceOAuthTemplate(this, bundle); + + + } else { + throw new IllegalStateException("No SPS API access information found!"); + } } return this.apiTemplate; @@ -913,9 +971,8 @@ class ScreenProctoringAPIBinding { private static final List SCOPES = Collections.unmodifiableList( Arrays.asList("read", "write")); - private final ScreenProctoringSettings screenProctoringSettings; + private final SPSAPIAccessData spsAPIAccessData; private final CircuitBreaker> circuitBreaker; - private final ResourceOwnerPasswordResourceDetails resource; private final ClientCredentials clientCredentials; private final ClientCredentials userCredentials; @@ -923,32 +980,31 @@ class ScreenProctoringAPIBinding { ScreenProctoringServiceOAuthTemplate( final ScreenProctoringAPIBinding sebScreenProctoringService, - final ScreenProctoringSettings screenProctoringSettings) { + final SPSAPIAccessData spsAPIAccessData) { - this.screenProctoringSettings = screenProctoringSettings; + this.spsAPIAccessData = spsAPIAccessData; this.circuitBreaker = sebScreenProctoringService.asyncService.createCircuitBreaker( 2, 10 * Constants.SECOND_IN_MILLIS, 10 * Constants.SECOND_IN_MILLIS); this.clientCredentials = new ClientCredentials( - this.screenProctoringSettings.spsAPIKey, - this.screenProctoringSettings.spsAPISecret); + spsAPIAccessData.getSpsAPIKey(), + spsAPIAccessData.getSpsAPISecret()); CharSequence decryptedSecret = sebScreenProctoringService.cryptor .decrypt(this.clientCredentials.secret) .getOrThrow(); this.resource = new ResourceOwnerPasswordResourceDetails(); - this.resource.setAccessTokenUri(this.screenProctoringSettings.spsServiceURL + SPS_API.TOKEN_ENDPOINT); + this.resource.setAccessTokenUri(spsAPIAccessData.getSpsServiceURL() + SPS_API.TOKEN_ENDPOINT); this.resource.setClientId(this.clientCredentials.clientIdAsString()); this.resource.setClientSecret(decryptedSecret.toString()); this.resource.setGrantType(GRANT_TYPE); this.resource.setScope(SCOPES); - this.userCredentials = new ClientCredentials( - this.screenProctoringSettings.spsAccountId, - this.screenProctoringSettings.spsAccountPassword); + spsAPIAccessData.getSpsAccountId(), + spsAPIAccessData.getSpsAccountPassword()); decryptedSecret = sebScreenProctoringService.cryptor .decrypt(this.userCredentials.secret) @@ -976,7 +1032,7 @@ class ScreenProctoringAPIBinding { try { final String url = UriComponentsBuilder - .fromUriString(this.screenProctoringSettings.spsServiceURL) + .fromUriString(this.spsAPIAccessData.getSpsServiceURL()) .path(SPS_API.TEST_ENDPOINT) .queryParam("pageSize", "1") .queryParam("pageNumber", "1") @@ -994,7 +1050,7 @@ class ScreenProctoringAPIBinding { boolean isValid(final Long examId) { - if (this.screenProctoringSettings.examId != examId) { + if (!Objects.equals(this.spsAPIAccessData.getExamId(), examId)) { return false; } @@ -1010,12 +1066,8 @@ class ScreenProctoringAPIBinding { return false; } - final int expiresIn = accessToken.getExpiresIn(); - if (expiresIn < 60) { - return false; - } + return accessToken.getExpiresIn() >= 60; - return true; } catch (final Exception e) { log.error("Failed to verify SEB Screen Proctoring OAuth2RestTemplate status", e); return false; @@ -1092,14 +1144,10 @@ class ScreenProctoringAPIBinding { public static final String ATTR_SPS_ACTIVE = "spsExamActive"; public static final String ATTR_SPS_ACCESS_DATA = "spsAccessData"; - @JsonProperty("spsUserUUID") - String spsUserUUID = null; - @JsonProperty("spsUserName") - String spsUserName = null; @JsonProperty("spsUserPWD") String spsUserPWD = null; - @JsonProperty("spsSEBAccesUUID") - String spsSEBAccesUUID = null; + @JsonProperty("spsSEBAccessUUID") + String spsSEBAccessUUID = null; @JsonProperty("spsSEBAccessName") String spsSEBAccessName = null; @JsonProperty("spsSEBAccessPWD") @@ -1112,18 +1160,16 @@ class ScreenProctoringAPIBinding { @JsonCreator public SPSData( - @JsonProperty("spsUserUUID") final String spsUserUUID, - @JsonProperty("spsUserName") final String spsUserName, @JsonProperty("spsUserPWD") final String spsUserPWD, + @JsonProperty("spsSEBAccessUUID") final String spsSEBAccessUUID, + // NOTE: this is only for compatibility reasons, TODO as soon as possible @JsonProperty("spsSEBAccesUUID") final String spsSEBAccesUUID, @JsonProperty("spsSEBAccessName") final String spsSEBAccessName, @JsonProperty("spsSEBAccessPWD") final String spsSEBAccessPWD, @JsonProperty("psExamUUID") final String spsExamUUID) { - this.spsUserUUID = spsUserUUID; - this.spsUserName = spsUserName; this.spsUserPWD = spsUserPWD; - this.spsSEBAccesUUID = spsSEBAccesUUID; + this.spsSEBAccessUUID = StringUtils.isNotBlank(spsSEBAccesUUID) ? spsSEBAccesUUID : spsSEBAccessUUID; this.spsSEBAccessName = spsSEBAccessName; this.spsSEBAccessPWD = spsSEBAccessPWD; this.spsExamUUID = spsExamUUID; diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ScreenProctoringServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ScreenProctoringServiceImpl.java index fd68c1df..a33f4fa8 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ScreenProctoringServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ScreenProctoringServiceImpl.java @@ -15,6 +15,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.Map; +import ch.ethz.seb.sebserver.webservice.WebserviceInfo; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; @@ -59,7 +60,6 @@ public class ScreenProctoringServiceImpl implements ScreenProctoringService { private static final Logger log = LoggerFactory.getLogger(ScreenProctoringServiceImpl.class); private final Cryptor cryptor; - private final JSONMapper jsonMapper; private final ScreenProctoringAPIBinding screenProctoringAPIBinding; private final ScreenProctoringGroupDAO screenProctoringGroupDAO; private final ProctoringSettingsDAO proctoringSettingsDAO; @@ -67,6 +67,7 @@ public class ScreenProctoringServiceImpl implements ScreenProctoringService { private final ExamDAO examDAO; private final SEBClientInstructionService sebInstructionService; private final ExamSessionCacheService examSessionCacheService; + private final WebserviceInfo webserviceInfo; public ScreenProctoringServiceImpl( final Cryptor cryptor, @@ -79,24 +80,25 @@ public class ScreenProctoringServiceImpl implements ScreenProctoringService { final AdditionalAttributesDAO additionalAttributesDAO, final ScreenProctoringGroupDAO screenProctoringGroupDAO, final SEBClientInstructionService sebInstructionService, - final ExamSessionCacheService examSessionCacheService) { + final ExamSessionCacheService examSessionCacheService, + final WebserviceInfo webserviceInfo) { this.cryptor = cryptor; - this.jsonMapper = jsonMapper; this.examDAO = examDAO; this.screenProctoringGroupDAO = screenProctoringGroupDAO; this.clientConnectionDAO = clientConnectionDAO; this.sebInstructionService = sebInstructionService; this.examSessionCacheService = examSessionCacheService; this.proctoringSettingsDAO = proctoringSettingsDAO; - + this.webserviceInfo = webserviceInfo; this.screenProctoringAPIBinding = new ScreenProctoringAPIBinding( userDAO, cryptor, asyncService, jsonMapper, proctoringSettingsDAO, - additionalAttributesDAO); + additionalAttributesDAO, + webserviceInfo); } @Override @@ -161,7 +163,7 @@ public class ScreenProctoringServiceImpl implements ScreenProctoringService { } @Override - public Result applyScreenProctoingForExam(final Long examId) { + public Result applyScreenProctoringForExam(final Long examId) { return this.examDAO .byPK(examId) @@ -187,7 +189,7 @@ public class ScreenProctoringServiceImpl implements ScreenProctoringService { } else if (!isEnabling && isSPSActive) { this.screenProctoringAPIBinding - .dispsoseScreenProctoring(exam) + .disposeScreenProctoring(exam) .onError(error -> log.error("Failed to dispose screen proctoring for exam: {}", exam, error)) @@ -205,7 +207,7 @@ public class ScreenProctoringServiceImpl implements ScreenProctoringService { } @Override - public Result updateExamOnScreenProctoingService(final Long examId) { + public Result updateExamOnScreenProctoringService(final Long examId) { return this.examDAO.byPK(examId) .map(exam -> { @@ -213,10 +215,10 @@ public class ScreenProctoringServiceImpl implements ScreenProctoringService { log.debug("Update changed exam attributes for screen proctoring: {}", exam); } - final String enabeld = exam.additionalAttributes + final String enabled = exam.additionalAttributes .get(ScreenProctoringSettings.ATTR_ENABLE_SCREEN_PROCTORING); - if (!BooleanUtils.toBoolean(enabeld)) { + if (!BooleanUtils.toBoolean(enabled)) { return exam; } @@ -239,7 +241,7 @@ public class ScreenProctoringServiceImpl implements ScreenProctoringService { .flatMap(this.clientConnectionDAO::getAllForScreenProctoringUpdate) .getOrThrow() .stream() - .forEach(cc -> applyScreenProctoringSession(cc)); + .forEach(this::applyScreenProctoringSession); } catch (final Exception e) { log.error("Failed to update active SEB connections for screen proctoring"); @@ -248,10 +250,10 @@ public class ScreenProctoringServiceImpl implements ScreenProctoringService { @Override public void notifyExamSaved(final Exam exam) { - final String enabeld = exam.additionalAttributes + final String enabled = exam.additionalAttributes .get(ScreenProctoringSettings.ATTR_ENABLE_SCREEN_PROCTORING); - if (!BooleanUtils.toBoolean(enabeld)) { + if (!BooleanUtils.toBoolean(enabled)) { return; } @@ -259,6 +261,16 @@ public class ScreenProctoringServiceImpl implements ScreenProctoringService { this.screenProctoringAPIBinding.createExamReadPrivileges(exam); } + @Override + public void synchronizeSPSUser(final String userUUID) { + + if (!webserviceInfo.getScreenProctoringServiceBundle().bundled) { + return; + } + + this.screenProctoringAPIBinding.synchronizeUserAccount(userUUID); + } + @Override public void notifyExamStarted(final ExamStartedEvent event) { final Exam exam = event.exam; @@ -321,7 +333,7 @@ public class ScreenProctoringServiceImpl implements ScreenProctoringService { .getOrThrow(); } catch (final Exception e) { - log.error("Failed to apply screen proctoring session to SEB with connection: ", ccRecord, e); + log.error("Failed to apply screen proctoring session to SEB with connection: {}", ccRecord, e); if (placeReservedInGroup != null) { // release reserved place in group @@ -361,13 +373,13 @@ public class ScreenProctoringServiceImpl implements ScreenProctoringService { } private ScreenProctoringGroup applyToDefaultGroup( - final Long connectioId, + final Long connectionId, final String connectionToken, final Exam exam) { final ScreenProctoringGroup screenProctoringGroup = reservePlaceOnProctoringGroup(exam); this.clientConnectionDAO.assignToScreenProctoringGroup( - connectioId, + connectionId, connectionToken, screenProctoringGroup.id) .getOrThrow(); @@ -405,7 +417,9 @@ public class ScreenProctoringServiceImpl implements ScreenProctoringService { private ScreenProctoringGroup applyNewGroup(final Exam exam, final Integer groupSize) { - final String spsExamUUID = this.getSPSData(exam).spsExamUUID; + final String spsExamUUID = this.screenProctoringAPIBinding + .getSPSData(exam.id) + .spsExamUUID; return this.screenProctoringGroupDAO .getCollectingGroups(exam.id) @@ -458,7 +472,7 @@ public class ScreenProctoringServiceImpl implements ScreenProctoringService { log.debug("Register JOIN instruction for client "); } - final SPSData spsData = getSPSData(exam); + final SPSData spsData = this.screenProctoringAPIBinding.getSPSData(exam.id); final String url = exam.additionalAttributes.get(ScreenProctoringSettings.ATTR_SPS_SERVICE_URL); final Map attributes = new HashMap<>(); @@ -483,21 +497,4 @@ public class ScreenProctoringServiceImpl implements ScreenProctoringService { ccRecord, error)); } - - // TODO make this with caching if performance is not good - private SPSData getSPSData(final Exam exam) { - try { - - final String dataEncrypted = exam.additionalAttributes.get(SPSData.ATTR_SPS_ACCESS_DATA); - - return this.jsonMapper.readValue( - this.cryptor.decrypt(dataEncrypted).getOrThrow().toString(), - SPSData.class); - - } catch (final Exception e) { - log.error("Failed to get local SPSData for exam: {}", exam); - return null; - } - } - } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/UserAccountController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/UserAccountController.java index 18b84619..4e1feb6e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/UserAccountController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/UserAccountController.java @@ -15,6 +15,7 @@ import java.util.List; import javax.validation.Valid; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.ScreenProctoringService; import org.mybatis.dynamic.sql.SqlTable; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.ApplicationEventPublisher; @@ -61,6 +62,7 @@ public class UserAccountController extends ActivatableEntityController notifySaved(final UserInfo entity) { + final Result userInfoResult = super.notifySaved(entity); + this.synchronizeUserWithSPS(entity); + return userInfoResult; + } + @RequestMapping( path = API.PASSWORD_PATH_SEGMENT, method = RequestMethod.PUT, @@ -159,6 +170,7 @@ public class UserAccountController extends ActivatableEntityController this.userDAO.changePassword(modelId, passwordChange.getNewPassword())) .flatMap(this::revokeAccessToken) .flatMap(e -> this.userActivityLogDAO.log(UserLogActivityType.PASSWORD_CHANGE, e)) + .map(this::synchronizeUserWithSPS) .getOrThrow(); } @@ -258,6 +270,10 @@ public class UserAccountController extends ActivatableEntityController