SEBSERV-417 and SEBSP-111
This commit is contained in:
parent
3a5129f796
commit
c161e3c5ef
13 changed files with 414 additions and 168 deletions
|
@ -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.
|
||||||
*
|
*
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue