SEBSERV-139 Proctoring room join and leave API

This commit is contained in:
anhefti 2020-08-25 16:47:30 +02:00
parent 9923e06029
commit 3ea936cf81
12 changed files with 252 additions and 51 deletions

View file

@ -128,7 +128,9 @@ public final class API {
public static final String EXAM_ADMINISTRATION_CHECK_RESTRICTION_PATH_SEGMENT = "/check-seb-restriction"; public static final String EXAM_ADMINISTRATION_CHECK_RESTRICTION_PATH_SEGMENT = "/check-seb-restriction";
public static final String EXAM_ADMINISTRATION_CHECK_IMPORTED_PATH_SEGMENT = "/check-imported"; public static final String EXAM_ADMINISTRATION_CHECK_IMPORTED_PATH_SEGMENT = "/check-imported";
public static final String EXAM_ADMINISTRATION_SEB_RESTRICTION_CHAPTERS_PATH_SEGMENT = "/chapters"; public static final String EXAM_ADMINISTRATION_SEB_RESTRICTION_CHAPTERS_PATH_SEGMENT = "/chapters";
public static final String EXAM_ADMINISTRATION_PROCTOR_PATH_SEGMENT = "/proctoring"; public static final String PROCTOR_PATH_SEGMENT = "/proctoring";
public static final String PROCTOR_JOIN_ROOM_PATH_SEGMENT = "/join";
public static final String PROCTOR_LEAVE_ROOM_PATH_SEGMENT = "/leave";
public static final String EXAM_INDICATOR_ENDPOINT = "/indicator"; public static final String EXAM_INDICATOR_ENDPOINT = "/indicator";
@ -166,9 +168,9 @@ public final class API {
public static final String EXAM_MONITORING_ENDPOINT = "/monitoring"; public static final String EXAM_MONITORING_ENDPOINT = "/monitoring";
public static final String EXAM_MONITORING_INSTRUCTION_ENDPOINT = "/instruction"; public static final String EXAM_MONITORING_INSTRUCTION_ENDPOINT = "/instruction";
public static final String EXAM_MONITORING_DISABLE_CONNECTION_ENDPOINT = "/disable-connection"; public static final String EXAM_MONITORING_DISABLE_CONNECTION_ENDPOINT = "/disable-connection";
public static final String EXAM_MONITORING_STATE_FILTER = "hidden-states";
public static final String EXAM_MONITORING_SEB_CONNECTION_TOKEN_PATH_SEGMENT = public static final String EXAM_MONITORING_SEB_CONNECTION_TOKEN_PATH_SEGMENT =
"/{" + EXAM_API_SEB_CONNECTION_TOKEN + "}"; "/{" + EXAM_API_SEB_CONNECTION_TOKEN + "}";
public static final String EXAM_MONITORING_STATE_FILTER = "hidden-states";
public static final String SEB_CLIENT_CONNECTION_ENDPOINT = "/seb-client-connection"; public static final String SEB_CLIENT_CONNECTION_ENDPOINT = "/seb-client-connection";

View file

@ -28,6 +28,11 @@ public final class ClientInstruction {
SEB_PROCTORING SEB_PROCTORING
} }
public enum ProctoringInstructionMethod {
JOIN,
LEAVE
}
public interface SEB_INSTRUCTION_ATTRIBUTES { public interface SEB_INSTRUCTION_ATTRIBUTES {
public interface SEB_PROCTORING { public interface SEB_PROCTORING {
public static final String SERVICE_TYPE = "service-type"; public static final String SERVICE_TYPE = "service-type";

View file

@ -54,7 +54,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicator
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetProctoringSettings; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetProctoringSettings;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.logs.GetExtendedClientEventPage; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.logs.GetExtendedClientEventPage;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetClientConnectionData; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetClientConnectionData;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetProctorURLForClient; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetProctorDataForSEBClient;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser; import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
import ch.ethz.seb.sebserver.gui.service.session.ClientConnectionDetails; import ch.ethz.seb.sebserver.gui.service.session.ClientConnectionDetails;
import ch.ethz.seb.sebserver.gui.service.session.InstructionProcessor; import ch.ethz.seb.sebserver.gui.service.session.InstructionProcessor;
@ -296,7 +296,7 @@ public class MonitoringClientConnection implements TemplateComposer {
private PageAction openProctorScreen(final PageAction action, final String connectionToken) { private PageAction openProctorScreen(final PageAction action, final String connectionToken) {
final SEBClientProctoringConnectionData proctoringConnectionData = final SEBClientProctoringConnectionData proctoringConnectionData =
this.pageService.getRestService().getBuilder(GetProctorURLForClient.class) this.pageService.getRestService().getBuilder(GetProctorDataForSEBClient.class)
.withURIVariable(API.PARAM_MODEL_ID, action.getEntityKey().modelId) .withURIVariable(API.PARAM_MODEL_ID, action.getEntityKey().modelId)
.withURIVariable(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionToken) .withURIVariable(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionToken)
.call() .call()

View file

@ -21,7 +21,7 @@ import ch.ethz.seb.sebserver.gui.service.examconfig.impl.ViewContext;
@Lazy @Lazy
@Service @Service
@GuiProfile @GuiProfile
public class ProcotringViewRules implements ValueChangeRule { public class ProctoringViewRules implements ValueChangeRule {
public static final String KEY_ENABLE_AI = "proctoringAIEnable"; public static final String KEY_ENABLE_AI = "proctoringAIEnable";
public static final String KEY_ENABLE_JITSI = "jitsiMeetEnable"; public static final String KEY_ENABLE_JITSI = "jitsiMeetEnable";

View file

@ -36,7 +36,7 @@ public class GetProctoringSettings extends RestCall<ProctoringSettings> {
MediaType.APPLICATION_JSON_UTF8, MediaType.APPLICATION_JSON_UTF8,
API.EXAM_ADMINISTRATION_ENDPOINT API.EXAM_ADMINISTRATION_ENDPOINT
+ API.MODEL_ID_VAR_PATH_SEGMENT + API.MODEL_ID_VAR_PATH_SEGMENT
+ API.EXAM_ADMINISTRATION_PROCTOR_PATH_SEGMENT); + API.PROCTOR_PATH_SEGMENT);
} }
} }

View file

@ -36,7 +36,7 @@ public class SaveProctoringSettings extends RestCall<Exam> {
MediaType.APPLICATION_JSON_UTF8, MediaType.APPLICATION_JSON_UTF8,
API.EXAM_ADMINISTRATION_ENDPOINT API.EXAM_ADMINISTRATION_ENDPOINT
+ API.MODEL_ID_VAR_PATH_SEGMENT + API.MODEL_ID_VAR_PATH_SEGMENT
+ API.EXAM_ADMINISTRATION_PROCTOR_PATH_SEGMENT); + API.PROCTOR_PATH_SEGMENT);
} }
} }

