SEBSERV-435 improved SEB Server SPS user account sync

This commit is contained in:
anhefti 2023-11-29 14:21:28 +01:00
parent 6a0d53c8c4
commit 9bda8630f6
9 changed files with 81 additions and 14 deletions

View file

@ -180,7 +180,9 @@ public class MonitoringProctoringService {
proctoringGUIService, proctoringGUIService,
room)); room));
if (proctoringSettings.enabledFeatures.contains(ProctoringFeature.TOWN_HALL)) { if (BooleanUtils.isTrue(proctoringSettings.enableProctoring) &&
proctoringSettings.enabledFeatures.contains(ProctoringFeature.TOWN_HALL)) {
updateTownhallButton(proctoringGUIService, pageContext); updateTownhallButton(proctoringGUIService, pageContext);
} }

View file

@ -499,8 +499,6 @@ public class UserDAOImpl implements UserDAO {
this.userRecordMapper this.userRecordMapper
.selectByExample() .selectByExample()
.where(UserRecordDynamicSqlSupport.username, isEqualTo(username)) .where(UserRecordDynamicSqlSupport.username, isEqualTo(username))
.and(UserRecordDynamicSqlSupport.active,
isEqualTo(BooleanUtils.toInteger(true)))
.build() .build()
.execute()); .execute());
} }

View file

@ -90,4 +90,7 @@ public interface ScreenProctoringService extends SessionUpdateTask {
@Async(AsyncServiceSpringConfig.EXECUTOR_BEAN_NAME) @Async(AsyncServiceSpringConfig.EXECUTOR_BEAN_NAME)
void synchronizeSPSUser(final String userUUID); void synchronizeSPSUser(final String userUUID);
@Async(AsyncServiceSpringConfig.EXECUTOR_BEAN_NAME)
void deleteSPSUser(String userUUID);
} }

View file

@ -494,8 +494,9 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
.getConnectionData(connectionToken) .getConnectionData(connectionToken)
.getOrThrow(); .getOrThrow();
// A connection can only be disabled if we have a missing ping // A connection can only be disabled if we have a missing ping or for closed connections
if (!BooleanUtils.isTrue(connectionData.getMissingPing())) { if (connectionData.clientConnection.status != ConnectionStatus.CLOSED &&
!BooleanUtils.isTrue(connectionData.getMissingPing())) {
return connectionData.clientConnection; return connectionData.clientConnection;
} }

View file

@ -363,7 +363,7 @@ class ScreenProctoringAPIBinding {
.toUriString(); .toUriString();
final ResponseEntity<String> exchange = apiTemplate.exchange( final ResponseEntity<String> exchange = apiTemplate.exchange(
uri, HttpMethod.POST, null, apiTemplate.getHeaders()); uri, HttpMethod.GET, null, apiTemplate.getHeaders());
if (exchange.getStatusCode() == HttpStatus.OK) { if (exchange.getStatusCode() == HttpStatus.OK) {
log.info("Synchronize SPS user account for SEB Server user account with id: {} ", userUUID); log.info("Synchronize SPS user account for SEB Server user account with id: {} ", userUUID);
@ -386,6 +386,30 @@ class ScreenProctoringAPIBinding {
log.error("Failed to synchronize user accounts with SPS for exam: {}", exam); log.error("Failed to synchronize user accounts with SPS for exam: {}", exam);
} }
} }
void deleteSPSUser(final String userUUID) {
try {
final ScreenProctoringServiceOAuthTemplate apiTemplate = this.getAPITemplate(null);
final String uri = UriComponentsBuilder
.fromUriString(apiTemplate.spsAPIAccessData.getSpsServiceURL())
.path(SPS_API.USER_ACCOUNT_ENDPOINT + userUUID)
.build()
.toUriString();
final ResponseEntity<String> exchange = apiTemplate.exchange(
uri, HttpMethod.DELETE, null, apiTemplate.getHeaders());
if (exchange.getStatusCode() == HttpStatus.OK) {
log.info("Successfully deleted User Account on SPS for user: {}", userUUID);
} else {
log.error("Failed to delete user account on SPS for user: {} response: {}", userUUID, exchange);
}
} catch (final Exception e) {
log.error("Failed to delete user account on SPS for user: {}", userUUID);
}
}
/** This is called when an exam has changed its parameter and needs data update on SPS side /** This is called when an exam has changed its parameter and needs data update on SPS side
* *
@ -583,7 +607,7 @@ class ScreenProctoringAPIBinding {
.byModelId(userUUID) .byModelId(userUUID)
.getOrThrow(); .getOrThrow();
final SEBServerUser accountInfo = this.userDAO final SEBServerUser accountInfo = this.userDAO
.sebServerUserByUsername(userInfo.name) .sebServerUserByUsername(userInfo.username)
.getOrThrow(); .getOrThrow();
final UserMod userMod = getUserModifications(userInfo, accountInfo); final UserMod userMod = getUserModifications(userInfo, accountInfo);
@ -600,8 +624,24 @@ class ScreenProctoringAPIBinding {
if (exchange.getStatusCode() != HttpStatus.OK) { if (exchange.getStatusCode() != HttpStatus.OK) {
log.warn("Failed to synchronize user account on SPS: {}", exchange); log.warn("Failed to synchronize user account on SPS: {}", exchange);
} else { } else {
log.info("Successfully synchronize user account on SPS for user: "); log.info("Successfully synchronize user account on SPS for user: {}", userUUID);
}
// sync activity
final String activityURI = UriComponentsBuilder
.fromUriString(apiTemplate.spsAPIAccessData.getSpsServiceURL())
.path(SPS_API.USER_ACCOUNT_ENDPOINT)
.pathSegment(userUUID)
.path(BooleanUtils.isTrue(userInfo.active) ? "/active" : "/inactive")
.build()
.toUriString();
final ResponseEntity<String> activityRequest = apiTemplate.exchange(
activityURI, HttpMethod.POST, jsonBody, apiTemplate.getHeaders());
if (activityRequest.getStatusCode() != HttpStatus.OK) {
log.warn("Failed to synchronize activity for user account on SPS: {}", activityRequest);
} else {
log.info("Successfully synchronize activity for user account on SPS for user: {}", userUUID);
} }
} catch (final Exception e) { } catch (final Exception e) {

View file

@ -15,12 +15,14 @@ import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import ch.ethz.seb.sebserver.gbl.async.AsyncServiceSpringConfig;
import ch.ethz.seb.sebserver.webservice.WebserviceInfo; import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import ch.ethz.seb.sebserver.gbl.api.APIMessage; import ch.ethz.seb.sebserver.gbl.api.APIMessage;
@ -262,6 +264,7 @@ public class ScreenProctoringServiceImpl implements ScreenProctoringService {
} }
@Override @Override
@Async(AsyncServiceSpringConfig.EXECUTOR_BEAN_NAME)
public void synchronizeSPSUser(final String userUUID) { public void synchronizeSPSUser(final String userUUID) {
if (!webserviceInfo.getScreenProctoringServiceBundle().bundled) { if (!webserviceInfo.getScreenProctoringServiceBundle().bundled) {
@ -271,6 +274,17 @@ public class ScreenProctoringServiceImpl implements ScreenProctoringService {
this.screenProctoringAPIBinding.synchronizeUserAccount(userUUID); this.screenProctoringAPIBinding.synchronizeUserAccount(userUUID);
} }
@Override
@Async(AsyncServiceSpringConfig.EXECUTOR_BEAN_NAME)
public void deleteSPSUser(final String userUUID) {
if (!webserviceInfo.getScreenProctoringServiceBundle().bundled) {
return;
}
this.screenProctoringAPIBinding.deleteSPSUser(userUUID);
}
@Override @Override
public void notifyExamStarted(final ExamStartedEvent event) { public void notifyExamStarted(final ExamStartedEvent event) {
final Exam exam = event.exam; final Exam exam = event.exam;

View file

@ -15,6 +15,8 @@ import java.util.List;
import javax.validation.Valid; import javax.validation.Valid;
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
import ch.ethz.seb.sebserver.gbl.util.Pair;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ScreenProctoringService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ScreenProctoringService;
import org.mybatis.dynamic.sql.SqlTable; import org.mybatis.dynamic.sql.SqlTable;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
@ -156,6 +158,13 @@ public class UserAccountController extends ActivatableEntityController<UserInfo,
return userInfoResult; return userInfoResult;
} }
@Override
protected Result<Pair<UserInfo, EntityProcessingReport>> notifyDeleted(final Pair<UserInfo, EntityProcessingReport> pair) {
final Result<Pair<UserInfo, EntityProcessingReport>> result = super.notifyDeleted(pair);
this.screenProctoringService.deleteSPSUser(pair.a.uuid);
return result;
}
@RequestMapping( @RequestMapping(
path = API.PASSWORD_PATH_SEGMENT, path = API.PASSWORD_PATH_SEGMENT,
method = RequestMethod.PUT, method = RequestMethod.PUT,

View file

@ -64,6 +64,6 @@ management.endpoints.web.exposure.include=logfile,loggers,jolokia
management.endpoints.web.path-mapping.jolokia=jmx management.endpoints.web.path-mapping.jolokia=jmx
sebserver.feature.seb.screenProctoring.bundled=true sebserver.feature.seb.screenProctoring.bundled=true
sebserver.feature.seb.screenProctoring.bundled.url=localhost:8090 sebserver.feature.seb.screenProctoring.bundled.url=http://localhost:8090
sebserver.feature.seb.screenProctoring.bundled.clientId=sebserverClient sebserver.feature.seb.screenProctoring.bundled.clientId=sebserverClient
sebserver.feature.seb.screenProctoring.bundled.sebserveraccount.username=SEBServerAPIAccount sebserver.feature.seb.screenProctoring.bundled.sebserveraccount.username=SEBServerAPIAccount

View file

@ -2437,7 +2437,7 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
connections = connectionsCall.get(); connections = connectionsCall.get();
assertFalse(connections.isEmpty()); assertFalse(connections.isEmpty());
conData = connections.iterator().next(); conData = connections.iterator().next();
assertEquals("CLOSED", conData.clientConnection.status.name()); assertEquals("DISABLED", conData.clientConnection.status.name());
// get client logs // get client logs
final Result<Page<ExtendedClientEvent>> clientLogPage = restService final Result<Page<ExtendedClientEvent>> clientLogPage = restService
@ -2520,7 +2520,7 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
assertFalse(ccDataPage.content.isEmpty()); assertFalse(ccDataPage.content.isEmpty());
final ClientConnectionData clientConnectionData = ccDataPage.content.get(0); final ClientConnectionData clientConnectionData = ccDataPage.content.get(0);
assertNotNull(clientConnectionData); assertNotNull(clientConnectionData);
assertEquals("CLOSED", clientConnectionData.clientConnection.status.toString()); assertEquals("DISABLED", clientConnectionData.clientConnection.status.toString());
connectionDatacall = restService connectionDatacall = restService
.getBuilder(GetFinishedExamClientConnectionPage.class) .getBuilder(GetFinishedExamClientConnectionPage.class)
@ -2552,9 +2552,9 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
@Order(18) @Order(18)
// ************************************* // *************************************
// Use Case 18: Login as examAdmin2 and get dependencies of examAdmin2 // Use Case 18: Login as examAdmin2 and get dependencies of examAdmin2
// - Get all dependencies and check correctnes. // - Get all dependencies and check correctness.
// - Get all dependencies including only Exam Configuration and check correctnes. // - Get all dependencies including only Exam Configuration and check correctness.
// - Get all dependencies including only ClientConnection and check correctnes. // - Get all dependencies including only ClientConnection and check correctness.
public void testUsecase18_UserDependencies() throws IOException { public void testUsecase18_UserDependencies() throws IOException {
final RestServiceImpl restService = createRestServiceForUser( final RestServiceImpl restService = createRestServiceForUser(
"examAdmin2", "examAdmin2",