SEBSERV-417 and SEBSP-111

This commit is contained in:
anhefti 2024-06-12 15:36:35 +02:00
parent 3a5129f796
commit c161e3c5ef
13 changed files with 414 additions and 168 deletions

View file

@ -20,6 +20,8 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.impl.SEBServe
public interface UserService { public interface UserService {
String USERS_INSTITUTION_AS_DEFAULT = "USERS_INSTITUTION_AS_DEFAULT"; String USERS_INSTITUTION_AS_DEFAULT = "USERS_INSTITUTION_AS_DEFAULT";
String LMS_INTEGRATION_CLIENT_UUID = "LMS_INTEGRATION_CLIENT";
String LMS_INTEGRATION_CLIENT_NAME = "lmsIntegrationClient";
/** Use this to get the current User within a request-response thread cycle. /** Use this to get the current User within a request-response thread cycle.
* *

View file

@ -38,6 +38,7 @@ public class UserServiceImpl implements UserService {
private static final Logger log = LoggerFactory.getLogger(UserServiceImpl.class); private static final Logger log = LoggerFactory.getLogger(UserServiceImpl.class);
public interface ExtractUserFromAuthenticationStrategy { public interface ExtractUserFromAuthenticationStrategy {
SEBServerUser extract(Principal principal); SEBServerUser extract(Principal principal);
} }
@ -137,7 +138,13 @@ public class UserServiceImpl implements UserService {
if ("lmsClient".equals(name)) { if ("lmsClient".equals(name)) {
return new SEBServerUser( return new SEBServerUser(
-1L, -1L,
new UserInfo("LMS_INTEGRATION_CLIENT", -1L, null, "lmsIntegrationClient", "lmsIntegrationClient", "lmsIntegrationClient", null, new UserInfo(
LMS_INTEGRATION_CLIENT_UUID,
-1L,
null,
LMS_INTEGRATION_CLIENT_NAME,
LMS_INTEGRATION_CLIENT_NAME,
LMS_INTEGRATION_CLIENT_NAME, null,
false, false,
false, false,
true, true,

View file

@ -52,7 +52,12 @@ public interface LmsSetupDAO extends ActivatableEntityDAO<LmsSetup, LmsSetup>, B
* @return Result refers to the specified LMS Setup or to en error when happened */ * @return Result refers to the specified LMS Setup or to en error when happened */
Result<LmsSetup> setIntegrationActive(Long lmsSetupId, boolean active); Result<LmsSetup> setIntegrationActive(Long lmsSetupId, boolean active);
boolean isIntegrationActive(Long lmsSetupId);
Result<Collection<Long>> idsOfActiveWithFullIntegration(Long institutionId); Result<Collection<Long>> idsOfActiveWithFullIntegration(Long institutionId);
Result<Collection<Long>> allIdsFullIntegration(); Result<Collection<Long>> allIdsFullIntegration();
} }

View file

@ -317,6 +317,21 @@ public class LmsSetupDAOImpl implements LmsSetupDAO {
.onError(TransactionHandler::rollback); .onError(TransactionHandler::rollback);
} }
@Override
@Transactional(readOnly = true)
public boolean isIntegrationActive(final Long lmsSetupId) {
try {
return this.lmsSetupRecordMapper.countByExample()
.where(LmsSetupRecordDynamicSqlSupport.id, isEqualTo(lmsSetupId))
.and(integrationActive, isEqualTo(BooleanUtils.toInteger(true)))
.build()
.execute() > 0;
} catch (final Exception e) {
log.warn("Failed to verify if full LMS integration is active: {}", e.getMessage());
return false;
}
}
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public boolean isActive(final String modelId) { public boolean isActive(final String modelId) {

View file

@ -32,7 +32,7 @@ public interface FullLmsIntegrationService {
@EventListener @EventListener
void notifyLmsSetupChange(final LmsSetupChangeEvent event); void notifyLmsSetupChange(final LmsSetupChangeEvent event);
Result<LmsSetup> applyLMSSetupDeactivation(LmsSetup lmsSetup); //Result<LmsSetup> applyLMSSetupDeactivation(LmsSetup lmsSetup);
@EventListener @EventListener
void notifyExamTemplateChange(final ExamTemplateChangeEvent event); void notifyExamTemplateChange(final ExamTemplateChangeEvent event);

View file

@ -73,6 +73,6 @@ public interface SEBRestrictionService {
@EventListener @EventListener
void notifyLmsSetupChange(final LmsSetupChangeEvent event); void notifyLmsSetupChange(final LmsSetupChangeEvent event);
Result<LmsSetup> applyLMSSetupDeactivation(LmsSetup lmsSetup); Result<LmsSetup> releaseAllRestrictionsOf(LmsSetup lmsSetup);
} }

View file

@ -148,7 +148,7 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
return Result.tryCatch(() -> { return Result.tryCatch(() -> {
final LmsSetup lmsSetup = lmsSetupDAO.byPK(exam.lmsSetupId).getOrThrow(); final LmsSetup lmsSetup = lmsSetupDAO.byPK(exam.lmsSetupId).getOrThrow();
if (lmsSetup.lmsType.features.contains(LmsSetup.Features.LMS_FULL_INTEGRATION)) { if (lmsSetup.lmsType.features.contains(LmsSetup.Features.LMS_FULL_INTEGRATION)) {
return this.applyExamData(exam, false); return this.applyExamData(exam, !exam.active);
} }
return exam; return exam;
@ -183,17 +183,7 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
.map(data -> reapplyExistingExams(data,lmsSetup)) .map(data -> reapplyExistingExams(data,lmsSetup))
.onError(error -> log.warn("Failed to update LMS integration for: {} error {}", lmsSetup, error.getMessage())) .onError(error -> log.warn("Failed to update LMS integration for: {} error {}", lmsSetup, error.getMessage()))
.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 (event.activation == Activatable.ActivationAction.DEACTIVATE) {
}
@Override
public Result<LmsSetup> applyLMSSetupDeactivation(final LmsSetup lmsSetup) {
if (!lmsSetup.getLmsType().features.contains(LmsSetup.Features.LMS_FULL_INTEGRATION)) {
return Result.of(lmsSetup);
}
return Result.tryCatch(() -> {
// remove all active exam data for involved exams before deactivate them // remove all active exam data for involved exams before deactivate them
this.examDAO this.examDAO
.allActiveForLMSSetup(Arrays.asList(lmsSetup.id)) .allActiveForLMSSetup(Arrays.asList(lmsSetup.id))
@ -203,13 +193,10 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
.map(e -> applyExamData(e, true)) .map(e -> applyExamData(e, true))
.onError(error -> log.warn("Failed delete teacher accounts for exam: {}", exam.name)); .onError(error -> log.warn("Failed delete teacher accounts for exam: {}", exam.name));
}); });
// delete full integration on Moodle side due to deactivation
// delete full integration on Moodle side before deactivate LMS Setup
this.deleteFullLmsIntegration(lmsSetup.id) this.deleteFullLmsIntegration(lmsSetup.id)
.getOrThrow(); .getOrThrow();
}
return lmsSetup;
});
} }
@Override @Override
@ -302,6 +289,10 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
return false; return false;
} }
if (!lmsSetupDAO.isIntegrationActive(lmsSetupId)) {
return true;
}
lmsAPITemplateCacheService.getLmsAPITemplate(lmsSetupId) lmsAPITemplateCacheService.getLmsAPITemplate(lmsSetupId)
.getOrThrow() .getOrThrow()
.deleteConnectionDetails() .deleteConnectionDetails()

