From 43383bcebbf13fbac4cbf2acf5a5c0fb3166c461 Mon Sep 17 00:00:00 2001 From: anhefti Date: Tue, 16 Mar 2021 17:34:23 +0100 Subject: [PATCH] zoom integration --- .../ch/ethz/seb/sebserver/gbl/Constants.java | 1 + .../model/exam/ProctoringRoomConnection.java | 8 +- .../gbl/model/session/ClientInstruction.java | 1 + .../ch/ethz/seb/sebserver/gbl/util/Utils.java | 1 - .../dao/RemoteProctoringRoomDAO.java | 2 + .../session/ExamProctoringService.java | 53 +- .../ExamProctoringRoomServiceImpl.java | 57 +- .../proctoring/JitsiProctoringService.java | 55 +- .../proctoring/ZoomProctoringService.java | 635 +++++++++++++----- .../proctoring/ZoomRoomRequestResponse.java | 32 +- .../sql/base/V6__create_alter_tables_v1_2.sql | 4 +- .../ExamJITSIProctoringServiceTest.java | 2 - src/test/resources/schema-test.sql | 4 +- 13 files changed, 574 insertions(+), 281 deletions(-) diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java b/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java index 50e1a851..b1855220 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java @@ -53,6 +53,7 @@ public final class Constants { public static final Character SLASH = '/'; public static final Character BACKSLASH = '\\'; public static final Character QUOTE = '\''; + public static final Character QUERY = '?'; public static final Character DOUBLE_QUOTE = '"'; public static final Character COMMA = ','; public static final Character PIPE = '|'; diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ProctoringRoomConnection.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ProctoringRoomConnection.java index 03f16d82..f44447de 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ProctoringRoomConnection.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ProctoringRoomConnection.java @@ -24,6 +24,7 @@ public class ProctoringRoomConnection { public static final String ATTR_SUBJECT = "subject"; public static final String ATTR_ACCESS_TOKEN = "accessToken"; public static final String ATTR_CONNECTION_URL = "connectionURL"; + public static final String ATTR_USER_NAME = "userName"; @JsonProperty(ProctoringServiceSettings.ATTR_SERVER_TYPE) public final ProctoringServerType proctoringServerType; @@ -46,6 +47,9 @@ public class ProctoringRoomConnection { @JsonProperty(ATTR_ACCESS_TOKEN) public final String accessToken; + @JsonProperty(ATTR_USER_NAME) + public final String userName; + @JsonCreator public ProctoringRoomConnection( @JsonProperty(ProctoringServiceSettings.ATTR_SERVER_TYPE) final ProctoringServerType proctoringServerType, @@ -54,7 +58,8 @@ public class ProctoringRoomConnection { @JsonProperty(ATTR_SERVER_URL) final String serverURL, @JsonProperty(ATTR_ROOM_NAME) final String roomName, @JsonProperty(ATTR_SUBJECT) final String subject, - @JsonProperty(ATTR_ACCESS_TOKEN) final String accessToken) { + @JsonProperty(ATTR_ACCESS_TOKEN) final String accessToken, + @JsonProperty(ATTR_USER_NAME) final String userName) { this.proctoringServerType = proctoringServerType; this.connectionToken = connectionToken; @@ -63,6 +68,7 @@ public class ProctoringRoomConnection { this.roomName = roomName; this.subject = subject; this.accessToken = accessToken; + this.userName = userName; } public ProctoringServerType getProctoringServerType() { diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientInstruction.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientInstruction.java index 49a169fb..eb1bdf24 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientInstruction.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientInstruction.java @@ -51,6 +51,7 @@ public final class ClientInstruction { public static final String ZOOM_URL = "zoomMeetServerURL"; public static final String ZOOM_ROOM = "zoomMeetRoom"; public static final String ZOOM_ROOM_SUBJECT = "zoomMeetSubject"; + public static final String ZOOM_USER_NAME = "zoomUserName"; public static final String ZOOM_TOKEN = "zoomMeetToken"; public static final String ZOOM_RECEIVE_AUDIO = "zoomMeetReceiveAudio"; public static final String ZOOM_RECEIVE_VIDEO = "zoomMeetReceiveVideo"; diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/util/Utils.java b/src/main/java/ch/ethz/seb/sebserver/gbl/util/Utils.java index 60ff5b9e..a76655f9 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/util/Utils.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/util/Utils.java @@ -686,5 +686,4 @@ public final class Utils { public static String valueOrEmptyNote(final String value) { return StringUtils.isBlank(value) ? Constants.EMPTY_NOTE : value; } - } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/RemoteProctoringRoomDAO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/RemoteProctoringRoomDAO.java index aa222dc5..82e3f63e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/RemoteProctoringRoomDAO.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/RemoteProctoringRoomDAO.java @@ -20,6 +20,8 @@ public interface RemoteProctoringRoomDAO { Result> getCollectingRoomsForExam(Long examId); + //Result> getRoomsOfExam(Long examId); + Result getRoom(Long roomId); Result getRoom(Long examId, String roomName); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamProctoringService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamProctoringService.java index 7f526e5e..b2192a1e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamProctoringService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamProctoringService.java @@ -44,51 +44,20 @@ public interface ExamProctoringService { String roomName, String subject); - Result getClientBreakOutRoomConnection( + Result getClientRoomConnection( final ProctoringServiceSettings proctoringSettings, final String connectionToken, final String roomName, final String subject); - Result getClientCollectingRoomConnection( - final ProctoringServiceSettings proctoringSettings, - final String connectionToken, - final String roomName, - final String subject); +// Result getClientCollectingRoomConnection( +// final ProctoringServiceSettings proctoringSettings, +// final String connectionToken, +// final String roomName, +// final String subject); - Map createJoinInstructionAttributes(final ProctoringRoomConnection proctoringConnection); - -// /** This instructs all sepcified SEB clients to join a defined room by creating a individual room access token -// * and join instruction for each client and put this instruction to the clients instruction queue. -// * -// * @param proctoringSettings The proctoring service settings -// * @param clientConnectionTokens A collection of SEB connection tokens. Only active SEB clients will get the -// * instructions -// * @param roomName The name of the room to join -// * @param subject the subject of the room to join -// * @return Result refer to the room connection data for the proctor to join to room too or to an error when -// * happened */ -// Result sendJoinRoomToClients( -// ProctoringServiceSettings proctoringSettings, -// Collection clientConnectionTokens, -// String roomName, -// String subject); - -// /** Sends instructions to join or rejoin the individual assigned collecting rooms of each involved SEB client. -// * Creates an individual join instruction for each involved client and put that to the clients instruction queue. -// * -// * INFO: -// * A collecting room is assigned to each SEB client connection while connecting to the SEB server and -// * each SEB client that has successfully connected to the SEB Server and is participating in an exam -// * with proctoring enabled, is assigned to a collecting room. -// * -// * @param proctoringSettings he proctoring service settings -// * @param clientConnectionTokens A collection of SEB connection tokens. Only active SEB clients will get the -// * instructions -// * @return Empty Result that refers to an error when happened */ -// Result sendJoinCollectingRoomToClients( -// ProctoringServiceSettings proctoringSettings, -// Collection clientConnectionTokens); + Map createJoinInstructionAttributes( + final ProctoringRoomConnection proctoringConnection); Result disposeServiceRoomsForExam(Exam exam); @@ -100,11 +69,11 @@ public interface ExamProctoringService { throw new RuntimeException("Test Why: " + connectionToken); } - Result newCollectingRoom(Long roomNumber); + Result newCollectingRoom(ProctoringServiceSettings proctoringSettings, Long roomNumber); - Result newBreakOutRoom(String subject); + Result newBreakOutRoom(ProctoringServiceSettings proctoringSettings, String subject); - Result disposeBreakOutRoom(String roomName); + Result disposeBreakOutRoom(ProctoringServiceSettings proctoringSettings, String roomName); Map getDefaultInstructionAttributes(); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamProctoringRoomServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamProctoringRoomServiceImpl.java index 2f839393..cb7a0f66 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamProctoringRoomServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamProctoringRoomServiceImpl.java @@ -149,7 +149,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService // First create and get the town-hall room for specified exam final RemoteProctoringRoom townhallRoom = examProctoringService - .newBreakOutRoom(subject) + .newBreakOutRoom(settings, subject) .flatMap(room -> this.remoteProctoringRoomDAO.createTownhallRoom(examId, room)) .getOrThrow(); @@ -187,7 +187,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService .getOrThrow(); final RemoteProctoringRoom breakOutRoom = examProctoringService - .newBreakOutRoom(subject) + .newBreakOutRoom(settings, subject) .flatMap(room -> this.remoteProctoringRoomDAO.createBreakOutRoom(examId, room, connectionTokens)) .getOrThrow(); @@ -302,7 +302,9 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService return this.remoteProctoringRoomDAO.reservePlaceInCollectingRoom( examId, proctoringSettings.collectingRoomSize, - examProctoringService::newCollectingRoom) + roomNumber -> examProctoringService.newCollectingRoom( + proctoringSettings, + roomNumber)) .getOrThrow(); } catch (final Exception e) { @@ -334,7 +336,9 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService this.remoteProctoringRoomDAO .getTownhallRoom(examId) .map(RemoteProctoringRoom::getName) - .flatMap(examProctoringService::disposeBreakOutRoom) + .flatMap(roomName -> examProctoringService.disposeBreakOutRoom( + proctoringSettings, + roomName)) .flatMap(service -> this.remoteProctoringRoomDAO.deleteTownhallRoom(examId)) .getOrThrow(); @@ -382,7 +386,8 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService .getOrThrow(); // Dispose the proctoring room on service side - examProctoringService.disposeBreakOutRoom(remoteProctoringRoom.name) + examProctoringService + .disposeBreakOutRoom(proctoringSettings, remoteProctoringRoom.name) .getOrThrow(); // Send join collecting rooms to involving clients @@ -448,12 +453,13 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService connectionTokens .stream() .forEach(connectionToken -> { - this.sebInstructionService.registerInstruction( - examId, - InstructionType.SEB_RECONFIGURE_SETTINGS, - attributes, - connectionToken, - true) + this.sebInstructionService + .registerInstruction( + examId, + InstructionType.SEB_RECONFIGURE_SETTINGS, + attributes, + connectionToken, + true) .onError(error -> log.error( "Failed to register reconfiguring instruction for connection: {}", connectionToken, @@ -496,17 +502,17 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService clientConnectionTokens .stream() .forEach(connectionToken -> { - final ProctoringRoomConnection proctoringConnection = - examProctoringService.getClientBreakOutRoomConnection( + final ProctoringRoomConnection proctoringConnection = examProctoringService + .getClientRoomConnection( proctoringSettings, connectionToken, examProctoringService.verifyRoomName(roomName, connectionToken), (StringUtils.isNotBlank(subject)) ? subject : roomName) - .onError(error -> log.error( - "Failed to get client room connection data for {} cause: {}", - connectionToken, - error.getMessage())) - .get(); + .onError(error -> log.error( + "Failed to get client room connection data for {} cause: {}", + connectionToken, + error.getMessage())) + .get(); if (proctoringConnection != null) { sendJoinInstruction( proctoringSettings.examId, @@ -551,7 +557,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService .getOrThrow(); final ProctoringRoomConnection proctoringConnection = examProctoringService - .getClientCollectingRoomConnection( + .getClientRoomConnection( proctoringSettings, clientConnection.clientConnection.connectionToken, roomName, @@ -577,12 +583,13 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService final Map attributes = examProctoringService .createJoinInstructionAttributes(proctoringConnection); - this.sebInstructionService.registerInstruction( - examId, - InstructionType.SEB_PROCTORING, - attributes, - connectionToken, - true) + this.sebInstructionService + .registerInstruction( + examId, + InstructionType.SEB_PROCTORING, + attributes, + connectionToken, + true) .onError(error -> log.error("Failed to send join instruction: {}", connectionToken, error)); } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/JitsiProctoringService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/JitsiProctoringService.java index c92dc09f..de6e9e65 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/JitsiProctoringService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/JitsiProctoringService.java @@ -170,21 +170,30 @@ public class JitsiProctoringService implements ExamProctoringService { } @Override - public Result newCollectingRoom(final Long roomNumber) { + public Result newCollectingRoom( + final ProctoringServiceSettings proctoringSettings, + final Long roomNumber) { + return Result.of(new NewRoom( UUID.randomUUID().toString(), "Room " + (roomNumber + 1))); } @Override - public Result newBreakOutRoom(final String subject) { + public Result newBreakOutRoom( + final ProctoringServiceSettings proctoringSettings, + final String subject) { + return Result.of(new NewRoom( UUID.randomUUID().toString(), subject)); } @Override - public Result disposeBreakOutRoom(final String roomName) { + public Result disposeBreakOutRoom( + final ProctoringServiceSettings proctoringSettings, + final String roomName) { + return Result.EMPTY; } @@ -237,7 +246,6 @@ public class JitsiProctoringService implements ExamProctoringService { return Result.tryCatch(() -> { return createProctoringConnection( - proctoringSettings.serverType, null, proctoringSettings.serverURL, proctoringSettings.appKey, @@ -253,7 +261,7 @@ public class JitsiProctoringService implements ExamProctoringService { } @Override - public Result getClientCollectingRoomConnection( + public Result getClientRoomConnection( final ProctoringServiceSettings proctoringSettings, final String connectionToken, final String roomName, @@ -265,7 +273,6 @@ public class JitsiProctoringService implements ExamProctoringService { .getOrThrow(); return createProctoringConnection( - proctoringSettings.serverType, null, proctoringSettings.serverURL, proctoringSettings.appKey, @@ -280,38 +287,7 @@ public class JitsiProctoringService implements ExamProctoringService { }); } - @Override - public Result getClientBreakOutRoomConnection( - final ProctoringServiceSettings proctoringSettings, - final String connectionToken, - final String roomName, - final String subject) { - - return Result.tryCatch(() -> { - final long expTime = forExam(proctoringSettings); - - final ClientConnectionData connectionData = this.examSessionService - .getConnectionData(connectionToken) - .getOrThrow(); - - return createProctoringConnection( - proctoringSettings.serverType, - connectionToken, - proctoringSettings.serverURL, - proctoringSettings.appKey, - proctoringSettings.getAppSecret(), - connectionData.clientConnection.userSessionId, - SEB_CLIENT_KEY, - roomName, - subject, - expTime, - false) - .getOrThrow(); - }); - } - protected Result createProctoringConnection( - final ProctoringServerType proctoringServerType, final String connectionToken, final String url, final String appKey, @@ -341,13 +317,14 @@ public class JitsiProctoringService implements ExamProctoringService { moderator); return new ProctoringRoomConnection( - proctoringServerType, + ProctoringServerType.JITSI_MEET, connectionToken, host, url, roomName, subject, - token); + token, + clientName); }); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ZoomProctoringService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ZoomProctoringService.java index c5cb1165..32aa5981 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ZoomProctoringService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ZoomProctoringService.java @@ -8,16 +8,20 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.proctoring; +import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Base64; import java.util.Base64.Encoder; +import java.util.HashMap; import java.util.Map; +import java.util.UUID; import java.util.stream.Collectors; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Lazy; @@ -29,8 +33,11 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; -import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; import ch.ethz.seb.sebserver.ClientHttpRequestFactoryService; import ch.ethz.seb.sebserver.gbl.Constants; @@ -41,20 +48,27 @@ import ch.ethz.seb.sebserver.gbl.api.APIMessage.FieldValidationException; import ch.ethz.seb.sebserver.gbl.api.JSONMapper; import ch.ethz.seb.sebserver.gbl.async.AsyncService; import ch.ethz.seb.sebserver.gbl.async.CircuitBreaker; +import ch.ethz.seb.sebserver.gbl.client.ClientCredentials; import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringRoomConnection; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringServerType; +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.RemoteProctoringRoom; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.util.Cryptor; import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Tuple; import ch.ethz.seb.sebserver.gbl.util.Utils; +import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.RemoteProctoringRoomDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProctoringService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.proctoring.ZoomRoomRequestResponse.CreateMeetingRequest; import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.proctoring.ZoomRoomRequestResponse.CreateUserRequest; -import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.proctoring.ZoomRoomRequestResponse.UserPageResponse; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.proctoring.ZoomRoomRequestResponse.MeetingResponse; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.proctoring.ZoomRoomRequestResponse.UserResponse; @Lazy @Service @@ -67,8 +81,10 @@ public class ZoomProctoringService implements ExamProctoringService { private static final String ZOOM_ACCESS_TOKEN_HEADER = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}"; - private static final String ZOOM_ACCESS_TOKEN_PAYLOAD = + private static final String ZOOM_API_ACCESS_TOKEN_PAYLOAD = "{\"iss\":\"%s\",\"exp\":%s}"; + private static final String ZOOM_MEETING_ACCESS_TOKEN_PAYLOAD = + "{\"app_key\":\"%s\",\"iat\":%s,\"exp\":%s,\"tpc\":\"%s\",\"pwd\":\"%s\"}"; private static final Map SEB_API_NAME_INSTRUCTION_NAME_MAPPING = Utils.immutableMapOf(Arrays.asList( new Tuple<>( @@ -100,20 +116,26 @@ public class ZoomProctoringService implements ExamProctoringService { private final AsyncService asyncService; private final JSONMapper jsonMapper; private final ZoomRestTemplate zoomRestTemplate; + private final RemoteProctoringRoomDAO remoteProctoringRoomDAO; + private final AuthorizationService authorizationService; public ZoomProctoringService( final ExamSessionService examSessionService, final ClientHttpRequestFactoryService clientHttpRequestFactoryService, final Cryptor cryptor, final AsyncService asyncService, - final JSONMapper jsonMapper) { + final JSONMapper jsonMapper, + final RemoteProctoringRoomDAO remoteProctoringRoomDAO, + final AuthorizationService authorizationService) { this.examSessionService = examSessionService; this.clientHttpRequestFactoryService = clientHttpRequestFactoryService; this.cryptor = cryptor; this.asyncService = asyncService; this.jsonMapper = jsonMapper; - this.zoomRestTemplate = new ZoomRestTemplate(); + this.zoomRestTemplate = new ZoomRestTemplate(this); + this.remoteProctoringRoomDAO = remoteProctoringRoomDAO; + this.authorizationService = authorizationService; } @Override @@ -132,25 +154,32 @@ public class ZoomProctoringService implements ExamProctoringService { try { + final ClientCredentials credentials = new ClientCredentials( + proctoringSettings.appKey, + proctoringSettings.appSecret); + final ResponseEntity result = this.zoomRestTemplate - .testServiceConnection(proctoringSettings); + .testServiceConnection( + proctoringSettings.serverURL, + credentials); if (result.getStatusCode() != HttpStatus.OK) { throw new APIMessageException( APIMessage.ErrorMessage.BINDING_ERROR, String.valueOf(result.getStatusCode())); - } else { - final UserPageResponse response = this.jsonMapper.readValue( - result.getBody(), - UserPageResponse.class); - - System.out.println(response); - - final ResponseEntity createUser = this.zoomRestTemplate - .createUser(proctoringSettings); - - System.out.println(response); } +// else { +// final UserPageResponse response = this.jsonMapper.readValue( +// result.getBody(), +// UserPageResponse.class); +// +// System.out.println(response); +// +// final ResponseEntity createUser = this.zoomRestTemplate +// .createUser(credentials, "TestRoom"); +// +// System.out.println(response); +// } } catch (final Exception e) { log.error("Failed to access Zoom service at: {}", proctoringSettings.serverURL, e); throw new APIMessageException(APIMessage.ErrorMessage.BINDING_ERROR, e.getMessage()); @@ -160,32 +189,34 @@ public class ZoomProctoringService implements ExamProctoringService { }); } - @Override - public Result getClientBreakOutRoomConnection( - final ProctoringServiceSettings proctoringSettings, - final String connectionToken, - final String roomName, - final String subject) { - - // TODO Auto-generated method stub - return null; - } - - @Override - public Result getClientCollectingRoomConnection( - final ProctoringServiceSettings proctoringSettings, - final String connectionToken, - final String roomName, - final String subject) { - - // TODO Auto-generated method stub - return null; - } - @Override public Map createJoinInstructionAttributes(final ProctoringRoomConnection proctoringConnection) { - // TODO Auto-generated method stub - return null; + final Map attributes = new HashMap<>(); + attributes.put( + ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.SERVICE_TYPE, + ProctoringServiceSettings.ProctoringServerType.ZOOM.name()); + attributes.put( + ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.METHOD, + ClientInstruction.ProctoringInstructionMethod.JOIN.name()); + attributes.put( + ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.ZOOM_URL, + proctoringConnection.serverURL); + attributes.put( + ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.ZOOM_ROOM, + proctoringConnection.roomName); + if (StringUtils.isNotBlank(proctoringConnection.subject)) { + attributes.put( + ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.ZOOM_ROOM_SUBJECT, + proctoringConnection.subject); + } + attributes.put( + ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.ZOOM_TOKEN, + proctoringConnection.accessToken); + attributes.put( + ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.ZOOM_USER_NAME, + proctoringConnection.userName); + + return attributes; } @Override @@ -194,33 +225,136 @@ public class ZoomProctoringService implements ExamProctoringService { final String roomName, final String subject) { - // TODO Auto-generated method stub - return null; + return Result.tryCatch(() -> { + + final RemoteProctoringRoom remoteProctoringRoom = this.remoteProctoringRoomDAO + .getRoom(proctoringSettings.examId, roomName) + .getOrThrow(); + + final AdditionalZoomRoomData additionalZoomRoomData = this.jsonMapper.readValue( + remoteProctoringRoom.additionalRoomData, + AdditionalZoomRoomData.class); + + final ClientCredentials credentials = new ClientCredentials( + proctoringSettings.appKey, + this.cryptor.decrypt(proctoringSettings.appSecret), + this.cryptor.decrypt(remoteProctoringRoom.joinKey)); + + final String jwt = this.createJWTForMeetingAccess(credentials, subject); + + return new ProctoringRoomConnection( + ProctoringServerType.ZOOM, + null, + proctoringSettings.serverURL, + additionalZoomRoomData.join_url, + roomName, + subject, + jwt, + this.authorizationService.getUserService().getCurrentUser().getUsername()); + }); + } + + @Override + public Result getClientRoomConnection( + final ProctoringServiceSettings proctoringSettings, + final String connectionToken, + final String roomName, + final String subject) { + + return Result.tryCatch(() -> { + + final RemoteProctoringRoom remoteProctoringRoom = this.remoteProctoringRoomDAO + .getRoom(proctoringSettings.examId, roomName) + .getOrThrow(); + + final AdditionalZoomRoomData additionalZoomRoomData = this.jsonMapper.readValue( + remoteProctoringRoom.additionalRoomData, + AdditionalZoomRoomData.class); + + final ClientCredentials credentials = new ClientCredentials( + proctoringSettings.appKey, + this.cryptor.decrypt(proctoringSettings.appSecret), + this.cryptor.decrypt(remoteProctoringRoom.joinKey)); + + final String jwt = this.createJWTForMeetingAccess(credentials, subject); + + final ClientConnectionData clientConnection = this.examSessionService + .getConnectionData(connectionToken) + .getOrThrow(); + + return new ProctoringRoomConnection( + ProctoringServerType.ZOOM, + connectionToken, + proctoringSettings.serverURL, + additionalZoomRoomData.join_url, + roomName, + subject, + jwt, + clientConnection.clientConnection.userSessionId); + }); } @Override public Result disposeServiceRoomsForExam(final Exam exam) { - // TODO get all rooms of the exam - // close the rooms on Zoom service - return null; + + return Result.tryCatch(() -> { + //this.remoteProctoringRoomDAO.getRoomsOfExam(exam.id); + }); + // Get all rooms of the exam + } @Override - public Result newCollectingRoom(final Long roomNumber) { - // TODO create new room on zoom side and use the id as room name - return null; + public Result newCollectingRoom( + final ProctoringServiceSettings proctoringSettings, + final Long roomNumber) { + + return createAdHocMeeting( + UUID.randomUUID().toString(), + "Proctoring Room " + (roomNumber + 1), + proctoringSettings); } @Override - public Result newBreakOutRoom(final String subject) { - // TODO create new room on zoom side and use the id as room name - return null; + public Result newBreakOutRoom( + final ProctoringServiceSettings proctoringSettings, + final String subject) { + + return createAdHocMeeting( + UUID.randomUUID().toString(), + subject, + proctoringSettings); } @Override - public Result disposeBreakOutRoom(final String roomName) { - // TODO close the room with specified roomName on zoom side - return null; + public Result disposeBreakOutRoom( + final ProctoringServiceSettings proctoringSettings, + final String roomName) { + + return Result.tryCatch(() -> { + try { + + final RemoteProctoringRoom roomData = this.remoteProctoringRoomDAO + .getRoom(proctoringSettings.examId, roomName) + .getOrThrow(); + + AdditionalZoomRoomData additionalZoomRoomData; + + additionalZoomRoomData = this.jsonMapper.readValue( + roomData.getAdditionalRoomData(), + AdditionalZoomRoomData.class); + + this.deleteAdHocMeeting( + proctoringSettings, + roomName, + additionalZoomRoomData.user_id) + .getOrThrow(); + + } catch (final Exception e) { + throw new RuntimeException("Unexpected error while trying to dispose ad-hoc room for zoom proctoring"); + } + }); + } @Override @@ -237,82 +371,298 @@ public class ZoomProctoringService implements ExamProctoringService { .collect(Collectors.toMap(Tuple::get_1, Tuple::get_2)); } - protected String createPayload( - final String clientKey, - final Long expTime) { + private Result createAdHocMeeting( + final String roomName, + final String subject, + final ProctoringServiceSettings proctoringSettings) { - return String.format( - ZOOM_ACCESS_TOKEN_PAYLOAD.replaceAll(" ", "").replaceAll("\n", ""), - clientKey, - expTime); + return Result.tryCatch(() -> { + final ClientCredentials credentials = new ClientCredentials( + proctoringSettings.appKey, + this.cryptor.decrypt(proctoringSettings.appSecret)); + + // First create a new user/host for the new room + final ResponseEntity createUser = this.zoomRestTemplate.createUser( + proctoringSettings.serverURL, + credentials, + roomName); + final UserResponse userResponse = this.jsonMapper.readValue( + createUser.getBody(), + UserResponse.class); + + // Then create new meeting with the ad-hoc user/host + final CharSequence meetingPwd = UUID.randomUUID().toString(); + final ResponseEntity createMeeting = this.zoomRestTemplate.createMeeting( + roomName, + credentials, + userResponse.id, + subject, + meetingPwd); + final MeetingResponse meetingResponse = this.jsonMapper.readValue( + createMeeting.getBody(), + MeetingResponse.class); + + // TODO start the meeting automatically ??? + + // Create NewRoom data with all needed information to store persistent + final AdditionalZoomRoomData additionalZoomRoomData = new AdditionalZoomRoomData( + userResponse.id, + meetingResponse.start_url, + meetingResponse.join_url); + final String additionalZoomRoomDataString = this.jsonMapper + .writeValueAsString(additionalZoomRoomData); + + return new NewRoom( + roomName, + subject, + this.cryptor.encrypt(meetingPwd), + additionalZoomRoomDataString); + }); } -// private long forExam(final ProctoringServiceSettings 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; -// } + private Result deleteAdHocMeeting( + final ProctoringServiceSettings proctoringSettings, + final String meetingId, + final String userId) { - private class ZoomRestTemplate { + return Result.tryCatch(() -> { + + final ClientCredentials credentials = new ClientCredentials( + proctoringSettings.appKey, + this.cryptor.decrypt(proctoringSettings.appSecret)); + + this.zoomRestTemplate.deleteMeeting(proctoringSettings.serverURL, credentials, meetingId); + this.zoomRestTemplate.deleteUser(proctoringSettings.serverURL, credentials, userId); + + }); + } + + private String createJWTForAPIAccess( + final ClientCredentials credentials, + final Long expTime) { + + try { + + final CharSequence decryptedSecret = this.cryptor.decrypt(credentials.secret); + + final StringBuilder builder = new StringBuilder(); + final Encoder urlEncoder = Base64.getUrlEncoder().withoutPadding(); + + final String jwtHeaderPart = urlEncoder.encodeToString( + ZOOM_ACCESS_TOKEN_HEADER.getBytes(StandardCharsets.UTF_8)); + final String jwtPayload = String.format( + ZOOM_API_ACCESS_TOKEN_PAYLOAD.replaceAll(" ", "").replaceAll("\n", ""), + credentials.clientIdAsString(), + expTime); + final String jwtPayloadPart = urlEncoder.encodeToString( + jwtPayload.getBytes(StandardCharsets.UTF_8)); + final String message = jwtHeaderPart + "." + jwtPayloadPart; + + final Mac sha256_HMAC = Mac.getInstance(TOKEN_ENCODE_ALG); + final SecretKeySpec secret_key = new SecretKeySpec( + Utils.toByteArray(decryptedSecret), + TOKEN_ENCODE_ALG); + sha256_HMAC.init(secret_key); + final String hash = urlEncoder.encodeToString( + sha256_HMAC.doFinal(Utils.toByteArray(message))); + + builder.append(message) + .append(".") + .append(hash); + + return builder.toString(); + } catch (final Exception e) { + throw new RuntimeException("Failed to create JWT for Zoom API access: ", e); + } + } + + private String createJWTForMeetingAccess( + final ClientCredentials credentials, + final String subject) { + + try { + + final long iat = Utils.getMillisecondsNow() / 1000; + final long exp = iat + 7200; + + final CharSequence decryptedSecret = this.cryptor.decrypt(credentials.secret); + final StringBuilder builder = new StringBuilder(); + final Encoder urlEncoder = Base64.getUrlEncoder().withoutPadding(); + + final String jwtHeaderPart = urlEncoder.encodeToString( + ZOOM_ACCESS_TOKEN_HEADER.getBytes(StandardCharsets.UTF_8)); + final String jwtPayload = String.format( + ZOOM_API_ACCESS_TOKEN_PAYLOAD.replaceAll(" ", "").replaceAll("\n", ""), + credentials.clientIdAsString(), + iat, + exp, + subject, + credentials.accessTokenAsString()); + final String jwtPayloadPart = urlEncoder.encodeToString( + jwtPayload.getBytes(StandardCharsets.UTF_8)); + final String message = jwtHeaderPart + "." + jwtPayloadPart; + + final Mac sha256_HMAC = Mac.getInstance(TOKEN_ENCODE_ALG); + final SecretKeySpec secret_key = new SecretKeySpec( + Utils.toByteArray(decryptedSecret), + TOKEN_ENCODE_ALG); + sha256_HMAC.init(secret_key); + final String hash = urlEncoder.encodeToString( + sha256_HMAC.doFinal(Utils.toByteArray(message))); + + builder.append(message) + .append(".") + .append(hash); + + return builder.toString(); + } catch (final Exception e) { + throw new RuntimeException("Failed to create JWT for Zoom meeting access: ", e); + } + } + + private final static class ZoomRestTemplate { private static final String API_TEST_ENDPOINT = "v2/users?status=active&page_size=30&page_number=1&data_type=Json"; + private static final String API_CREATE_USER_ENDPOINT = "v2/users"; + private static final String API_DELETE_USER_ENDPOINT = "v2/users/{userid}?action=delete"; + private static final String API_USER_CUST_CREATE = "custCreate"; + private static final String API_ZOOM_ROOM_USER = "ZoomRoomUser"; + private static final String API_CREATE_MEETING_ENDPOINT = "v2/users/{userid}/meetings"; + private static final String API_DELETE_MEETING_ENDPOINT = "v2/meetings/{meetingid}"; + private final ZoomProctoringService zoomProctoringService; private final RestTemplate restTemplate; private final CircuitBreaker> circuitBreaker; - public ZoomRestTemplate() { + public ZoomRestTemplate(final ZoomProctoringService zoomProctoringService) { - this.restTemplate = new RestTemplate(ZoomProctoringService.this.clientHttpRequestFactoryService + this.zoomProctoringService = zoomProctoringService; + this.restTemplate = new RestTemplate(zoomProctoringService.clientHttpRequestFactoryService .getClientHttpRequestFactory() .getOrThrow()); - this.circuitBreaker = ZoomProctoringService.this.asyncService.createCircuitBreaker( + this.circuitBreaker = zoomProctoringService.asyncService.createCircuitBreaker( 2, 10 * Constants.SECOND_IN_MILLIS, 10 * Constants.SECOND_IN_MILLIS); } - public ResponseEntity testServiceConnection(final ProctoringServiceSettings proctoringSettings) { - final String url = proctoringSettings.serverURL.endsWith(Constants.SLASH.toString()) - ? proctoringSettings.serverURL + API_TEST_ENDPOINT - : proctoringSettings.serverURL + "/" + API_TEST_ENDPOINT; - return exchange(url, HttpMethod.GET, proctoringSettings); + public ResponseEntity testServiceConnection( + final String zoomServerUrl, + final ClientCredentials credentials) { + + final String url = UriComponentsBuilder + .fromUriString(zoomServerUrl) + .path(API_TEST_ENDPOINT) + .toString(); + return exchange(url, HttpMethod.GET, credentials); } - public ResponseEntity createUser(final ProctoringServiceSettings proctoringSettings) - throws JsonProcessingException { - final String url = proctoringSettings.serverURL.endsWith(Constants.SLASH.toString()) - ? proctoringSettings.serverURL + "v2/users" - : proctoringSettings.serverURL + "/" + "v2/users"; - final CreateUserRequest createUserRequest = new CreateUserRequest( - "custCreate", - new CreateUserRequest.UserInfo( - "andreas.hefti@let.ethz.ch", - 1, - "Andreas", - "Hefti")); - final String body = ZoomProctoringService.this.jsonMapper.writeValueAsString(createUserRequest); - final HttpHeaders headers = getHeaders(proctoringSettings); - headers.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); - return exchange(url, HttpMethod.POST, body, headers, null); + public ResponseEntity createUser( + final String zoomServerUrl, + final ClientCredentials credentials, + final String roomName) { + + try { + final String url = UriComponentsBuilder + .fromUriString(zoomServerUrl) + .path(API_CREATE_USER_ENDPOINT) + .toString(); + final String host = new URL(zoomServerUrl).getHost(); + final CreateUserRequest createUserRequest = new CreateUserRequest( + API_USER_CUST_CREATE, + new CreateUserRequest.UserInfo( + roomName + "@" + host, + 1, + roomName, + API_ZOOM_ROOM_USER)); + + final String body = this.zoomProctoringService.jsonMapper.writeValueAsString(createUserRequest); + final HttpHeaders headers = getHeaders(credentials); + headers.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); + return exchange(url, HttpMethod.POST, body, headers); + + } catch (final Exception e) { + log.error("Failed to create Zoom ad-hoc user for room: {}", roomName, e); + throw new RuntimeException("Failed to create Zoom ad-hoc user", e); + } } - private HttpHeaders getHeaders(final ProctoringServiceSettings proctoringSettings) { - final String jwt = createJWT( - proctoringSettings.appKey, - proctoringSettings.appSecret, - System.currentTimeMillis() + Constants.MINUTE_IN_MILLIS); + public ResponseEntity createMeeting( + final String zoomServerUrl, + final ClientCredentials credentials, + final String userId, + final String topic, + final CharSequence password) { + + try { + + final String url = UriComponentsBuilder + .fromUriString(zoomServerUrl) + .path(API_CREATE_MEETING_ENDPOINT) + .buildAndExpand(userId) + .toString(); + + final CreateMeetingRequest createRoomRequest = new CreateMeetingRequest(topic, password); + + final String body = this.zoomProctoringService.jsonMapper.writeValueAsString(createRoomRequest); + final HttpHeaders headers = getHeaders(credentials); + headers.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); + return exchange(url, HttpMethod.POST, body, headers); + + } catch (final Exception e) { + log.error("Failed to create Zoom ad-hoc meeting: {}", topic, e); + throw new RuntimeException("Failed to create Zoom ad-hoc meeting", e); + } + } + + public ResponseEntity deleteMeeting( + final String zoomServerUrl, + final ClientCredentials credentials, + final String meetingId) { + + try { + + final String url = UriComponentsBuilder + .fromUriString(zoomServerUrl) + .path(API_DELETE_MEETING_ENDPOINT) + .buildAndExpand(meetingId) + .toString(); + + return exchange(url, HttpMethod.DELETE, credentials); + + } catch (final Exception e) { + log.error("Failed to delete Zoom ad-hoc meeting: {}", meetingId, e); + throw new RuntimeException("Failed to delete Zoom ad-hoc meeting", e); + } + } + + public ResponseEntity deleteUser( + final String zoomServerUrl, + final ClientCredentials credentials, + final String userId) { + + try { + final String url = UriComponentsBuilder + .fromUriString(zoomServerUrl) + .path(API_DELETE_USER_ENDPOINT) + .buildAndExpand(userId) + .toString(); + + return exchange(url, HttpMethod.DELETE, credentials); + + } catch (final Exception e) { + log.error("Failed to delete Zoom ad-hoc user with id: {}", userId, e); + throw new RuntimeException("Failed to delete Zoom ad-hoc user", e); + } + } + + private HttpHeaders getHeaders(final ClientCredentials credentials) { + final String jwt = this.zoomProctoringService + .createJWTForAPIAccess( + credentials, + System.currentTimeMillis() + Constants.MINUTE_IN_MILLIS); final HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.set(HttpHeaders.AUTHORIZATION, "Bearer " + jwt); @@ -323,38 +673,31 @@ public class ZoomProctoringService implements ExamProctoringService { private ResponseEntity exchange( final String url, final HttpMethod method, - final ProctoringServiceSettings proctoringSettings) { + final ClientCredentials credentials) { - return exchange(url, HttpMethod.GET, null, getHeaders(proctoringSettings), null); + return exchange(url, HttpMethod.GET, null, getHeaders(credentials)); } private ResponseEntity exchange( final String url, final HttpMethod method, final Object body, - final HttpHeaders httpHeaders, - final Map uriVariables) { + final HttpHeaders httpHeaders) { final Result> protectedRunResult = this.circuitBreaker.protectedRun(() -> { final HttpEntity httpEntity = (body != null) ? new HttpEntity<>(body, httpHeaders) : new HttpEntity<>(httpHeaders); - final ResponseEntity result = (uriVariables != null) - ? this.restTemplate.exchange( - url, - method, - httpEntity, - String.class, - uriVariables) - : this.restTemplate.exchange( - url, - method, - httpEntity, - String.class); + final ResponseEntity result = this.restTemplate.exchange( + url, + method, + httpEntity, + String.class); if (result.getStatusCode() != HttpStatus.OK) { log.warn("Zoom API call to {} respond not 200 -> {}", url, result.getStatusCode()); + throw new RuntimeException("Error Response: " + result.getStatusCode()); } return result; @@ -362,39 +705,29 @@ public class ZoomProctoringService implements ExamProctoringService { return protectedRunResult.getOrThrow(); } - private String createJWT( - final String appKey, - final CharSequence appSecret, - final Long expTime) { + } - try { - final StringBuilder builder = new StringBuilder(); - final Encoder urlEncoder = Base64.getUrlEncoder().withoutPadding(); + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class AdditionalZoomRoomData { - final String jwtHeaderPart = urlEncoder.encodeToString( - ZOOM_ACCESS_TOKEN_HEADER.getBytes(StandardCharsets.UTF_8)); - final String jwtPayload = createPayload(appKey, expTime); - final String jwtPayloadPart = urlEncoder.encodeToString( - jwtPayload.getBytes(StandardCharsets.UTF_8)); - final String message = jwtHeaderPart + "." + jwtPayloadPart; + @JsonProperty("user_id") + public final String user_id; + @JsonProperty("start_url") + public final String start_url; + @JsonProperty("join_url") + public final String join_url; - final Mac sha256_HMAC = Mac.getInstance(TOKEN_ENCODE_ALG); - final SecretKeySpec secret_key = new SecretKeySpec( - Utils.toByteArray(appSecret), - TOKEN_ENCODE_ALG); - sha256_HMAC.init(secret_key); - final String hash = urlEncoder.encodeToString( - sha256_HMAC.doFinal(Utils.toByteArray(message))); + @JsonCreator + public AdditionalZoomRoomData( + @JsonProperty("user_id") final String user_id, + @JsonProperty("start_url") final String start_url, + @JsonProperty("join_url") final String join_url) { - builder.append(message) - .append(".") - .append(hash); - - return builder.toString(); - } catch (final Exception e) { - throw new RuntimeException("Failed to create JWT for Zoom API access: ", e); - } + this.user_id = user_id; + this.start_url = start_url; + this.join_url = join_url; } + } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ZoomRoomRequestResponse.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ZoomRoomRequestResponse.java index 77ecba9b..517beee2 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ZoomRoomRequestResponse.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ZoomRoomRequestResponse.java @@ -88,14 +88,14 @@ public interface ZoomRoomRequestResponse { } @JsonIgnoreProperties(ignoreUnknown = true) - static class CreateUserResponse { + static class UserResponse { final String id; final String email; final int type; final String first_name; final String lasr_name; @JsonCreator - public CreateUserResponse( + public UserResponse( @JsonProperty("id") final String id, @JsonProperty("email") final String email, @JsonProperty("type") final int type, @@ -111,34 +111,34 @@ public interface ZoomRoomRequestResponse { // https://marketplace.zoom.us/docs/api-reference/zoom-api/meetings/meetingcreate @JsonIgnoreProperties(ignoreUnknown = true) - static class NewRoomRequest { + static class CreateMeetingRequest { @JsonProperty final String topic; @JsonProperty final int type = 1; // Instant meeting @JsonProperty final String start_time = DateTime.now(DateTimeZone.UTC).toString("yyyy-MM-dd`T`HH:mm:ssZ"); - @JsonProperty final String password; + @JsonProperty final CharSequence password; @JsonProperty final Settings settings; - public NewRoomRequest(final String topic, final String password, final Settings settings) { + public CreateMeetingRequest(final String topic, final CharSequence password) { this.topic = topic; this.password = password; - this.settings = settings; + this.settings = new Settings(); } @JsonIgnoreProperties(ignoreUnknown = true) static class Settings { - final boolean host_video = false; - final boolean participant_video = true; - final boolean join_before_host = true; - final int jbh_time = 0; - final boolean use_pmi = false; - final String audio = "voip"; + @JsonProperty final boolean host_video = false; + @JsonProperty final boolean participant_video = true; + @JsonProperty final boolean join_before_host = true; + @JsonProperty final int jbh_time = 0; + @JsonProperty final boolean use_pmi = false; + @JsonProperty final String audio = "voip"; } } // https://marketplace.zoom.us/docs/api-reference/zoom-api/meetings/meetingcreate @JsonIgnoreProperties(ignoreUnknown = true) - static class NewRoomResponse { - final Integer id; + static class MeetingResponse { + final Long id; final String join_url; final String start_url; final String start_time; @@ -148,8 +148,8 @@ public interface ZoomRoomRequestResponse { final String host_id; @JsonCreator - public NewRoomResponse( - @JsonProperty("id") final Integer id, + public MeetingResponse( + @JsonProperty("id") final Long id, @JsonProperty("join_url") final String join_url, @JsonProperty("start_url") final String start_url, @JsonProperty("start_time") final String start_time, diff --git a/src/main/resources/config/sql/base/V6__create_alter_tables_v1_2.sql b/src/main/resources/config/sql/base/V6__create_alter_tables_v1_2.sql index c914294a..142e4161 100644 --- a/src/main/resources/config/sql/base/V6__create_alter_tables_v1_2.sql +++ b/src/main/resources/config/sql/base/V6__create_alter_tables_v1_2.sql @@ -9,6 +9,6 @@ ADD COLUMN IF NOT EXISTS `vdi_pair_token` VARCHAR(255) NULL AFTER `vdi`; -- Alter Table `remote_proctoring_room` -- ----------------------------------------------------- ALTER TABLE `remote_proctoring_room` -ADD COLUMN IF NOT EXISTS `break_out_connections` VARCHAR(10000) NULL, +ADD COLUMN IF NOT EXISTS `break_out_connections` VARCHAR(4000) NULL, ADD COLUMN IF NOT EXISTS `join_key` VARCHAR(255) NULL, -ADD COLUMN IF NOT EXISTS `room_data` VARCHAR(10000) NULL; \ No newline at end of file +ADD COLUMN IF NOT EXISTS `room_data` VARCHAR(4000) NULL; \ No newline at end of file diff --git a/src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamJITSIProctoringServiceTest.java b/src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamJITSIProctoringServiceTest.java index f7e05e6c..864b8097 100644 --- a/src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamJITSIProctoringServiceTest.java +++ b/src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamJITSIProctoringServiceTest.java @@ -18,7 +18,6 @@ import org.junit.Test; import org.mockito.Mockito; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringRoomConnection; -import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringServerType; import ch.ethz.seb.sebserver.gbl.util.Cryptor; public class ExamJITSIProctoringServiceTest { @@ -64,7 +63,6 @@ public class ExamJITSIProctoringServiceTest { final JitsiProctoringService examJITSIProctoringService = new JitsiProctoringService(null, null, cryptorMock, null); final ProctoringRoomConnection data = examJITSIProctoringService.createProctoringConnection( - ProctoringServerType.JITSI_MEET, "connectionToken", "https://seb-jitsi.example.ch", "test-app", diff --git a/src/test/resources/schema-test.sql b/src/test/resources/schema-test.sql index f0c75403..7e71738d 100644 --- a/src/test/resources/schema-test.sql +++ b/src/test/resources/schema-test.sql @@ -95,9 +95,9 @@ CREATE TABLE IF NOT EXISTS `remote_proctoring_room` ( `size` INT NULL, `subject` VARCHAR(255) NULL, `townhall_room` INT(1) NOT NULL DEFAULT 0, - `break_out_connections` VARCHAR(10000) NULL, + `break_out_connections` VARCHAR(4000) NULL, `join_key` VARCHAR(255) NULL, - `room_data` VARCHAR(10000) NULL, + `room_data` VARCHAR(4000) NULL, PRIMARY KEY (`id`), INDEX `proctor_room_exam_id_idx` (`exam_id` ASC), CONSTRAINT `proctorRoomExamRef`