zoom integration
This commit is contained in:
parent
564b7ec9c9
commit
43383bcebb
13 changed files with 574 additions and 281 deletions
|
@ -53,6 +53,7 @@ public final class Constants {
|
||||||
public static final Character SLASH = '/';
|
public static final Character SLASH = '/';
|
||||||
public static final Character BACKSLASH = '\\';
|
public static final Character BACKSLASH = '\\';
|
||||||
public static final Character QUOTE = '\'';
|
public static final Character QUOTE = '\'';
|
||||||
|
public static final Character QUERY = '?';
|
||||||
public static final Character DOUBLE_QUOTE = '"';
|
public static final Character DOUBLE_QUOTE = '"';
|
||||||
public static final Character COMMA = ',';
|
public static final Character COMMA = ',';
|
||||||
public static final Character PIPE = '|';
|
public static final Character PIPE = '|';
|
||||||
|
|
|
@ -24,6 +24,7 @@ public class ProctoringRoomConnection {
|
||||||
public static final String ATTR_SUBJECT = "subject";
|
public static final String ATTR_SUBJECT = "subject";
|
||||||
public static final String ATTR_ACCESS_TOKEN = "accessToken";
|
public static final String ATTR_ACCESS_TOKEN = "accessToken";
|
||||||
public static final String ATTR_CONNECTION_URL = "connectionURL";
|
public static final String ATTR_CONNECTION_URL = "connectionURL";
|
||||||
|
public static final String ATTR_USER_NAME = "userName";
|
||||||
|
|
||||||
@JsonProperty(ProctoringServiceSettings.ATTR_SERVER_TYPE)
|
@JsonProperty(ProctoringServiceSettings.ATTR_SERVER_TYPE)
|
||||||
public final ProctoringServerType proctoringServerType;
|
public final ProctoringServerType proctoringServerType;
|
||||||
|
@ -46,6 +47,9 @@ public class ProctoringRoomConnection {
|
||||||
@JsonProperty(ATTR_ACCESS_TOKEN)
|
@JsonProperty(ATTR_ACCESS_TOKEN)
|
||||||
public final String accessToken;
|
public final String accessToken;
|
||||||
|
|
||||||
|
@JsonProperty(ATTR_USER_NAME)
|
||||||
|
public final String userName;
|
||||||
|
|
||||||
@JsonCreator
|
@JsonCreator
|
||||||
public ProctoringRoomConnection(
|
public ProctoringRoomConnection(
|
||||||
@JsonProperty(ProctoringServiceSettings.ATTR_SERVER_TYPE) final ProctoringServerType proctoringServerType,
|
@JsonProperty(ProctoringServiceSettings.ATTR_SERVER_TYPE) final ProctoringServerType proctoringServerType,
|
||||||
|
@ -54,7 +58,8 @@ public class ProctoringRoomConnection {
|
||||||
@JsonProperty(ATTR_SERVER_URL) final String serverURL,
|
@JsonProperty(ATTR_SERVER_URL) final String serverURL,
|
||||||
@JsonProperty(ATTR_ROOM_NAME) final String roomName,
|
@JsonProperty(ATTR_ROOM_NAME) final String roomName,
|
||||||
@JsonProperty(ATTR_SUBJECT) final String subject,
|
@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.proctoringServerType = proctoringServerType;
|
||||||
this.connectionToken = connectionToken;
|
this.connectionToken = connectionToken;
|
||||||
|
@ -63,6 +68,7 @@ public class ProctoringRoomConnection {
|
||||||
this.roomName = roomName;
|
this.roomName = roomName;
|
||||||
this.subject = subject;
|
this.subject = subject;
|
||||||
this.accessToken = accessToken;
|
this.accessToken = accessToken;
|
||||||
|
this.userName = userName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ProctoringServerType getProctoringServerType() {
|
public ProctoringServerType getProctoringServerType() {
|
||||||
|
|
|
@ -51,6 +51,7 @@ public final class ClientInstruction {
|
||||||
public static final String ZOOM_URL = "zoomMeetServerURL";
|
public static final String ZOOM_URL = "zoomMeetServerURL";
|
||||||
public static final String ZOOM_ROOM = "zoomMeetRoom";
|
public static final String ZOOM_ROOM = "zoomMeetRoom";
|
||||||
public static final String ZOOM_ROOM_SUBJECT = "zoomMeetSubject";
|
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_TOKEN = "zoomMeetToken";
|
||||||
public static final String ZOOM_RECEIVE_AUDIO = "zoomMeetReceiveAudio";
|
public static final String ZOOM_RECEIVE_AUDIO = "zoomMeetReceiveAudio";
|
||||||
public static final String ZOOM_RECEIVE_VIDEO = "zoomMeetReceiveVideo";
|
public static final String ZOOM_RECEIVE_VIDEO = "zoomMeetReceiveVideo";
|
||||||
|
|
|
@ -686,5 +686,4 @@ public final class Utils {
|
||||||
public static String valueOrEmptyNote(final String value) {
|
public static String valueOrEmptyNote(final String value) {
|
||||||
return StringUtils.isBlank(value) ? Constants.EMPTY_NOTE : value;
|
return StringUtils.isBlank(value) ? Constants.EMPTY_NOTE : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,8 @@ public interface RemoteProctoringRoomDAO {
|
||||||
|
|
||||||
Result<Collection<RemoteProctoringRoom>> getCollectingRoomsForExam(Long examId);
|
Result<Collection<RemoteProctoringRoom>> getCollectingRoomsForExam(Long examId);
|
||||||
|
|
||||||
|
//Result<Collection<RemoteProctoringRoom>> getRoomsOfExam(Long examId);
|
||||||
|
|
||||||
Result<RemoteProctoringRoom> getRoom(Long roomId);
|
Result<RemoteProctoringRoom> getRoom(Long roomId);
|
||||||
|
|
||||||
Result<RemoteProctoringRoom> getRoom(Long examId, String roomName);
|
Result<RemoteProctoringRoom> getRoom(Long examId, String roomName);
|
||||||
|
|
|
@ -44,51 +44,20 @@ public interface ExamProctoringService {
|
||||||
String roomName,
|
String roomName,
|
||||||
String subject);
|
String subject);
|
||||||
|
|
||||||
Result<ProctoringRoomConnection> getClientBreakOutRoomConnection(
|
Result<ProctoringRoomConnection> getClientRoomConnection(
|
||||||
final ProctoringServiceSettings proctoringSettings,
|
final ProctoringServiceSettings proctoringSettings,
|
||||||
final String connectionToken,
|
final String connectionToken,
|
||||||
final String roomName,
|
final String roomName,
|
||||||
final String subject);
|
final String subject);
|
||||||
|
|
||||||
Result<ProctoringRoomConnection> getClientCollectingRoomConnection(
|
// Result<ProctoringRoomConnection> getClientCollectingRoomConnection(
|
||||||
final ProctoringServiceSettings proctoringSettings,
|
// final ProctoringServiceSettings proctoringSettings,
|
||||||
final String connectionToken,
|
// final String connectionToken,
|
||||||
final String roomName,
|
// final String roomName,
|
||||||
final String subject);
|
// final String subject);
|
||||||
|
|
||||||
Map<String, String> createJoinInstructionAttributes(final ProctoringRoomConnection proctoringConnection);
|
Map<String, String> 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<ProctoringRoomConnection> sendJoinRoomToClients(
|
|
||||||
// ProctoringServiceSettings proctoringSettings,
|
|
||||||
// Collection<String> 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<Void> sendJoinCollectingRoomToClients(
|
|
||||||
// ProctoringServiceSettings proctoringSettings,
|
|
||||||
// Collection<String> clientConnectionTokens);
|
|
||||||
|
|
||||||
Result<Void> disposeServiceRoomsForExam(Exam exam);
|
Result<Void> disposeServiceRoomsForExam(Exam exam);
|
||||||
|
|
||||||
|
@ -100,11 +69,11 @@ public interface ExamProctoringService {
|
||||||
throw new RuntimeException("Test Why: " + connectionToken);
|
throw new RuntimeException("Test Why: " + connectionToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result<NewRoom> newCollectingRoom(Long roomNumber);
|
Result<NewRoom> newCollectingRoom(ProctoringServiceSettings proctoringSettings, Long roomNumber);
|
||||||
|
|
||||||
Result<NewRoom> newBreakOutRoom(String subject);
|
Result<NewRoom> newBreakOutRoom(ProctoringServiceSettings proctoringSettings, String subject);
|
||||||
|
|
||||||
Result<Void> disposeBreakOutRoom(String roomName);
|
Result<Void> disposeBreakOutRoom(ProctoringServiceSettings proctoringSettings, String roomName);
|
||||||
|
|
||||||
Map<String, String> getDefaultInstructionAttributes();
|
Map<String, String> getDefaultInstructionAttributes();
|
||||||
|
|
||||||
|
|
|
@ -149,7 +149,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
||||||
|
|
||||||
// First create and get the town-hall room for specified exam
|
// First create and get the town-hall room for specified exam
|
||||||
final RemoteProctoringRoom townhallRoom = examProctoringService
|
final RemoteProctoringRoom townhallRoom = examProctoringService
|
||||||
.newBreakOutRoom(subject)
|
.newBreakOutRoom(settings, subject)
|
||||||
.flatMap(room -> this.remoteProctoringRoomDAO.createTownhallRoom(examId, room))
|
.flatMap(room -> this.remoteProctoringRoomDAO.createTownhallRoom(examId, room))
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
|
||||||
|
@ -187,7 +187,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
|
||||||
final RemoteProctoringRoom breakOutRoom = examProctoringService
|
final RemoteProctoringRoom breakOutRoom = examProctoringService
|
||||||
.newBreakOutRoom(subject)
|
.newBreakOutRoom(settings, subject)
|
||||||
.flatMap(room -> this.remoteProctoringRoomDAO.createBreakOutRoom(examId, room, connectionTokens))
|
.flatMap(room -> this.remoteProctoringRoomDAO.createBreakOutRoom(examId, room, connectionTokens))
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
|
||||||
|
@ -302,7 +302,9 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
||||||
return this.remoteProctoringRoomDAO.reservePlaceInCollectingRoom(
|
return this.remoteProctoringRoomDAO.reservePlaceInCollectingRoom(
|
||||||
examId,
|
examId,
|
||||||
proctoringSettings.collectingRoomSize,
|
proctoringSettings.collectingRoomSize,
|
||||||
examProctoringService::newCollectingRoom)
|
roomNumber -> examProctoringService.newCollectingRoom(
|
||||||
|
proctoringSettings,
|
||||||
|
roomNumber))
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
|
@ -334,7 +336,9 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
||||||
this.remoteProctoringRoomDAO
|
this.remoteProctoringRoomDAO
|
||||||
.getTownhallRoom(examId)
|
.getTownhallRoom(examId)
|
||||||
.map(RemoteProctoringRoom::getName)
|
.map(RemoteProctoringRoom::getName)
|
||||||
.flatMap(examProctoringService::disposeBreakOutRoom)
|
.flatMap(roomName -> examProctoringService.disposeBreakOutRoom(
|
||||||
|
proctoringSettings,
|
||||||
|
roomName))
|
||||||
.flatMap(service -> this.remoteProctoringRoomDAO.deleteTownhallRoom(examId))
|
.flatMap(service -> this.remoteProctoringRoomDAO.deleteTownhallRoom(examId))
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
|
||||||
|
@ -382,7 +386,8 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
|
||||||
// Dispose the proctoring room on service side
|
// Dispose the proctoring room on service side
|
||||||
examProctoringService.disposeBreakOutRoom(remoteProctoringRoom.name)
|
examProctoringService
|
||||||
|
.disposeBreakOutRoom(proctoringSettings, remoteProctoringRoom.name)
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
|
||||||
// Send join collecting rooms to involving clients
|
// Send join collecting rooms to involving clients
|
||||||
|
@ -448,12 +453,13 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
||||||
connectionTokens
|
connectionTokens
|
||||||
.stream()
|
.stream()
|
||||||
.forEach(connectionToken -> {
|
.forEach(connectionToken -> {
|
||||||
this.sebInstructionService.registerInstruction(
|
this.sebInstructionService
|
||||||
examId,
|
.registerInstruction(
|
||||||
InstructionType.SEB_RECONFIGURE_SETTINGS,
|
examId,
|
||||||
attributes,
|
InstructionType.SEB_RECONFIGURE_SETTINGS,
|
||||||
connectionToken,
|
attributes,
|
||||||
true)
|
connectionToken,
|
||||||
|
true)
|
||||||
.onError(error -> log.error(
|
.onError(error -> log.error(
|
||||||
"Failed to register reconfiguring instruction for connection: {}",
|
"Failed to register reconfiguring instruction for connection: {}",
|
||||||
connectionToken,
|
connectionToken,
|
||||||
|
@ -496,17 +502,17 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
||||||
clientConnectionTokens
|
clientConnectionTokens
|
||||||
.stream()
|
.stream()
|
||||||
.forEach(connectionToken -> {
|
.forEach(connectionToken -> {
|
||||||
final ProctoringRoomConnection proctoringConnection =
|
final ProctoringRoomConnection proctoringConnection = examProctoringService
|
||||||
examProctoringService.getClientBreakOutRoomConnection(
|
.getClientRoomConnection(
|
||||||
proctoringSettings,
|
proctoringSettings,
|
||||||
connectionToken,
|
connectionToken,
|
||||||
examProctoringService.verifyRoomName(roomName, connectionToken),
|
examProctoringService.verifyRoomName(roomName, connectionToken),
|
||||||
(StringUtils.isNotBlank(subject)) ? subject : roomName)
|
(StringUtils.isNotBlank(subject)) ? subject : roomName)
|
||||||
.onError(error -> log.error(
|
.onError(error -> log.error(
|
||||||
"Failed to get client room connection data for {} cause: {}",
|
"Failed to get client room connection data for {} cause: {}",
|
||||||
connectionToken,
|
connectionToken,
|
||||||
error.getMessage()))
|
error.getMessage()))
|
||||||
.get();
|
.get();
|
||||||
if (proctoringConnection != null) {
|
if (proctoringConnection != null) {
|
||||||
sendJoinInstruction(
|
sendJoinInstruction(
|
||||||
proctoringSettings.examId,
|
proctoringSettings.examId,
|
||||||
|
@ -551,7 +557,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
|
||||||
final ProctoringRoomConnection proctoringConnection = examProctoringService
|
final ProctoringRoomConnection proctoringConnection = examProctoringService
|
||||||
.getClientCollectingRoomConnection(
|
.getClientRoomConnection(
|
||||||
proctoringSettings,
|
proctoringSettings,
|
||||||
clientConnection.clientConnection.connectionToken,
|
clientConnection.clientConnection.connectionToken,
|
||||||
roomName,
|
roomName,
|
||||||
|
@ -577,12 +583,13 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
||||||
final Map<String, String> attributes = examProctoringService
|
final Map<String, String> attributes = examProctoringService
|
||||||
.createJoinInstructionAttributes(proctoringConnection);
|
.createJoinInstructionAttributes(proctoringConnection);
|
||||||
|
|
||||||
this.sebInstructionService.registerInstruction(
|
this.sebInstructionService
|
||||||
examId,
|
.registerInstruction(
|
||||||
InstructionType.SEB_PROCTORING,
|
examId,
|
||||||
attributes,
|
InstructionType.SEB_PROCTORING,
|
||||||
connectionToken,
|
attributes,
|
||||||
true)
|
connectionToken,
|
||||||
|
true)
|
||||||
.onError(error -> log.error("Failed to send join instruction: {}", connectionToken, error));
|
.onError(error -> log.error("Failed to send join instruction: {}", connectionToken, error));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -170,21 +170,30 @@ public class JitsiProctoringService implements ExamProctoringService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<NewRoom> newCollectingRoom(final Long roomNumber) {
|
public Result<NewRoom> newCollectingRoom(
|
||||||
|
final ProctoringServiceSettings proctoringSettings,
|
||||||
|
final Long roomNumber) {
|
||||||
|
|
||||||
return Result.of(new NewRoom(
|
return Result.of(new NewRoom(
|
||||||
UUID.randomUUID().toString(),
|
UUID.randomUUID().toString(),
|
||||||
"Room " + (roomNumber + 1)));
|
"Room " + (roomNumber + 1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<NewRoom> newBreakOutRoom(final String subject) {
|
public Result<NewRoom> newBreakOutRoom(
|
||||||
|
final ProctoringServiceSettings proctoringSettings,
|
||||||
|
final String subject) {
|
||||||
|
|
||||||
return Result.of(new NewRoom(
|
return Result.of(new NewRoom(
|
||||||
UUID.randomUUID().toString(),
|
UUID.randomUUID().toString(),
|
||||||
subject));
|
subject));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<Void> disposeBreakOutRoom(final String roomName) {
|
public Result<Void> disposeBreakOutRoom(
|
||||||
|
final ProctoringServiceSettings proctoringSettings,
|
||||||
|
final String roomName) {
|
||||||
|
|
||||||
return Result.EMPTY;
|
return Result.EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -237,7 +246,6 @@ public class JitsiProctoringService implements ExamProctoringService {
|
||||||
|
|
||||||
return Result.tryCatch(() -> {
|
return Result.tryCatch(() -> {
|
||||||
return createProctoringConnection(
|
return createProctoringConnection(
|
||||||
proctoringSettings.serverType,
|
|
||||||
null,
|
null,
|
||||||
proctoringSettings.serverURL,
|
proctoringSettings.serverURL,
|
||||||
proctoringSettings.appKey,
|
proctoringSettings.appKey,
|
||||||
|
@ -253,7 +261,7 @@ public class JitsiProctoringService implements ExamProctoringService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<ProctoringRoomConnection> getClientCollectingRoomConnection(
|
public Result<ProctoringRoomConnection> getClientRoomConnection(
|
||||||
final ProctoringServiceSettings proctoringSettings,
|
final ProctoringServiceSettings proctoringSettings,
|
||||||
final String connectionToken,
|
final String connectionToken,
|
||||||
final String roomName,
|
final String roomName,
|
||||||
|
@ -265,7 +273,6 @@ public class JitsiProctoringService implements ExamProctoringService {
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
|
||||||
return createProctoringConnection(
|
return createProctoringConnection(
|
||||||
proctoringSettings.serverType,
|
|
||||||
null,
|
null,
|
||||||
proctoringSettings.serverURL,
|
proctoringSettings.serverURL,
|
||||||
proctoringSettings.appKey,
|
proctoringSettings.appKey,
|
||||||
|
@ -280,38 +287,7 @@ public class JitsiProctoringService implements ExamProctoringService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Result<ProctoringRoomConnection> 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<ProctoringRoomConnection> createProctoringConnection(
|
protected Result<ProctoringRoomConnection> createProctoringConnection(
|
||||||
final ProctoringServerType proctoringServerType,
|
|
||||||
final String connectionToken,
|
final String connectionToken,
|
||||||
final String url,
|
final String url,
|
||||||
final String appKey,
|
final String appKey,
|
||||||
|
@ -341,13 +317,14 @@ public class JitsiProctoringService implements ExamProctoringService {
|
||||||
moderator);
|
moderator);
|
||||||
|
|
||||||
return new ProctoringRoomConnection(
|
return new ProctoringRoomConnection(
|
||||||
proctoringServerType,
|
ProctoringServerType.JITSI_MEET,
|
||||||
connectionToken,
|
connectionToken,
|
||||||
host,
|
host,
|
||||||
url,
|
url,
|
||||||
roomName,
|
roomName,
|
||||||
subject,
|
subject,
|
||||||
token);
|
token,
|
||||||
|
clientName);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,16 +8,20 @@
|
||||||
|
|
||||||
package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.proctoring;
|
package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.proctoring;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.Base64.Encoder;
|
import java.util.Base64.Encoder;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import javax.crypto.Mac;
|
import javax.crypto.Mac;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
@ -29,8 +33,11 @@ import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.web.client.RestTemplate;
|
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.ClientHttpRequestFactoryService;
|
||||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
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.api.JSONMapper;
|
||||||
import ch.ethz.seb.sebserver.gbl.async.AsyncService;
|
import ch.ethz.seb.sebserver.gbl.async.AsyncService;
|
||||||
import ch.ethz.seb.sebserver.gbl.async.CircuitBreaker;
|
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.Exam;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringRoomConnection;
|
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;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringServerType;
|
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.ClientInstruction;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom;
|
||||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Cryptor;
|
import ch.ethz.seb.sebserver.gbl.util.Cryptor;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Tuple;
|
import ch.ethz.seb.sebserver.gbl.util.Tuple;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
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.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.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.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
|
@Lazy
|
||||||
@Service
|
@Service
|
||||||
|
@ -67,8 +81,10 @@ public class ZoomProctoringService implements ExamProctoringService {
|
||||||
|
|
||||||
private static final String ZOOM_ACCESS_TOKEN_HEADER =
|
private static final String ZOOM_ACCESS_TOKEN_HEADER =
|
||||||
"{\"alg\":\"HS256\",\"typ\":\"JWT\"}";
|
"{\"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}";
|
"{\"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<String, String> SEB_API_NAME_INSTRUCTION_NAME_MAPPING = Utils.immutableMapOf(Arrays.asList(
|
private static final Map<String, String> SEB_API_NAME_INSTRUCTION_NAME_MAPPING = Utils.immutableMapOf(Arrays.asList(
|
||||||
new Tuple<>(
|
new Tuple<>(
|
||||||
|
@ -100,20 +116,26 @@ public class ZoomProctoringService implements ExamProctoringService {
|
||||||
private final AsyncService asyncService;
|
private final AsyncService asyncService;
|
||||||
private final JSONMapper jsonMapper;
|
private final JSONMapper jsonMapper;
|
||||||
private final ZoomRestTemplate zoomRestTemplate;
|
private final ZoomRestTemplate zoomRestTemplate;
|
||||||
|
private final RemoteProctoringRoomDAO remoteProctoringRoomDAO;
|
||||||
|
private final AuthorizationService authorizationService;
|
||||||
|
|
||||||
public ZoomProctoringService(
|
public ZoomProctoringService(
|
||||||
final ExamSessionService examSessionService,
|
final ExamSessionService examSessionService,
|
||||||
final ClientHttpRequestFactoryService clientHttpRequestFactoryService,
|
final ClientHttpRequestFactoryService clientHttpRequestFactoryService,
|
||||||
final Cryptor cryptor,
|
final Cryptor cryptor,
|
||||||
final AsyncService asyncService,
|
final AsyncService asyncService,
|
||||||
final JSONMapper jsonMapper) {
|
final JSONMapper jsonMapper,
|
||||||
|
final RemoteProctoringRoomDAO remoteProctoringRoomDAO,
|
||||||
|
final AuthorizationService authorizationService) {
|
||||||
|
|
||||||
this.examSessionService = examSessionService;
|
this.examSessionService = examSessionService;
|
||||||
this.clientHttpRequestFactoryService = clientHttpRequestFactoryService;
|
this.clientHttpRequestFactoryService = clientHttpRequestFactoryService;
|
||||||
this.cryptor = cryptor;
|
this.cryptor = cryptor;
|
||||||
this.asyncService = asyncService;
|
this.asyncService = asyncService;
|
||||||
this.jsonMapper = jsonMapper;
|
this.jsonMapper = jsonMapper;
|
||||||
this.zoomRestTemplate = new ZoomRestTemplate();
|
this.zoomRestTemplate = new ZoomRestTemplate(this);
|
||||||
|
this.remoteProctoringRoomDAO = remoteProctoringRoomDAO;
|
||||||
|
this.authorizationService = authorizationService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -132,25 +154,32 @@ public class ZoomProctoringService implements ExamProctoringService {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
|
final ClientCredentials credentials = new ClientCredentials(
|
||||||
|
proctoringSettings.appKey,
|
||||||
|
proctoringSettings.appSecret);
|
||||||
|
|
||||||
final ResponseEntity<String> result = this.zoomRestTemplate
|
final ResponseEntity<String> result = this.zoomRestTemplate
|
||||||
.testServiceConnection(proctoringSettings);
|
.testServiceConnection(
|
||||||
|
proctoringSettings.serverURL,
|
||||||
|
credentials);
|
||||||
|
|
||||||
if (result.getStatusCode() != HttpStatus.OK) {
|
if (result.getStatusCode() != HttpStatus.OK) {
|
||||||
throw new APIMessageException(
|
throw new APIMessageException(
|
||||||
APIMessage.ErrorMessage.BINDING_ERROR,
|
APIMessage.ErrorMessage.BINDING_ERROR,
|
||||||
String.valueOf(result.getStatusCode()));
|
String.valueOf(result.getStatusCode()));
|
||||||
} else {
|
|
||||||
final UserPageResponse response = this.jsonMapper.readValue(
|
|
||||||
result.getBody(),
|
|
||||||
UserPageResponse.class);
|
|
||||||
|
|
||||||
System.out.println(response);
|
|
||||||
|
|
||||||
final ResponseEntity<String> 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<String> createUser = this.zoomRestTemplate
|
||||||
|
// .createUser(credentials, "TestRoom");
|
||||||
|
//
|
||||||
|
// System.out.println(response);
|
||||||
|
// }
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
log.error("Failed to access Zoom service at: {}", proctoringSettings.serverURL, e);
|
log.error("Failed to access Zoom service at: {}", proctoringSettings.serverURL, e);
|
||||||
throw new APIMessageException(APIMessage.ErrorMessage.BINDING_ERROR, e.getMessage());
|
throw new APIMessageException(APIMessage.ErrorMessage.BINDING_ERROR, e.getMessage());
|
||||||
|
@ -160,32 +189,34 @@ public class ZoomProctoringService implements ExamProctoringService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Result<ProctoringRoomConnection> getClientBreakOutRoomConnection(
|
|
||||||
final ProctoringServiceSettings proctoringSettings,
|
|
||||||
final String connectionToken,
|
|
||||||
final String roomName,
|
|
||||||
final String subject) {
|
|
||||||
|
|
||||||
// TODO Auto-generated method stub
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Result<ProctoringRoomConnection> getClientCollectingRoomConnection(
|
|
||||||
final ProctoringServiceSettings proctoringSettings,
|
|
||||||
final String connectionToken,
|
|
||||||
final String roomName,
|
|
||||||
final String subject) {
|
|
||||||
|
|
||||||
// TODO Auto-generated method stub
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, String> createJoinInstructionAttributes(final ProctoringRoomConnection proctoringConnection) {
|
public Map<String, String> createJoinInstructionAttributes(final ProctoringRoomConnection proctoringConnection) {
|
||||||
// TODO Auto-generated method stub
|
final Map<String, String> attributes = new HashMap<>();
|
||||||
return null;
|
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
|
@Override
|
||||||
|
@ -194,33 +225,136 @@ public class ZoomProctoringService implements ExamProctoringService {
|
||||||
final String roomName,
|
final String roomName,
|
||||||
final String subject) {
|
final String subject) {
|
||||||
|
|
||||||
// TODO Auto-generated method stub
|
return Result.tryCatch(() -> {
|
||||||
return null;
|
|
||||||
|
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<ProctoringRoomConnection> 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
|
@Override
|
||||||
public Result<Void> disposeServiceRoomsForExam(final Exam exam) {
|
public Result<Void> disposeServiceRoomsForExam(final Exam exam) {
|
||||||
// TODO get all rooms of the exam
|
|
||||||
// close the rooms on Zoom service
|
return Result.tryCatch(() -> {
|
||||||
return null;
|
//this.remoteProctoringRoomDAO.getRoomsOfExam(exam.id);
|
||||||
|
});
|
||||||
|
// Get all rooms of the exam
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<NewRoom> newCollectingRoom(final Long roomNumber) {
|
public Result<NewRoom> newCollectingRoom(
|
||||||
// TODO create new room on zoom side and use the id as room name
|
final ProctoringServiceSettings proctoringSettings,
|
||||||
return null;
|
final Long roomNumber) {
|
||||||
|
|
||||||
|
return createAdHocMeeting(
|
||||||
|
UUID.randomUUID().toString(),
|
||||||
|
"Proctoring Room " + (roomNumber + 1),
|
||||||
|
proctoringSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<NewRoom> newBreakOutRoom(final String subject) {
|
public Result<NewRoom> newBreakOutRoom(
|
||||||
// TODO create new room on zoom side and use the id as room name
|
final ProctoringServiceSettings proctoringSettings,
|
||||||
return null;
|
final String subject) {
|
||||||
|
|
||||||
|
return createAdHocMeeting(
|
||||||
|
UUID.randomUUID().toString(),
|
||||||
|
subject,
|
||||||
|
proctoringSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<Void> disposeBreakOutRoom(final String roomName) {
|
public Result<Void> disposeBreakOutRoom(
|
||||||
// TODO close the room with specified roomName on zoom side
|
final ProctoringServiceSettings proctoringSettings,
|
||||||
return null;
|
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
|
@Override
|
||||||
|
@ -237,82 +371,298 @@ public class ZoomProctoringService implements ExamProctoringService {
|
||||||
.collect(Collectors.toMap(Tuple::get_1, Tuple::get_2));
|
.collect(Collectors.toMap(Tuple::get_1, Tuple::get_2));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String createPayload(
|
private Result<NewRoom> createAdHocMeeting(
|
||||||
final String clientKey,
|
final String roomName,
|
||||||
final Long expTime) {
|
final String subject,
|
||||||
|
final ProctoringServiceSettings proctoringSettings) {
|
||||||
|
|
||||||
return String.format(
|
return Result.tryCatch(() -> {
|
||||||
ZOOM_ACCESS_TOKEN_PAYLOAD.replaceAll(" ", "").replaceAll("\n", ""),
|
final ClientCredentials credentials = new ClientCredentials(
|
||||||
clientKey,
|
proctoringSettings.appKey,
|
||||||
expTime);
|
this.cryptor.decrypt(proctoringSettings.appSecret));
|
||||||
|
|
||||||
|
// First create a new user/host for the new room
|
||||||
|
final ResponseEntity<String> 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<String> 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) {
|
private Result<Void> deleteAdHocMeeting(
|
||||||
// if (examProctoring.examId == null) {
|
final ProctoringServiceSettings proctoringSettings,
|
||||||
// throw new IllegalStateException("Missing exam identifier from ExamProctoring data");
|
final String meetingId,
|
||||||
// }
|
final String userId) {
|
||||||
//
|
|
||||||
// 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 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 =
|
private static final String API_TEST_ENDPOINT =
|
||||||
"v2/users?status=active&page_size=30&page_number=1&data_type=Json";
|
"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 RestTemplate restTemplate;
|
||||||
private final CircuitBreaker<ResponseEntity<String>> circuitBreaker;
|
private final CircuitBreaker<ResponseEntity<String>> 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()
|
.getClientHttpRequestFactory()
|
||||||
.getOrThrow());
|
.getOrThrow());
|
||||||
|
|
||||||
this.circuitBreaker = ZoomProctoringService.this.asyncService.createCircuitBreaker(
|
this.circuitBreaker = zoomProctoringService.asyncService.createCircuitBreaker(
|
||||||
2,
|
2,
|
||||||
10 * Constants.SECOND_IN_MILLIS,
|
10 * Constants.SECOND_IN_MILLIS,
|
||||||
10 * Constants.SECOND_IN_MILLIS);
|
10 * Constants.SECOND_IN_MILLIS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResponseEntity<String> testServiceConnection(final ProctoringServiceSettings proctoringSettings) {
|
public ResponseEntity<String> testServiceConnection(
|
||||||
final String url = proctoringSettings.serverURL.endsWith(Constants.SLASH.toString())
|
final String zoomServerUrl,
|
||||||
? proctoringSettings.serverURL + API_TEST_ENDPOINT
|
final ClientCredentials credentials) {
|
||||||
: proctoringSettings.serverURL + "/" + API_TEST_ENDPOINT;
|
|
||||||
return exchange(url, HttpMethod.GET, proctoringSettings);
|
final String url = UriComponentsBuilder
|
||||||
|
.fromUriString(zoomServerUrl)
|
||||||
|
.path(API_TEST_ENDPOINT)
|
||||||
|
.toString();
|
||||||
|
return exchange(url, HttpMethod.GET, credentials);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResponseEntity<String> createUser(final ProctoringServiceSettings proctoringSettings)
|
public ResponseEntity<String> createUser(
|
||||||
throws JsonProcessingException {
|
final String zoomServerUrl,
|
||||||
final String url = proctoringSettings.serverURL.endsWith(Constants.SLASH.toString())
|
final ClientCredentials credentials,
|
||||||
? proctoringSettings.serverURL + "v2/users"
|
final String roomName) {
|
||||||
: proctoringSettings.serverURL + "/" + "v2/users";
|
|
||||||
final CreateUserRequest createUserRequest = new CreateUserRequest(
|
try {
|
||||||
"custCreate",
|
final String url = UriComponentsBuilder
|
||||||
new CreateUserRequest.UserInfo(
|
.fromUriString(zoomServerUrl)
|
||||||
"andreas.hefti@let.ethz.ch",
|
.path(API_CREATE_USER_ENDPOINT)
|
||||||
1,
|
.toString();
|
||||||
"Andreas",
|
final String host = new URL(zoomServerUrl).getHost();
|
||||||
"Hefti"));
|
final CreateUserRequest createUserRequest = new CreateUserRequest(
|
||||||
final String body = ZoomProctoringService.this.jsonMapper.writeValueAsString(createUserRequest);
|
API_USER_CUST_CREATE,
|
||||||
final HttpHeaders headers = getHeaders(proctoringSettings);
|
new CreateUserRequest.UserInfo(
|
||||||
headers.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
|
roomName + "@" + host,
|
||||||
return exchange(url, HttpMethod.POST, body, headers, null);
|
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) {
|
public ResponseEntity<String> createMeeting(
|
||||||
final String jwt = createJWT(
|
final String zoomServerUrl,
|
||||||
proctoringSettings.appKey,
|
final ClientCredentials credentials,
|
||||||
proctoringSettings.appSecret,
|
final String userId,
|
||||||
System.currentTimeMillis() + Constants.MINUTE_IN_MILLIS);
|
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<String> 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<String> 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();
|
final HttpHeaders httpHeaders = new HttpHeaders();
|
||||||
httpHeaders.set(HttpHeaders.AUTHORIZATION, "Bearer " + jwt);
|
httpHeaders.set(HttpHeaders.AUTHORIZATION, "Bearer " + jwt);
|
||||||
|
@ -323,38 +673,31 @@ public class ZoomProctoringService implements ExamProctoringService {
|
||||||
private ResponseEntity<String> exchange(
|
private ResponseEntity<String> exchange(
|
||||||
final String url,
|
final String url,
|
||||||
final HttpMethod method,
|
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<String> exchange(
|
private ResponseEntity<String> exchange(
|
||||||
final String url,
|
final String url,
|
||||||
final HttpMethod method,
|
final HttpMethod method,
|
||||||
final Object body,
|
final Object body,
|
||||||
final HttpHeaders httpHeaders,
|
final HttpHeaders httpHeaders) {
|
||||||
final Map<String, ?> uriVariables) {
|
|
||||||
|
|
||||||
final Result<ResponseEntity<String>> protectedRunResult = this.circuitBreaker.protectedRun(() -> {
|
final Result<ResponseEntity<String>> protectedRunResult = this.circuitBreaker.protectedRun(() -> {
|
||||||
final HttpEntity<Object> httpEntity = (body != null)
|
final HttpEntity<Object> httpEntity = (body != null)
|
||||||
? new HttpEntity<>(body, httpHeaders)
|
? new HttpEntity<>(body, httpHeaders)
|
||||||
: new HttpEntity<>(httpHeaders);
|
: new HttpEntity<>(httpHeaders);
|
||||||
|
|
||||||
final ResponseEntity<String> result = (uriVariables != null)
|
final ResponseEntity<String> result = this.restTemplate.exchange(
|
||||||
? this.restTemplate.exchange(
|
url,
|
||||||
url,
|
method,
|
||||||
method,
|
httpEntity,
|
||||||
httpEntity,
|
String.class);
|
||||||
String.class,
|
|
||||||
uriVariables)
|
|
||||||
: this.restTemplate.exchange(
|
|
||||||
url,
|
|
||||||
method,
|
|
||||||
httpEntity,
|
|
||||||
String.class);
|
|
||||||
|
|
||||||
if (result.getStatusCode() != HttpStatus.OK) {
|
if (result.getStatusCode() != HttpStatus.OK) {
|
||||||
log.warn("Zoom API call to {} respond not 200 -> {}", url, result.getStatusCode());
|
log.warn("Zoom API call to {} respond not 200 -> {}", url, result.getStatusCode());
|
||||||
|
throw new RuntimeException("Error Response: " + result.getStatusCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -362,39 +705,29 @@ public class ZoomProctoringService implements ExamProctoringService {
|
||||||
return protectedRunResult.getOrThrow();
|
return protectedRunResult.getOrThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String createJWT(
|
}
|
||||||
final String appKey,
|
|
||||||
final CharSequence appSecret,
|
|
||||||
final Long expTime) {
|
|
||||||
|
|
||||||
try {
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
final StringBuilder builder = new StringBuilder();
|
public static final class AdditionalZoomRoomData {
|
||||||
final Encoder urlEncoder = Base64.getUrlEncoder().withoutPadding();
|
|
||||||
|
|
||||||
final String jwtHeaderPart = urlEncoder.encodeToString(
|
@JsonProperty("user_id")
|
||||||
ZOOM_ACCESS_TOKEN_HEADER.getBytes(StandardCharsets.UTF_8));
|
public final String user_id;
|
||||||
final String jwtPayload = createPayload(appKey, expTime);
|
@JsonProperty("start_url")
|
||||||
final String jwtPayloadPart = urlEncoder.encodeToString(
|
public final String start_url;
|
||||||
jwtPayload.getBytes(StandardCharsets.UTF_8));
|
@JsonProperty("join_url")
|
||||||
final String message = jwtHeaderPart + "." + jwtPayloadPart;
|
public final String join_url;
|
||||||
|
|
||||||
final Mac sha256_HMAC = Mac.getInstance(TOKEN_ENCODE_ALG);
|
@JsonCreator
|
||||||
final SecretKeySpec secret_key = new SecretKeySpec(
|
public AdditionalZoomRoomData(
|
||||||
Utils.toByteArray(appSecret),
|
@JsonProperty("user_id") final String user_id,
|
||||||
TOKEN_ENCODE_ALG);
|
@JsonProperty("start_url") final String start_url,
|
||||||
sha256_HMAC.init(secret_key);
|
@JsonProperty("join_url") final String join_url) {
|
||||||
final String hash = urlEncoder.encodeToString(
|
|
||||||
sha256_HMAC.doFinal(Utils.toByteArray(message)));
|
|
||||||
|
|
||||||
builder.append(message)
|
this.user_id = user_id;
|
||||||
.append(".")
|
this.start_url = start_url;
|
||||||
.append(hash);
|
this.join_url = join_url;
|
||||||
|
|
||||||
return builder.toString();
|
|
||||||
} catch (final Exception e) {
|
|
||||||
throw new RuntimeException("Failed to create JWT for Zoom API access: ", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,14 +88,14 @@ public interface ZoomRoomRequestResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
static class CreateUserResponse {
|
static class UserResponse {
|
||||||
final String id;
|
final String id;
|
||||||
final String email;
|
final String email;
|
||||||
final int type;
|
final int type;
|
||||||
final String first_name;
|
final String first_name;
|
||||||
final String lasr_name;
|
final String lasr_name;
|
||||||
@JsonCreator
|
@JsonCreator
|
||||||
public CreateUserResponse(
|
public UserResponse(
|
||||||
@JsonProperty("id") final String id,
|
@JsonProperty("id") final String id,
|
||||||
@JsonProperty("email") final String email,
|
@JsonProperty("email") final String email,
|
||||||
@JsonProperty("type") final int type,
|
@JsonProperty("type") final int type,
|
||||||
|
@ -111,34 +111,34 @@ public interface ZoomRoomRequestResponse {
|
||||||
|
|
||||||
// https://marketplace.zoom.us/docs/api-reference/zoom-api/meetings/meetingcreate
|
// https://marketplace.zoom.us/docs/api-reference/zoom-api/meetings/meetingcreate
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
static class NewRoomRequest {
|
static class CreateMeetingRequest {
|
||||||
@JsonProperty final String topic;
|
@JsonProperty final String topic;
|
||||||
@JsonProperty final int type = 1; // Instant meeting
|
@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 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;
|
@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.topic = topic;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
this.settings = settings;
|
this.settings = new Settings();
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
static class Settings {
|
static class Settings {
|
||||||
final boolean host_video = false;
|
@JsonProperty final boolean host_video = false;
|
||||||
final boolean participant_video = true;
|
@JsonProperty final boolean participant_video = true;
|
||||||
final boolean join_before_host = true;
|
@JsonProperty final boolean join_before_host = true;
|
||||||
final int jbh_time = 0;
|
@JsonProperty final int jbh_time = 0;
|
||||||
final boolean use_pmi = false;
|
@JsonProperty final boolean use_pmi = false;
|
||||||
final String audio = "voip";
|
@JsonProperty final String audio = "voip";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://marketplace.zoom.us/docs/api-reference/zoom-api/meetings/meetingcreate
|
// https://marketplace.zoom.us/docs/api-reference/zoom-api/meetings/meetingcreate
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
static class NewRoomResponse {
|
static class MeetingResponse {
|
||||||
final Integer id;
|
final Long id;
|
||||||
final String join_url;
|
final String join_url;
|
||||||
final String start_url;
|
final String start_url;
|
||||||
final String start_time;
|
final String start_time;
|
||||||
|
@ -148,8 +148,8 @@ public interface ZoomRoomRequestResponse {
|
||||||
final String host_id;
|
final String host_id;
|
||||||
|
|
||||||
@JsonCreator
|
@JsonCreator
|
||||||
public NewRoomResponse(
|
public MeetingResponse(
|
||||||
@JsonProperty("id") final Integer id,
|
@JsonProperty("id") final Long id,
|
||||||
@JsonProperty("join_url") final String join_url,
|
@JsonProperty("join_url") final String join_url,
|
||||||
@JsonProperty("start_url") final String start_url,
|
@JsonProperty("start_url") final String start_url,
|
||||||
@JsonProperty("start_time") final String start_time,
|
@JsonProperty("start_time") final String start_time,
|
||||||
|
|
|
@ -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`
|
||||||
-- -----------------------------------------------------
|
-- -----------------------------------------------------
|
||||||
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 `join_key` VARCHAR(255) NULL,
|
||||||
ADD COLUMN IF NOT EXISTS `room_data` VARCHAR(10000) NULL;
|
ADD COLUMN IF NOT EXISTS `room_data` VARCHAR(4000) NULL;
|
|
@ -18,7 +18,6 @@ import org.junit.Test;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringRoomConnection;
|
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;
|
import ch.ethz.seb.sebserver.gbl.util.Cryptor;
|
||||||
|
|
||||||
public class ExamJITSIProctoringServiceTest {
|
public class ExamJITSIProctoringServiceTest {
|
||||||
|
@ -64,7 +63,6 @@ public class ExamJITSIProctoringServiceTest {
|
||||||
final JitsiProctoringService examJITSIProctoringService =
|
final JitsiProctoringService examJITSIProctoringService =
|
||||||
new JitsiProctoringService(null, null, cryptorMock, null);
|
new JitsiProctoringService(null, null, cryptorMock, null);
|
||||||
final ProctoringRoomConnection data = examJITSIProctoringService.createProctoringConnection(
|
final ProctoringRoomConnection data = examJITSIProctoringService.createProctoringConnection(
|
||||||
ProctoringServerType.JITSI_MEET,
|
|
||||||
"connectionToken",
|
"connectionToken",
|
||||||
"https://seb-jitsi.example.ch",
|
"https://seb-jitsi.example.ch",
|
||||||
"test-app",
|
"test-app",
|
||||||
|
|
|
@ -95,9 +95,9 @@ CREATE TABLE IF NOT EXISTS `remote_proctoring_room` (
|
||||||
`size` INT NULL,
|
`size` INT NULL,
|
||||||
`subject` VARCHAR(255) NULL,
|
`subject` VARCHAR(255) NULL,
|
||||||
`townhall_room` INT(1) NOT NULL DEFAULT 0,
|
`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,
|
`join_key` VARCHAR(255) NULL,
|
||||||
`room_data` VARCHAR(10000) NULL,
|
`room_data` VARCHAR(4000) NULL,
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
INDEX `proctor_room_exam_id_idx` (`exam_id` ASC),
|
INDEX `proctor_room_exam_id_idx` (`exam_id` ASC),
|
||||||
CONSTRAINT `proctorRoomExamRef`
|
CONSTRAINT `proctorRoomExamRef`
|
||||||
|
|
Loading…
Reference in a new issue