View file

@ -24,9 +24,9 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy @Lazy
@Component @Component
@GuiProfile @GuiProfile
public class GetProctorURLForClient extends RestCall<SEBClientProctoringConnectionData> { public class GetProctorDataForSEBClient extends RestCall<SEBClientProctoringConnectionData> {
public GetProctorURLForClient() { public GetProctorDataForSEBClient() {
super(new TypeKey<>( super(new TypeKey<>(
CallType.GET_SINGLE, CallType.GET_SINGLE,
EntityType.EXAM_PROCTOR_DATA, EntityType.EXAM_PROCTOR_DATA,
@ -34,9 +34,9 @@ public class GetProctorURLForClient extends RestCall<SEBClientProctoringConnecti
}), }),
HttpMethod.GET, HttpMethod.GET,
MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_FORM_URLENCODED,
API.EXAM_ADMINISTRATION_ENDPOINT API.EXAM_MONITORING_ENDPOINT
+ API.MODEL_ID_VAR_PATH_SEGMENT + API.MODEL_ID_VAR_PATH_SEGMENT
+ API.EXAM_ADMINISTRATION_PROCTOR_PATH_SEGMENT + API.PROCTOR_PATH_SEGMENT
+ API.EXAM_MONITORING_SEB_CONNECTION_TOKEN_PATH_SEGMENT); + API.EXAM_MONITORING_SEB_CONNECTION_TOKEN_PATH_SEGMENT);
} }

View file

@ -30,4 +30,9 @@ public interface ExamProctoringService {
ClientConnection clientConnection, ClientConnection clientConnection,
boolean server); boolean server);
Result<SEBClientProctoringConnectionData> createProcotringDataForRoom(
final ProctoringSettings examProctoring,
final String roomName,
final boolean server);
} }

View file