View file

@ -114,6 +114,7 @@ public class SEBRestrictionServiceImpl implements SEBRestrictionService {
if (!lmsSetup.lmsType.features.contains(Features.SEB_RESTRICTION)) { if (!lmsSetup.lmsType.features.contains(Features.SEB_RESTRICTION)) {
return; return;
} }
try { try {
if (event.activation == Activatable.ActivationAction.ACTIVATE) { if (event.activation == Activatable.ActivationAction.ACTIVATE) {
examDAO.allActiveForLMSSetup(Arrays.asList(lmsSetup.id)) examDAO.allActiveForLMSSetup(Arrays.asList(lmsSetup.id))
@ -125,6 +126,11 @@ public class SEBRestrictionServiceImpl implements SEBRestrictionService {
log.warn("Failed to update SEB restriction for exam: {} error: {}", exam.name, e.getMessage()); log.warn("Failed to update SEB restriction for exam: {} error: {}", exam.name, e.getMessage());
} }
}); });
} else if (event.activation == Activatable.ActivationAction.DEACTIVATE) {
releaseAllRestrictionsOf(lmsSetup)
.onError(error -> log.warn(
"Failed to remove all SEB Restrictions on LMS Setup deactivation: {}",
error.getMessage()));
} }
} catch (final Exception e) { } catch (final Exception e) {
log.error("Failed to update SEB restriction for re-activated exams: {}", e.getMessage()); log.error("Failed to update SEB restriction for re-activated exams: {}", e.getMessage());
@ -132,8 +138,7 @@ public class SEBRestrictionServiceImpl implements SEBRestrictionService {
} }
@Override @Override
public Result<LmsSetup> applyLMSSetupDeactivation(final LmsSetup lmsSetup) { public Result<LmsSetup> releaseAllRestrictionsOf(final LmsSetup lmsSetup) {
return Result.tryCatch(() -> { return Result.tryCatch(() -> {
// only relevant for LMS Setups with SEB restriction feature // only relevant for LMS Setups with SEB restriction feature
if (!lmsSetup.lmsType.features.contains(Features.SEB_RESTRICTION)) { if (!lmsSetup.lmsType.features.contains(Features.SEB_RESTRICTION)) {
@ -274,7 +279,6 @@ public class SEBRestrictionServiceImpl implements SEBRestrictionService {
// create new ones if needed // create new ones if needed
sebRestriction.additionalProperties sebRestriction.additionalProperties
.entrySet() .entrySet()
.stream()
.forEach(entry -> this.additionalAttributesDAO.saveAdditionalAttribute( .forEach(entry -> this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.EXAM, EntityType.EXAM,
exam.id, exam.id,
@ -332,7 +336,7 @@ public class SEBRestrictionServiceImpl implements SEBRestrictionService {
log.debug("ExamDeletionEvent received, process releaseSEBClientRestriction..."); log.debug("ExamDeletionEvent received, process releaseSEBClientRestriction...");
} }
event.ids.stream().forEach(this::processExamDeletion); event.ids.forEach(this::processExamDeletion);
} }
private Result<Exam> processExamDeletion(final Long examId) { private Result<Exam> processExamDeletion(final Long examId) {

View file

@ -12,6 +12,8 @@ import java.util.Collection;
import ch.ethz.seb.sebserver.gbl.async.AsyncServiceSpringConfig; import ch.ethz.seb.sebserver.gbl.async.AsyncServiceSpringConfig;
import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.LmsSetupChangeEvent;
import org.springframework.context.event.EventListener; import org.springframework.context.event.EventListener;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
@ -76,6 +78,9 @@ public interface ScreenProctoringService extends SessionUpdateTask {
@EventListener(ExamDeletionEvent.class) @EventListener(ExamDeletionEvent.class)
void notifyExamDeletion(final ExamDeletionEvent event); void notifyExamDeletion(final ExamDeletionEvent event);
@EventListener
void notifyLmsSetupChange(final LmsSetupChangeEvent event);
/** This is used to update the exam equivalent on the screen proctoring service side /** This is used to update the exam equivalent on the screen proctoring service side
* if screen proctoring is enabled for the specified exam. * if screen proctoring is enabled for the specified exam.
* *
@ -93,6 +98,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) @Async(AsyncServiceSpringConfig.EXECUTOR_BEAN_NAME)
void synchronizeSPSUserForExam(final Long examId); void synchronizeSPSUserForExam(final Long examId);

View file

@ -10,9 +10,11 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.proctoring;
import java.util.*; import java.util.*;
import ch.ethz.seb.sebserver.gbl.model.Page;
import ch.ethz.seb.sebserver.gbl.model.exam.SPSAPIAccessData; import ch.ethz.seb.sebserver.gbl.model.exam.SPSAPIAccessData;
import ch.ethz.seb.sebserver.gbl.model.user.UserRole; import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
import ch.ethz.seb.sebserver.webservice.WebserviceInfo; import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
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;
@ -111,6 +113,7 @@ class ScreenProctoringAPIBinding {
/** The screen proctoring service group API attribute names */ /** The screen proctoring service group API attribute names */
interface EXAM { interface EXAM {
String ATTR_ID = "id";
String ATTR_UUID = "uuid"; String ATTR_UUID = "uuid";
String ATTR_SEB_SERVER_ID = "sebserverId"; String ATTR_SEB_SERVER_ID = "sebserverId";
String ATTR_NAME = "name"; String ATTR_NAME = "name";
@ -269,7 +272,7 @@ class ScreenProctoringAPIBinding {
SPSData.class); SPSData.class);
} catch (final Exception e) { } catch (final Exception e) {
log.error("Failed to get local SPSData for exam: {}", examId); log.warn("Failed to get local SPSData for exam: {}", examId);
return null; return null;
} }
} }
@ -290,7 +293,9 @@ class ScreenProctoringAPIBinding {
} }
final ScreenProctoringServiceOAuthTemplate apiTemplate = this.getAPITemplate(exam.id); final ScreenProctoringServiceOAuthTemplate apiTemplate = this.getAPITemplate(exam.id);
// if we have an exam where SPS was initialized before but deactivated meanwhile
// reactivate on SPS site and synchronize
if (exam.additionalAttributes.containsKey(SPSData.ATTR_SPS_ACTIVE)) { if (exam.additionalAttributes.containsKey(SPSData.ATTR_SPS_ACTIVE)) {
log.info("SPS Exam for SEB Server Exam: {} already exists. Try to re-activate", exam.externalId); log.info("SPS Exam for SEB Server Exam: {} already exists. Try to re-activate", exam.externalId);
@ -298,51 +303,55 @@ class ScreenProctoringAPIBinding {
final SPSData spsData = this.getSPSData(exam.id); final SPSData spsData = this.getSPSData(exam.id);
// re-activate all needed entities on SPS side // re-activate all needed entities on SPS side
if (exam.status == Exam.ExamStatus.RUNNING) { if (exam.status == Exam.ExamStatus.RUNNING) {
activation(exam, SPS_API.SEB_ACCESS_ENDPOINT, spsData.spsSEBAccessUUID, true, apiTemplate); activateScreenProctoring(exam).getOrThrow();
activation(exam, SPS_API.EXAM_ENDPOINT, spsData.spsExamUUID, true, apiTemplate);
} }
synchronizeUserAccounts(exam); synchronizeUserAccounts(exam);
// mark successfully activated on SPS side
this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.EXAM,
exam.id,
SPSData.ATTR_SPS_ACTIVE,
Constants.TRUE_STRING);
return Collections.emptyList(); return Collections.emptyList();
} }
final SPSData spsData = new SPSData(); // if we have a new Exam but Exam on SPS site for ExamUUID exists, reinitialize the exam and synchronize
log.info( if (existsExamOnSPS(exam)) {
"SPS Exam for SEB Server Exam: {} don't exists yet, create necessary structures on SPS", return reinitializeScreenProctoring(exam);
exam.externalId); }
synchronizeUserAccounts(exam); // If this is a completely new exam with new SPS binding, initialize it
createSEBAccess(exam, apiTemplate, spsData); return initializeScreenProctoring(exam, apiTemplate);
createExam(exam, apiTemplate, spsData);
final Collection<ScreenProctoringGroup> initializeGroups = initializeGroups(exam, apiTemplate, spsData);
// store encrypted spsData
final String spsDataJSON = this.jsonMapper.writeValueAsString(spsData);
this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.EXAM,
exam.id,
SPSData.ATTR_SPS_ACCESS_DATA,
this.cryptor.encrypt(spsDataJSON).getOrThrow().toString());
// mark successfully activated on SPS side
this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.EXAM,
exam.id,
SPSData.ATTR_SPS_ACTIVE,
Constants.TRUE_STRING);
return initializeGroups;
}); });
} }
boolean existsExamOnSPS(final Exam exam) {
try {
final ScreenProctoringServiceOAuthTemplate apiTemplate = this.getAPITemplate(exam.id);
final String uri = UriComponentsBuilder
.fromUriString(apiTemplate.spsAPIAccessData.getSpsServiceURL())
.path(SPS_API.EXAM_ENDPOINT)
.pathSegment(createExamUUID(exam))
.build().toUriString();
final ResponseEntity<String> exchange = apiTemplate.exchange(uri, HttpMethod.GET);
if (exchange.getStatusCode() != HttpStatus.NOT_FOUND) {
return false;
} else if (exchange.getStatusCode() != HttpStatus.OK) {
return true;
} else {
log.warn("Failed to verify if Exam on SPS already exists: {}", exchange.getBody());
return false;
}
} catch (final Exception e) {
log.error("Failed to verify if Exam exists already on SPS site: ", e);
return false;
}
}
void synchronizeUserAccount(final String userUUID) { void synchronizeUserAccount(final String userUUID) {
if (UserService.LMS_INTEGRATION_CLIENT_UUID.equals(userUUID)) {
return;
}
try { try {
final ScreenProctoringServiceOAuthTemplate apiTemplate = this.getAPITemplate(null); final ScreenProctoringServiceOAuthTemplate apiTemplate = this.getAPITemplate(null);
@ -446,22 +455,21 @@ class ScreenProctoringAPIBinding {
log.error("Failed to update SPS exam data: {}", exchange); log.error("Failed to update SPS exam data: {}", exchange);
} }
} catch (Exception e) { } catch (final Exception e) {
log.error("Failed to update exam on SPS service for exam: {}", exam, e); log.error("Failed to update exam on SPS service for exam: {}", exam, e);
} }
} }
/** This is called when an exam finishes and deactivates the Exam, SEB Client Access and the ad-hoc User-Account /** This is called when an exam finishes and deactivates the Exam, SEB Client Access on Screen Proctoring Service side.
* on Screen Proctoring Service side.
* *
* @param exam The exam * @param exam The exam
* @return Result refer to the exam or to an error when happened */ * @return Result refer to the exam or to an error when happened */
Result<Exam> disposeScreenProctoring(final Exam exam) { Result<Exam> deactivateScreenProctoring(final Exam exam) {
return Result.tryCatch(() -> { return Result.tryCatch(() -> {
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
log.debug("Dispose active screen proctoring exam, groups and access on SPS for exam: {}", exam); log.debug("Deactivate active screen proctoring exam, groups and access on SPS for exam: {}", exam.name);
} }
final SPSData spsData = this.getSPSData(exam.id); final SPSData spsData = this.getSPSData(exam.id);
@ -469,7 +477,7 @@ class ScreenProctoringAPIBinding {
activation(exam, SPS_API.SEB_ACCESS_ENDPOINT, spsData.spsSEBAccessUUID, false, apiTemplate); activation(exam, SPS_API.SEB_ACCESS_ENDPOINT, spsData.spsSEBAccessUUID, false, apiTemplate);
activation(exam, SPS_API.EXAM_ENDPOINT, spsData.spsExamUUID, false, apiTemplate); activation(exam, SPS_API.EXAM_ENDPOINT, spsData.spsExamUUID, false, apiTemplate);
// mark successfully dispose on SPS side // mark local for successfully dispose on SPS side
this.additionalAttributesDAO.saveAdditionalAttribute( this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.EXAM, EntityType.EXAM,
exam.id, exam.id,
@ -480,56 +488,197 @@ class ScreenProctoringAPIBinding {
}); });
} }
/** This is called on exam delete and deletes the SEB Client Access and the ad-hoc User-Account Result<Exam> activateScreenProctoring(final Exam exam) {
* 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 Result.tryCatch(() -> {
if (log.isDebugEnabled()) {
log.debug("Activate screen proctoring exam, groups and access on SPS for exam: {}", exam.name);
}
final SPSData spsData = this.getSPSData(exam.id);
if (spsData == null) {
return exam; 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 ScreenProctoringServiceOAuthTemplate apiTemplate = this.getAPITemplate(exam.id);
final SPSData spsData = this.getSPSData(exam.id); activation(exam, SPS_API.SEB_ACCESS_ENDPOINT, spsData.spsSEBAccessUUID, true, apiTemplate);
deletion(SPS_API.SEB_ACCESS_ENDPOINT, spsData.spsSEBAccessUUID, apiTemplate); activation(exam, SPS_API.EXAM_ENDPOINT, spsData.spsExamUUID, true, apiTemplate);
activation(exam, SPS_API.EXAM_ENDPOINT, spsData.spsExamUUID, false, apiTemplate);
// exam delete request on SPS // mark local for successfully activated on SPS side
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<String> 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( this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.EXAM, EntityType.EXAM,
exam.id, exam.id,
SPSData.ATTR_SPS_ACTIVE, SPSData.ATTR_SPS_ACTIVE,
Constants.FALSE_STRING); Constants.TRUE_STRING);
return exam;
});
}
private Collection<ScreenProctoringGroup> initializeScreenProctoring(
final Exam exam,
final ScreenProctoringServiceOAuthTemplate apiTemplate) throws JsonProcessingException {
final SPSData spsData = new SPSData();
log.info(
"SPS Exam for SEB Server Exam: {} don't exists yet, create necessary structures on SPS",
exam.externalId);
synchronizeUserAccounts(exam);
createSEBAccess(exam, apiTemplate, spsData);
createExam(exam, apiTemplate, spsData);
final Collection<ScreenProctoringGroup> initializeGroups = initializeGroups(exam, apiTemplate, spsData);
// store encrypted spsData
final String spsDataJSON = this.jsonMapper.writeValueAsString(spsData);
this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.EXAM,
exam.id,
SPSData.ATTR_SPS_ACCESS_DATA,
this.cryptor.encrypt(spsDataJSON).getOrThrow().toString());
// mark successfully activated on SPS side
this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.EXAM,
exam.id,
SPSData.ATTR_SPS_ACTIVE,
Constants.TRUE_STRING);
return initializeGroups;
}
Collection<ScreenProctoringGroup> reinitializeScreenProctoring(final Exam exam) {
try {
final ScreenProctoringServiceOAuthTemplate apiTemplate = this.getAPITemplate(exam.id);
// get exam from SPS
final String examUUID = createExamUUID(exam);
final String uri = UriComponentsBuilder
.fromUriString(apiTemplate.spsAPIAccessData.getSpsServiceURL())
.path(SPS_API.EXAM_ENDPOINT)
.pathSegment(examUUID)
.build().toUriString();
final ResponseEntity<String> exchange = apiTemplate.exchange(uri, HttpMethod.GET);
if (exchange.getStatusCode() != HttpStatus.OK) {
throw new RuntimeException("Failed to get Exam from SPS. local exam uuid: " + examUUID);
}
final JsonNode requestJSON = this.jsonMapper.readTree(exchange.getBody());
final String spsExamId = requestJSON.get(SPS_API.EXAM.ATTR_ID).textValue();
// check if Exam has SPSData, if not create and if check completeness
SPSData spsData = this.getSPSData(exam.id);
if (spsData == null) {
spsData = new SPSData();
}
// create new SEB Account on SPS if needed
if (spsData.spsSEBAccessUUID == null) {
createSEBAccess(exam, apiTemplate, spsData);
}
spsData.spsExamUUID = examUUID;
// store encrypted spsData
final String spsDataJSON = this.jsonMapper.writeValueAsString(spsData);
this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.EXAM,
exam.id,
SPSData.ATTR_SPS_ACCESS_DATA,
this.cryptor.encrypt(spsDataJSON).getOrThrow().toString());
// reactivate exam on SPS
this.activateScreenProctoring(exam);
// recreate groups on SEB Server if needed
try {
final Collection<ScreenProctoringGroup> groups = new ArrayList<>();
final String groupRequestURI = UriComponentsBuilder
.fromUriString(apiTemplate.spsAPIAccessData.getSpsServiceURL())
.path(SPS_API.GROUP_ENDPOINT)
.queryParam(Page.ATTR_PAGE_SIZE, 100)
.queryParam(SPS_API.GROUP.ATTR_EXAM_ID, spsExamId)
.build()
.toUriString();
final JsonNode groupsJSON = this.jsonMapper.readTree(exchange.getBody());
final JsonNode pageContent = groupsJSON.get("content");
if (pageContent.isArray()) {
for (final JsonNode group : pageContent) {
groups.add(new ScreenProctoringGroup(
null,
exam.id,
group.get(SPS_API.GROUP.ATTR_UUID).textValue(),
group.get(SPS_API.GROUP.ATTR_NAME).textValue(),
0,
null
));
}
}
return groups;
} catch (final Exception e) {
log.error("Failed to get exam groups from SPS due to reinitialization: ", e);
return initializeGroups(exam, apiTemplate, spsData);
}
} catch (final Exception e) { } catch (final Exception e) {
log.warn("Failed to apply SPS deletion of exam: {} error: {}", exam, e.getMessage()); log.error("Failed to re-initialize Screen Proctoring: ", e);
return Collections.emptyList();
} }
return exam;
} }
// /** 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<String> 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<ScreenProctoringGroup> createGroup( Result<ScreenProctoringGroup> createGroup(
final String spsExamUUID, final String spsExamUUID,
final int groupNumber, final int groupNumber,
@ -583,7 +732,7 @@ class ScreenProctoringAPIBinding {
params.add(SPS_API.SESSION.ATTR_CLIENT_VERSION, clientConnection.getClientVersion()); params.add(SPS_API.SESSION.ATTR_CLIENT_VERSION, clientConnection.getClientVersion());
final String paramsFormEncoded = Utils.toAppFormUrlEncodedBody(params); final String paramsFormEncoded = Utils.toAppFormUrlEncodedBody(params);
final ResponseEntity<String> exchange = apiTemplate.exchange(uri, paramsFormEncoded, HttpMethod.POST); final ResponseEntity<String> exchange = apiTemplate.exchange(uri, paramsFormEncoded);
if (exchange.getStatusCode() != HttpStatus.OK) { if (exchange.getStatusCode() != HttpStatus.OK) {
throw new RuntimeException( throw new RuntimeException(
"Failed to create SPS SEB session for SEB connection: " + token); "Failed to create SPS SEB session for SEB connection: " + token);
@ -592,17 +741,18 @@ class ScreenProctoringAPIBinding {
return token; return token;
} }
void activateSEBAccessOnSPS(final Exam exam, final boolean activate) { // void activateExamOnSPS(final Exam exam, final boolean activate) {
try { // try {
final ScreenProctoringServiceOAuthTemplate apiTemplate = this.getAPITemplate(exam.id); // final ScreenProctoringServiceOAuthTemplate apiTemplate = this.getAPITemplate(exam.id);
final SPSData spsData = this.getSPSData(exam.id); // final SPSData spsData = this.getSPSData(exam.id);
//
activation(exam, SPS_API.SEB_ACCESS_ENDPOINT, spsData.spsSEBAccessUUID, activate, apiTemplate); // 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); // } catch (final Exception e) {
} // log.error("Failed to de/activate SEB Access on SPS for exam: {}", exam);
} // }
// }
private void synchronizeUserAccount( private void synchronizeUserAccount(
final String userUUID, final String userUUID,
@ -758,17 +908,17 @@ class ScreenProctoringAPIBinding {
params.add(SPS_API.GROUP.ATTR_EXAM_ID, spsExamUUID); params.add(SPS_API.GROUP.ATTR_EXAM_ID, spsExamUUID);
final String paramsFormEncoded = Utils.toAppFormUrlEncodedBody(params); final String paramsFormEncoded = Utils.toAppFormUrlEncodedBody(params);
final ResponseEntity<String> exchange = apiTemplate.exchange(uri, paramsFormEncoded, HttpMethod.POST); final ResponseEntity<String> exchange = apiTemplate.exchange(uri, paramsFormEncoded);
if (exchange.getStatusCode() != HttpStatus.OK) { if (exchange.getStatusCode() != HttpStatus.OK) {
throw new RuntimeException("Failed to create SPS SEB group for exam: " + spsExamUUID); throw new RuntimeException("Failed to create SPS SEB group for exam: " + spsExamUUID);
} }
final Map<String, String> userAttributes = this.jsonMapper.readValue( final Map<String, String> groupAttributes = this.jsonMapper.readValue(
exchange.getBody(), exchange.getBody(),
new TypeReference<Map<String, String>>() { new TypeReference<Map<String, String>>() {
}); });
final String spsGroupUUID = userAttributes.get(SPS_API.GROUP.ATTR_UUID); final String spsGroupUUID = groupAttributes.get(SPS_API.GROUP.ATTR_UUID);
return new ScreenProctoringGroup(null, examId, spsGroupUUID, name, size, exchange.getBody()); return new ScreenProctoringGroup(null, examId, spsGroupUUID, name, size, exchange.getBody());
} }
@ -790,22 +940,11 @@ class ScreenProctoringAPIBinding {
.path(SPS_API.EXAM_ENDPOINT) .path(SPS_API.EXAM_ENDPOINT)
.build().toUriString(); .build().toUriString();
final MultiValueMap<String, String> params = new LinkedMultiValueMap<>(); final String uuid = createExamUUID(exam);
params.add(SPS_API.EXAM.ATTR_NAME, exam.name); final MultiValueMap<String, String> params = createExamCreationParams(exam, uuid, userIds);
params.add(SPS_API.EXAM.ATTR_DESCRIPTION, exam.getDescription());
params.add(SPS_API.EXAM.ATTR_URL, exam.getStartURL());
if (!userIds.isEmpty()) {
params.add(SPS_API.EXAM.ATTR_USER_IDS, StringUtils.join(userIds, Constants.LIST_SEPARATOR));
}
params.add(SPS_API.EXAM.ATTR_TYPE, exam.getType().name());
params.add(SPS_API.EXAM.ATTR_START_TIME, String.valueOf(exam.startTime.getMillis()));
if (exam.endTime != null) {
params.add(SPS_API.EXAM.ATTR_END_TIME, String.valueOf(exam.endTime.getMillis()));
}
final String paramsFormEncoded = Utils.toAppFormUrlEncodedBody(params); final String paramsFormEncoded = Utils.toAppFormUrlEncodedBody(params);
final ResponseEntity<String> exchange = apiTemplate.exchange(uri, paramsFormEncoded, HttpMethod.POST); final ResponseEntity<String> exchange = apiTemplate.exchange(uri, paramsFormEncoded);
if (exchange.getStatusCode() != HttpStatus.OK) { if (exchange.getStatusCode() != HttpStatus.OK) {
throw new RuntimeException("Error response from Screen Proctoring Service: " throw new RuntimeException("Error response from Screen Proctoring Service: "
+ exchange.getStatusCodeValue() + exchange.getStatusCodeValue()
@ -814,7 +953,14 @@ class ScreenProctoringAPIBinding {
} }
final JsonNode requestJSON = this.jsonMapper.readTree(exchange.getBody()); final JsonNode requestJSON = this.jsonMapper.readTree(exchange.getBody());
spsData.spsExamUUID = requestJSON.get(SPS_API.EXAM.ATTR_UUID).textValue(); final String respondedUUID = requestJSON.get(SPS_API.EXAM.ATTR_UUID).textValue();
if (!uuid.equals(respondedUUID)) {
log.warn("Detected Exam ({}) generation UUID mismatch. propagated UUID: {} responded UUID: {}",
exam.name,
uuid,
respondedUUID);
}
spsData.spsExamUUID = respondedUUID;
} catch (final Exception e) { } catch (final Exception e) {
log.error( log.error(
@ -826,6 +972,32 @@ class ScreenProctoringAPIBinding {
} }
} }
private static MultiValueMap<String, String> createExamCreationParams(
final Exam exam,
final String uuid,
final List<String> userIds) {
final MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add(SPS_API.EXAM.ATTR_UUID, uuid);
params.add(SPS_API.EXAM.ATTR_NAME, exam.name);
params.add(SPS_API.EXAM.ATTR_DESCRIPTION, exam.getDescription());
params.add(SPS_API.EXAM.ATTR_URL, exam.getStartURL());
if (!userIds.isEmpty()) {
params.add(SPS_API.EXAM.ATTR_USER_IDS, StringUtils.join(userIds, Constants.LIST_SEPARATOR));
}
params.add(SPS_API.EXAM.ATTR_TYPE, exam.getType().name());
params.add(SPS_API.EXAM.ATTR_START_TIME, String.valueOf(exam.startTime.getMillis()));
if (exam.endTime != null) {
params.add(SPS_API.EXAM.ATTR_END_TIME, String.valueOf(exam.endTime.getMillis()));
}
return params;
}
private String createExamUUID(final Exam exam) {
return exam.externalId;
}
private void createSEBAccess( private void createSEBAccess(
final Exam exam, final Exam exam,
final ScreenProctoringServiceOAuthTemplate apiTemplate, final ScreenProctoringServiceOAuthTemplate apiTemplate,
@ -846,7 +1018,7 @@ class ScreenProctoringAPIBinding {
params.add(SPS_API.SEB_ACCESS.ATTR_DESCRIPTION, description); params.add(SPS_API.SEB_ACCESS.ATTR_DESCRIPTION, description);
final String paramsFormEncoded = Utils.toAppFormUrlEncodedBody(params); final String paramsFormEncoded = Utils.toAppFormUrlEncodedBody(params);
final ResponseEntity<String> exchange = apiTemplate.exchange(uri, paramsFormEncoded, HttpMethod.POST); final ResponseEntity<String> exchange = apiTemplate.exchange(uri, paramsFormEncoded);
if (exchange.getStatusCode() != HttpStatus.OK) { if (exchange.getStatusCode() != HttpStatus.OK) {
throw new RuntimeException("Failed to create SPS SEB access for exam: " + exam.externalId); throw new RuntimeException("Failed to create SPS SEB access for exam: " + exam.externalId);
} }
@ -1099,10 +1271,9 @@ class ScreenProctoringAPIBinding {
ResponseEntity<String> exchange( ResponseEntity<String> exchange(
final String url, final String url,
final String body, final String body) {
final HttpMethod method) {
return exchange(url, method, body, getHeaders()); return exchange(url, HttpMethod.POST, body, getHeaders());
} }
HttpHeaders getHeadersJSONRequest() { HttpHeaders getHeadersJSONRequest() {
@ -1160,8 +1331,6 @@ class ScreenProctoringAPIBinding {
public static final String ATTR_SPS_ACTIVE = "spsExamActive"; public static final String ATTR_SPS_ACTIVE = "spsExamActive";
public static final String ATTR_SPS_ACCESS_DATA = "spsAccessData"; public static final String ATTR_SPS_ACCESS_DATA = "spsAccessData";
@JsonProperty("spsUserPWD")
String spsUserPWD = null;
@JsonProperty("spsSEBAccessUUID") @JsonProperty("spsSEBAccessUUID")
String spsSEBAccessUUID = null; String spsSEBAccessUUID = null;
@JsonProperty("spsSEBAccessName") @JsonProperty("spsSEBAccessName")
@ -1175,16 +1344,13 @@ class ScreenProctoringAPIBinding {
} }
@JsonCreator @JsonCreator
public SPSData( public SPSData(@JsonProperty("spsSEBAccessUUID") final String spsSEBAccessUUID,
@JsonProperty("spsUserPWD") final String spsUserPWD,
@JsonProperty("spsSEBAccessUUID") final String spsSEBAccessUUID,
// NOTE: this is only for compatibility reasons, TODO as soon as possible // NOTE: this is only for compatibility reasons, TODO as soon as possible
@JsonProperty("spsSEBAccesUUID") final String spsSEBAccesUUID, @JsonProperty("spsSEBAccesUUID") final String spsSEBAccesUUID,
@JsonProperty("spsSEBAccessName") final String spsSEBAccessName, @JsonProperty("spsSEBAccessName") final String spsSEBAccessName,
@JsonProperty("spsSEBAccessPWD") final String spsSEBAccessPWD, @JsonProperty("spsSEBAccessPWD") final String spsSEBAccessPWD,
@JsonProperty("psExamUUID") final String spsExamUUID) { @JsonProperty("psExamUUID") final String spsExamUUID) {
this.spsUserPWD = spsUserPWD;
this.spsSEBAccessUUID = StringUtils.isNotBlank(spsSEBAccesUUID) ? spsSEBAccesUUID : spsSEBAccessUUID; this.spsSEBAccessUUID = StringUtils.isNotBlank(spsSEBAccesUUID) ? spsSEBAccesUUID : spsSEBAccessUUID;
this.spsSEBAccessName = spsSEBAccessName; this.spsSEBAccessName = spsSEBAccessName;
this.spsSEBAccessPWD = spsSEBAccessPWD; this.spsSEBAccessPWD = spsSEBAccessPWD;

View file

@ -10,14 +10,13 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.proctoring;
import static ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_SCREEN_PROCTORING.*; import static ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_SCREEN_PROCTORING.*;
import java.util.ArrayList; import java.util.*;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import ch.ethz.seb.sebserver.gbl.async.AsyncServiceSpringConfig; import ch.ethz.seb.sebserver.gbl.async.AsyncServiceSpringConfig;
import ch.ethz.seb.sebserver.gbl.model.Activatable;
import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.webservice.WebserviceInfo; import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.LmsSetupChangeEvent;
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;
@ -184,7 +183,7 @@ public class ScreenProctoringServiceImpl implements ScreenProctoringService {
final boolean isEnabling = this.proctoringSettingsDAO.isScreenProctoringEnabled(exam.id); final boolean isEnabling = this.proctoringSettingsDAO.isScreenProctoringEnabled(exam.id);
if (isEnabling && !isSPSActive) { if (isEnabling && !isSPSActive) {
// if screen proctoring has been enabled
this.screenProctoringAPIBinding this.screenProctoringAPIBinding
.startScreenProctoring(exam) .startScreenProctoring(exam)
.onError(error -> log.error( .onError(error -> log.error(
@ -197,9 +196,9 @@ public class ScreenProctoringServiceImpl implements ScreenProctoringService {
this.examDAO.markUpdate(exam.id); this.examDAO.markUpdate(exam.id);
} else if (!isEnabling && isSPSActive) { } else if (!isEnabling && isSPSActive) {
// if screen proctoring has been disabled...
this.screenProctoringAPIBinding this.screenProctoringAPIBinding
.disposeScreenProctoring(exam) .deactivateScreenProctoring(exam)
.onError(error -> log.error("Failed to dispose screen proctoring for exam: {}", .onError(error -> log.error("Failed to dispose screen proctoring for exam: {}",
exam, exam,
error)) error))
@ -301,7 +300,7 @@ public class ScreenProctoringServiceImpl implements ScreenProctoringService {
return; return;
} }
this.screenProctoringAPIBinding.activateSEBAccessOnSPS(exam, true); this.screenProctoringAPIBinding.activateScreenProctoring(exam);
} }
@Override @Override
@ -311,7 +310,7 @@ public class ScreenProctoringServiceImpl implements ScreenProctoringService {
return; return;
} }
this.screenProctoringAPIBinding.activateSEBAccessOnSPS(exam, false); this.screenProctoringAPIBinding.deactivateScreenProctoring(exam);
} }
@Override @Override
@ -330,6 +329,37 @@ public class ScreenProctoringServiceImpl implements ScreenProctoringService {
}); });
} }
@Override
public void notifyLmsSetupChange(final LmsSetupChangeEvent event) {
try {
if (event.activation == Activatable.ActivationAction.NONE) {
return;
}
examDAO.allActiveForLMSSetup(Arrays.asList(event.getLmsSetup().id))
.getOrThrow()
.forEach(exam -> {
if (screenProctoringAPIBinding.isSPSActive(exam)) {
if (event.activation == Activatable.ActivationAction.ACTIVATE) {
this.screenProctoringAPIBinding.activateScreenProctoring(exam)
.onError(error -> log.warn("Failed to re-activate SPS for exam: {} error: {}",
exam.name,
error.getMessage()));
} else if (event.activation == Activatable.ActivationAction.DEACTIVATE) {
this.screenProctoringAPIBinding.deactivateScreenProctoring(exam)
.onError(error -> log.warn("Failed to deactivate SPS for exam: {} error: {}",
exam.name,
error.getMessage()));
}
}
});
} catch (final Exception e) {
log.error("Failed to apply LMSSetup change activation/deactivation to Screen Proctoring: ", e);
}
}
private void applyScreenProctoringSession(final ClientConnectionRecord ccRecord) { private void applyScreenProctoringSession(final ClientConnectionRecord ccRecord) {
Long placeReservedInGroup = null; Long placeReservedInGroup = null;
@ -457,7 +487,7 @@ public class ScreenProctoringServiceImpl implements ScreenProctoringService {
private Result<Exam> deleteForExam(final Long examId) { private Result<Exam> deleteForExam(final Long examId) {
return this.examDAO return this.examDAO
.byPK(examId) .byPK(examId)
.map(this.screenProctoringAPIBinding::deleteScreenProctoring) .flatMap(this.screenProctoringAPIBinding::deactivateScreenProctoring)
.map(this::cleanupAllLocalGroups) .map(this::cleanupAllLocalGroups)
.onError(error -> log.error("Failed to delete SPS integration for exam: {}", examId, error)); .onError(error -> log.error("Failed to delete SPS integration for exam: {}", examId, error));
} }

View file

@ -14,6 +14,7 @@ import java.util.stream.Collectors;
import javax.validation.Valid; import javax.validation.Valid;
import ch.ethz.seb.sebserver.gbl.model.Activatable;
import ch.ethz.seb.sebserver.gbl.util.Cryptor; import ch.ethz.seb.sebserver.gbl.util.Cryptor;
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamImportService; import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamImportService;
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamUtils; import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamUtils;

View file

@ -14,6 +14,7 @@ import ch.ethz.seb.sebserver.gbl.model.Activatable;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsTestService; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsTestService;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.SEBRestrictionService; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.SEBRestrictionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ScreenProctoringService;
import org.mybatis.dynamic.sql.SqlTable; import org.mybatis.dynamic.sql.SqlTable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -61,7 +62,8 @@ public class LmsSetupController extends ActivatableEntityController<LmsSetup, Lm
private final LmsTestService lmsTestService; private final LmsTestService lmsTestService;
private final SEBRestrictionService sebRestrictionService; private final SEBRestrictionService sebRestrictionService;
private final FullLmsIntegrationService fullLmsIntegrationService; private final FullLmsIntegrationService fullLmsIntegrationService;
final ApplicationEventPublisher applicationEventPublisher; private final ApplicationEventPublisher applicationEventPublisher;
private final ScreenProctoringService screenProctoringService;
public LmsSetupController( public LmsSetupController(
final LmsSetupDAO lmsSetupDAO, final LmsSetupDAO lmsSetupDAO,
@ -74,7 +76,8 @@ public class LmsSetupController extends ActivatableEntityController<LmsSetup, Lm
final LmsTestService lmsTestService, final LmsTestService lmsTestService,
final SEBRestrictionService sebRestrictionService, final SEBRestrictionService sebRestrictionService,
final FullLmsIntegrationService fullLmsIntegrationService, final FullLmsIntegrationService fullLmsIntegrationService,
final ApplicationEventPublisher applicationEventPublisher) { final ApplicationEventPublisher applicationEventPublisher,
final ScreenProctoringService screenProctoringService) {
super(authorization, super(authorization,
bulkActionService, bulkActionService,
@ -88,6 +91,7 @@ public class LmsSetupController extends ActivatableEntityController<LmsSetup, Lm
this.sebRestrictionService = sebRestrictionService; this.sebRestrictionService = sebRestrictionService;
this.fullLmsIntegrationService = fullLmsIntegrationService; this.fullLmsIntegrationService = fullLmsIntegrationService;
this.applicationEventPublisher = applicationEventPublisher; this.applicationEventPublisher = applicationEventPublisher;
this.screenProctoringService = screenProctoringService;
} }
@Override @Override
@ -166,20 +170,35 @@ public class LmsSetupController extends ActivatableEntityController<LmsSetup, Lm
return super.notifySaved(entity, activation); return super.notifySaved(entity, activation);
} }
@Override // @Override
protected Result<LmsSetup> validForActivation(final LmsSetup entity, final boolean activation) { // protected Result<LmsSetup> validForActivation(final LmsSetup entity, final boolean activation) {
return super.validForActivation(entity, activation) // return super.validForActivation(entity, activation)
.map(lmsSetup -> { // .map(lmsSetup -> {
if (!activation) { // if (!activation) {
// on deactivation remove all SEB restrictions and delete full integration if in place // // on deactivation remove all SEB restrictions and delete full integration if in place
return sebRestrictionService // return sebRestrictionService
.applyLMSSetupDeactivation(lmsSetup) // .applyLMSSetupDeactivation(lmsSetup)
.flatMap(fullLmsIntegrationService::applyLMSSetupDeactivation) // .onErrorDo(error -> {
.getOrThrow(); // log.warn("Failed to apply LMSSetup deactivation for SEB Restriction: ", error);
} // return lmsSetup;
return entity; // })
}); // .flatMap(fullLmsIntegrationService::applyLMSSetupDeactivation)
} // .onErrorDo(error -> {
// log.warn("Failed to apply LMSSetup deactivation for LMS full integration: ", error);
// return lmsSetup;
// })
// .flatMap(screenProctoringService::applyLMSSetupDeactivation)
// .onErrorDo(error -> {
// log.warn("Failed to apply LMSSetup deactivation for Screen Proctoring: ", error);
// return lmsSetup;
// })
// .getOrThrow();
// } else {
//
// }
// return entity;
// });
// }
@Override @Override
protected Result<LmsSetup> validForDelete(final LmsSetup entity) { protected Result<LmsSetup> validForDelete(final LmsSetup entity) {
@ -187,7 +206,7 @@ public class LmsSetupController extends ActivatableEntityController<LmsSetup, Lm
// if there is a SEB Restriction involved, release all SEB Restriction for exams // if there is a SEB Restriction involved, release all SEB Restriction for exams
if (entity.lmsType.features.contains(LmsSetup.Features.SEB_RESTRICTION)) { if (entity.lmsType.features.contains(LmsSetup.Features.SEB_RESTRICTION)) {
sebRestrictionService sebRestrictionService
.applyLMSSetupDeactivation(entity) .releaseAllRestrictionsOf(entity)
.getOrThrow(); .getOrThrow();
} }
// if there is a full LMS integration involved, delete it first on LMS // if there is a full LMS integration involved, delete it first on LMS