@ -96,19 +96,7 @@ public class ExamJITSIProctoringService implements ExamProctoringService {
return Result.tryCatch(() -> { return Result.tryCatch(() -> {
if (examProctoring.examId == null) { final long expTime = forExam(examProctoring);
throw new IllegalStateException("Missing exam identifier from ExamProctoring data");
}
long expTime = System.currentTimeMillis() + Constants.DAY_IN_MILLIS;
if (this.examSessionService.isExamRunning(examProctoring.examId)) {
final Exam exam = this.examSessionService.getRunningExam(examProctoring.examId)
.getOrThrow();
if (exam.endTime != null) {
expTime = exam.endTime.getMillis();
}
}
final Encoder urlEncoder = Base64.getUrlEncoder().withoutPadding(); final Encoder urlEncoder = Base64.getUrlEncoder().withoutPadding();
final String roomName = urlEncoder.encodeToString( final String roomName = urlEncoder.encodeToString(
Utils.toByteArray(clientConnection.connectionToken)); Utils.toByteArray(clientConnection.connectionToken));
@ -127,6 +115,29 @@ public class ExamJITSIProctoringService implements ExamProctoringService {
} }
@Override
public Result<SEBClientProctoringConnectionData> createProcotringDataForRoom(
final ProctoringSettings examProctoring,
final String roomName,
final boolean server) {
return Result.tryCatch(() -> {
final long expTime = forExam(examProctoring);
return createProctoringConnectionData(
examProctoring.serverURL,
examProctoring.appKey,
examProctoring.getAppSecret(),
this.authorizationService.getUserService().getCurrentUser().getUsername(),
(server) ? "seb-server" : "seb-client",
roomName,
roomName,
expTime)
.getOrThrow();
});
}
public Result<SEBClientProctoringConnectionData> createProctoringConnectionData( public Result<SEBClientProctoringConnectionData> createProctoringConnectionData(
final String url, final String url,
final String appKey, final String appKey,
@ -224,4 +235,20 @@ public class ExamJITSIProctoringService implements ExamProctoringService {
return builder.toString(); return builder.toString();
} }
private long forExam(final ProctoringSettings examProctoring) {
if (examProctoring.examId == null) {
throw new IllegalStateException("Missing exam identifier from ExamProctoring data");
}
long expTime = System.currentTimeMillis() + Constants.DAY_IN_MILLIS;
if (this.examSessionService.isExamRunning(examProctoring.examId)) {
final Exam exam = this.examSessionService.getRunningExam(examProctoring.examId)
.getOrThrow();
if (exam.endTime != null) {
expTime = exam.endTime.getMillis();
}
}
return expTime;
}
} }

View file

@ -52,7 +52,6 @@ import ch.ethz.seb.sebserver.gbl.model.exam.Chapters;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings;
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData; import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
import ch.ethz.seb.sebserver.gbl.model.exam.SEBClientProctoringConnectionData;
import ch.ethz.seb.sebserver.gbl.model.exam.SEBRestriction; import ch.ethz.seb.sebserver.gbl.model.exam.SEBRestriction;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup; import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.Features; import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.Features;
@ -384,7 +383,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
@RequestMapping( @RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT path = API.MODEL_ID_VAR_PATH_SEGMENT
+ API.EXAM_ADMINISTRATION_PROCTOR_PATH_SEGMENT, + API.PROCTOR_PATH_SEGMENT,
method = RequestMethod.GET, method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE) produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ProctoringSettings getExamProctoring( public ProctoringSettings getExamProctoring(
@ -403,7 +402,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
@RequestMapping( @RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT path = API.MODEL_ID_VAR_PATH_SEGMENT
+ API.EXAM_ADMINISTRATION_PROCTOR_PATH_SEGMENT, + API.PROCTOR_PATH_SEGMENT,
method = RequestMethod.POST, method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE) produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Exam saveExamProctoring( public Exam saveExamProctoring(
@ -425,30 +424,6 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
.getOrThrow(); .getOrThrow();
} }
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT
+ API.EXAM_ADMINISTRATION_PROCTOR_PATH_SEGMENT
+ API.EXAM_MONITORING_SEB_CONNECTION_TOKEN_PATH_SEGMENT,
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public SEBClientProctoringConnectionData getExamProctoringURL(
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@PathVariable(name = API.PARAM_MODEL_ID) final Long examId,
@PathVariable(name = API.EXAM_API_SEB_CONNECTION_TOKEN) final String connectionToken) {
checkReadPrivilege(institutionId);
return this.entityDAO.byPK(examId)
.flatMap(this.authorization::checkRead)
.flatMap(this.examAdminService::getExamProctoring)
.flatMap(proc -> this.examAdminService
.getExamProctoringService(proc.serverType)
.flatMap(s -> s.createProctoringConnectionData(proc, connectionToken, true)))
.getOrThrow();
}
// **** Proctoring // **** Proctoring
// **************************************************************************** // ****************************************************************************

View file

@ -9,9 +9,12 @@
package ch.ethz.seb.sebserver.webservice.weblayer.api; package ch.ethz.seb.sebserver.webservice.weblayer.api;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -39,16 +42,22 @@ import ch.ethz.seb.sebserver.gbl.api.authorization.PrivilegeType;
import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.Page; import ch.ethz.seb.sebserver.gbl.model.Page;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings;
import ch.ethz.seb.sebserver.gbl.model.exam.SEBClientProctoringConnectionData;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction; import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction;
import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction.InstructionType;
import ch.ethz.seb.sebserver.gbl.model.user.UserRole; import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService; import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService; import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.PermissionDeniedException; import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.PermissionDeniedException;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService; import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService;
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamProctoringService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientConnectionService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientConnectionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBInstructionService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBInstructionService;
@ -62,16 +71,19 @@ public class ExamMonitoringController {
private final SEBClientConnectionService sebClientConnectionService; private final SEBClientConnectionService sebClientConnectionService;
private final ExamSessionService examSessionService; private final ExamSessionService examSessionService;
private final ExamAdminService examAdminService;
private final SEBInstructionService sebInstructionService; private final SEBInstructionService sebInstructionService;
private final AuthorizationService authorization; private final AuthorizationService authorization;
private final PaginationService paginationService; private final PaginationService paginationService;
public ExamMonitoringController( public ExamMonitoringController(
final ExamAdminService examAdminService,
final SEBClientConnectionService sebClientConnectionService, final SEBClientConnectionService sebClientConnectionService,
final SEBInstructionService sebInstructionService, final SEBInstructionService sebInstructionService,
final AuthorizationService authorization, final AuthorizationService authorization,
final PaginationService paginationService) { final PaginationService paginationService) {
this.examAdminService = examAdminService;
this.sebClientConnectionService = sebClientConnectionService; this.sebClientConnectionService = sebClientConnectionService;
this.examSessionService = sebClientConnectionService.getExamSessionService(); this.examSessionService = sebClientConnectionService.getExamSessionService();
this.sebInstructionService = sebInstructionService; this.sebInstructionService = sebInstructionService;
@ -269,6 +281,127 @@ public class ExamMonitoringController {
} }
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT
+ API.PROCTOR_PATH_SEGMENT
+ API.EXAM_MONITORING_SEB_CONNECTION_TOKEN_PATH_SEGMENT,
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public SEBClientProctoringConnectionData getClientSingleRoomProctoringData(
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@PathVariable(name = API.PARAM_MODEL_ID) final Long examId,
@PathVariable(name = API.EXAM_API_SEB_CONNECTION_TOKEN) final String connectionToken) {
this.authorization.check(
PrivilegeType.READ,
EntityType.EXAM,
institutionId);
return this.examSessionService.getRunningExam(examId)
.flatMap(this.authorization::checkRead)
.flatMap(this.examAdminService::getExamProctoring)
.flatMap(proc -> this.examAdminService
.getExamProctoringService(proc.serverType)
.flatMap(s -> s.createProctoringConnectionData(proc, connectionToken, true)))
.getOrThrow();
}
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT
+ API.PROCTOR_PATH_SEGMENT
+ API.PROCTOR_JOIN_ROOM_PATH_SEGMENT,
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public SEBClientProctoringConnectionData joinProctoringRoom(
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@PathVariable(name = API.PARAM_MODEL_ID) final Long examId,
@RequestParam(
name = SEBClientProctoringConnectionData.ATTR_ROOM_NAME,
required = true) final String roomName,
@RequestParam(
name = API.EXAM_API_SEB_CONNECTION_TOKEN,
required = true) final String connectionTokens) {
this.authorization.check(
PrivilegeType.READ,
EntityType.EXAM,
institutionId);
final ProctoringSettings settings = this.examSessionService
.getRunningExam(examId)
.flatMap(this.authorization::checkRead)
.flatMap(this.examAdminService::getExamProctoring)
.getOrThrow();
final SEBClientProctoringConnectionData result = this.examAdminService
.getExamProctoringService(settings.serverType)
.flatMap(s -> s.createProcotringDataForRoom(settings, roomName, false))
.getOrThrow();
if (StringUtils.isNotBlank(connectionTokens)) {
(connectionTokens.contains(Constants.LIST_SEPARATOR)
? Arrays.asList(StringUtils.split(connectionTokens, Constants.LIST_SEPARATOR))
: Arrays.asList(connectionTokens)).stream()
.forEach(connectionToken -> sendJoinInstruction(examId, connectionToken, result)
.onError(error -> log.error(
"Failed to send proctoring leave instruction to client: {} ",
connectionToken, error)));
}
return result;
}
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT
+ API.PROCTOR_PATH_SEGMENT
+ API.PROCTOR_LEAVE_ROOM_PATH_SEGMENT,
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public void leaveProctoringRoom(
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@PathVariable(name = API.PARAM_MODEL_ID) final Long examId,
@RequestParam(
name = SEBClientProctoringConnectionData.ATTR_ROOM_NAME,
required = true) final String roomName,
@RequestParam(
name = API.EXAM_API_SEB_CONNECTION_TOKEN,
required = true) final String connectionTokens) {
this.authorization.check(
PrivilegeType.READ,
EntityType.EXAM,
institutionId);
final ProctoringSettings settings = this.examSessionService
.getRunningExam(examId)
.flatMap(this.authorization::checkRead)
.flatMap(this.examAdminService::getExamProctoring)
.getOrThrow();
final ExamProctoringService examProctoringService = this.examAdminService
.getExamProctoringService(settings.serverType)
.getOrThrow();
(connectionTokens.contains(Constants.LIST_SEPARATOR)
? Arrays.asList(StringUtils.split(connectionTokens, Constants.LIST_SEPARATOR))
: Arrays.asList(connectionTokens)).stream()
.forEach(connectionToken -> examProctoringService
.createProctoringConnectionData(settings, connectionToken, false)
.flatMap(data -> sendLeaveInstruction(examId, connectionToken, data))
.onError(error -> log.error(
"Failed to send proctoring leave instruction to client: {} ",
connectionToken, error)));
}
private boolean hasRunningExamPrivilege(final Long examId, final Long institution) { private boolean hasRunningExamPrivilege(final Long examId, final Long institution) {
return hasRunningExamPrivilege( return hasRunningExamPrivilege(
this.examSessionService.getRunningExam(examId).getOr(null), this.examSessionService.getRunningExam(examId).getOr(null),
@ -284,4 +417,57 @@ public class ExamMonitoringController {
return exam.institutionId.equals(institution) && exam.isOwner(userId); return exam.institutionId.equals(institution) && exam.isOwner(userId);
} }
private Result<Void> sendJoinInstruction(
final Long examId,
final String connectionToken,
final SEBClientProctoringConnectionData data) {
return sendProctorInstruction(
examId,
connectionToken,
data,
ClientInstruction.ProctoringInstructionMethod.JOIN.name());
}
private Result<Void> sendLeaveInstruction(
final Long examId,
final String connectionToken,
final SEBClientProctoringConnectionData data) {
return sendProctorInstruction(
examId,
connectionToken,
data,
ClientInstruction.ProctoringInstructionMethod.LEAVE.name());
}
private Result<Void> sendProctorInstruction(
final Long examId,
final String connectionToken,
final SEBClientProctoringConnectionData data,
final String method) {
final Map<String, String> attributes = new HashMap<>();
attributes.put(
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.SERVICE_TYPE,
ProctoringSettings.ServerType.JITSI_MEET.name());
attributes.put(
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.METHOD,
method);
attributes.put(
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.JITSI_URL,
data.serverURL);
attributes.put(
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.JITSI_ROOM,
data.roomName);
attributes.put(
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.JITSI_TOKEN,
data.accessToken);
return this.sebInstructionService.registerInstruction(
examId,
InstructionType.SEB_PROCTORING,
attributes,
connectionToken,
true);
}
} }

View file

@ -1,6 +1,7 @@
server.address=localhost server.address=localhost
server.port=8080 server.port=8080
sebserver.gui.http.external.scheme=http
sebserver.gui.entrypoint=/gui sebserver.gui.entrypoint=/gui
sebserver.gui.webservice.protocol=http sebserver.gui.webservice.protocol=http
sebserver.gui.webservice.address=localhost sebserver.gui.webservice.address=localhost