From 9d27dbfbcf0efa9d2757aff9c1073f03943ddcd3 Mon Sep 17 00:00:00 2001 From: anhefti Date: Thu, 5 Aug 2021 09:37:14 +0200 Subject: [PATCH] Implement SDKToken for Zoom proctoring with SEB client for iOS and MacOS --- .../model/exam/ProctoringRoomConnection.java | 10 +++++ .../model/exam/ProctoringServiceSettings.java | 28 +++++++++++++- .../gbl/model/session/ClientInstruction.java | 1 + .../gui/content/ExamProctoringSettings.java | 37 +++++++++++++++++- .../exam/impl/ExamAdminServiceImpl.java | 20 +++++++++- .../proctoring/JitsiProctoringService.java | 1 + .../proctoring/ZoomProctoringService.java | 38 +++++++++++++++++++ .../config/application-dev-gui.properties | 4 +- src/main/resources/messages.properties | 8 +++- .../JitsiWindowScriptResolverTest.java | 2 + .../ZoomWindowScriptResolverTest.java | 2 + .../admin/ExamProctoringRoomServiceTest.java | 2 +- 12 files changed, 143 insertions(+), 10 deletions(-) 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 264858ef..8a8ac806 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 @@ -23,6 +23,7 @@ public class ProctoringRoomConnection { public static final String ATTR_ROOM_NAME = "roomName"; public static final String ATTR_SUBJECT = "subject"; public static final String ATTR_ACCESS_TOKEN = "accessToken"; + public static final String ATTR_SDK_TOKEN = "sdkToken"; public static final String ATTR_CONNECTION_URL = "connectionURL"; public static final String ATTR_USER_NAME = "userName"; public static final String ATTR_ROOM_KEY = "roomKey"; @@ -50,6 +51,9 @@ public class ProctoringRoomConnection { @JsonProperty(ATTR_ACCESS_TOKEN) public final CharSequence accessToken; + @JsonProperty(ATTR_SDK_TOKEN) + public final CharSequence sdkToken; + @JsonProperty(ATTR_ROOM_KEY) public final CharSequence roomKey; @@ -71,6 +75,7 @@ public class ProctoringRoomConnection { @JsonProperty(ATTR_ROOM_NAME) final String roomName, @JsonProperty(ATTR_SUBJECT) final String subject, @JsonProperty(ATTR_ACCESS_TOKEN) final CharSequence accessToken, + @JsonProperty(ATTR_SDK_TOKEN) final CharSequence sdkToken, @JsonProperty(ATTR_ROOM_KEY) final CharSequence roomKey, @JsonProperty(ATTR_API_KEY) final CharSequence apiKey, @JsonProperty(ATTR_MEETING_ID) final String meetingId, @@ -83,6 +88,7 @@ public class ProctoringRoomConnection { this.roomName = roomName; this.subject = subject; this.accessToken = accessToken; + this.sdkToken = sdkToken; this.roomKey = roomKey; this.apiKey = apiKey; this.meetingId = meetingId; @@ -105,6 +111,10 @@ public class ProctoringRoomConnection { return this.accessToken; } + public CharSequence getSdkToken() { + return this.sdkToken; + } + public CharSequence getRoomKey() { return this.roomKey; } diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ProctoringServiceSettings.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ProctoringServiceSettings.java index 4d65b4fc..92c094fe 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ProctoringServiceSettings.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ProctoringServiceSettings.java @@ -43,6 +43,8 @@ public class ProctoringServiceSettings implements Entity { public static final String ATTR_SERVER_URL = "serverURL"; public static final String ATTR_APP_KEY = "appKey"; public static final String ATTR_APP_SECRET = "appSecret"; + public static final String ATTR_SDK_KEY = "sdkKey"; + public static final String ATTR_SDK_SECRET = "sdkSecret"; public static final String ATTR_COLLECTING_ROOM_SIZE = "collectingRoomSize"; public static final String ATTR_ENABLED_FEATURES = "enabledFeatures"; public static final String ATTR_COLLECT_ALL_ROOM_NAME = "collectAllRoomName"; @@ -67,6 +69,12 @@ public class ProctoringServiceSettings implements Entity { @JsonProperty(ATTR_APP_SECRET) public final CharSequence appSecret; + @JsonProperty(ATTR_SDK_KEY) + public final String sdkKey; + + @JsonProperty(ATTR_SDK_SECRET) + public final CharSequence sdkSecret; + @JsonProperty(ATTR_COLLECTING_ROOM_SIZE) public final Integer collectingRoomSize; @@ -86,7 +94,9 @@ public class ProctoringServiceSettings implements Entity { @JsonProperty(ATTR_ENABLED_FEATURES) final EnumSet enabledFeatures, @JsonProperty(ATTR_SERVICE_IN_USE) final Boolean serviceInUse, @JsonProperty(ATTR_APP_KEY) final String appKey, - @JsonProperty(ATTR_APP_SECRET) final CharSequence appSecret) { + @JsonProperty(ATTR_APP_SECRET) final CharSequence appSecret, + @JsonProperty(ATTR_SDK_KEY) final String sdkKey, + @JsonProperty(ATTR_SDK_SECRET) final CharSequence sdkSecret) { this.examId = examId; this.enableProctoring = BooleanUtils.isTrue(enableProctoring); @@ -97,6 +107,8 @@ public class ProctoringServiceSettings implements Entity { this.serviceInUse = serviceInUse; this.appKey = appKey; this.appSecret = appSecret; + this.sdkKey = sdkKey; + this.sdkSecret = sdkSecret; } @@ -147,6 +159,14 @@ public class ProctoringServiceSettings implements Entity { return this.appSecret; } + public String getSdkKey() { + return this.sdkKey; + } + + public CharSequence getSdkSecret() { + return this.sdkSecret; + } + public Boolean getServiceInUse() { return this.serviceInUse; } @@ -193,7 +213,11 @@ public class ProctoringServiceSettings implements Entity { builder.append(", appKey="); builder.append(this.appKey); builder.append(", appSecret="); - builder.append(this.appSecret); + builder.append("--"); + builder.append(", sdkKey="); + builder.append(this.sdkKey); + builder.append(", sdkSecret="); + builder.append("--"); builder.append(", collectingRoomSize="); builder.append(this.collectingRoomSize); builder.append(", enabledFeatures="); 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 b75f02bc..aa111164 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 @@ -54,6 +54,7 @@ public final class ClientInstruction { public static final String ZOOM_USER_NAME = "zoomUserName"; public static final String ZOOM_API_KEY = "zoomAPIKey"; public static final String ZOOM_TOKEN = "zoomToken"; + public static final String ZOOM_SDK_TOKEN = "zoomSDKToken"; public static final String ZOOM_MEETING_KEY = "zoomMeetingKey"; public static final String ZOOM_RECEIVE_AUDIO = "zoomReceiveAudio"; public static final String ZOOM_RECEIVE_VIDEO = "zoomReceiveVideo"; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamProctoringSettings.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamProctoringSettings.java index a8771350..c2a07821 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamProctoringSettings.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamProctoringSettings.java @@ -72,6 +72,11 @@ public class ExamProctoringSettings { new LocTextKey("sebserver.exam.proctoring.form.appkey"); private final static LocTextKey SEB_PROCTORING_FORM_SECRET = new LocTextKey("sebserver.exam.proctoring.form.secret"); + private final static LocTextKey SEB_PROCTORING_FORM_SDKKEY = + new LocTextKey("sebserver.exam.proctoring.form.sdkkey"); + private final static LocTextKey SEB_PROCTORING_FORM_SDKSECRET = + new LocTextKey("sebserver.exam.proctoring.form.sdksecret"); + private final static LocTextKey SEB_PROCTORING_FORM_FEATURES = new LocTextKey("sebserver.exam.proctoring.form.features"); @@ -155,7 +160,9 @@ public class ExamProctoringSettings { featureFlags, false, form.getFieldValue(ProctoringServiceSettings.ATTR_APP_KEY), - form.getFieldValue(ProctoringServiceSettings.ATTR_APP_SECRET)); + form.getFieldValue(ProctoringServiceSettings.ATTR_APP_SECRET), + form.getFieldValue(ProctoringServiceSettings.ATTR_SDK_KEY), + form.getFieldValue(ProctoringServiceSettings.ATTR_SDK_SECRET)); } catch (final Exception e) { log.error("Unexpected error while trying to get settings from form: ", e); @@ -225,6 +232,8 @@ public class ExamProctoringSettings { .copyOf(content) .clearEntityKeys(); + final boolean isZoom = proctoringSettings.serverType == ProctoringServerType.ZOOM; + final FormHandle formHandle = this.pageService.formBuilder( formContext) .withDefaultSpanInput(5) @@ -249,7 +258,8 @@ public class ExamProctoringSettings { ProctoringServiceSettings.ATTR_SERVER_TYPE, SEB_PROCTORING_FORM_TYPE, proctoringSettings.serverType.name(), - resourceService::examProctoringTypeResources)) + resourceService::examProctoringTypeResources) + .withSelectionListener(this::serviceSelection)) .addField(FormBuilder.text( ProctoringServiceSettings.ATTR_SERVER_URL, @@ -269,6 +279,21 @@ public class ExamProctoringSettings { ? String.valueOf(proctoringSettings.appSecret) : null)) + .addField(FormBuilder.text( + ProctoringServiceSettings.ATTR_SDK_KEY, + SEB_PROCTORING_FORM_SDKKEY, + proctoringSettings.sdkKey) + .visibleIf(isZoom)) + .withEmptyCellSeparation(false) + + .addField(FormBuilder.password( + ProctoringServiceSettings.ATTR_SDK_SECRET, + SEB_PROCTORING_FORM_SDKSECRET, + (proctoringSettings.sdkSecret != null) + ? String.valueOf(proctoringSettings.sdkSecret) + : null) + .visibleIf(isZoom)) + .withDefaultSpanInput(1) .addField(FormBuilder.text( ProctoringServiceSettings.ATTR_COLLECTING_ROOM_SIZE, @@ -294,6 +319,14 @@ public class ExamProctoringSettings { return () -> formHandle; } + + private void serviceSelection(final Form form) { + final boolean isZoom = ProctoringServerType.ZOOM.name() + .equals(form.getFieldValue(ProctoringServiceSettings.ATTR_SERVER_TYPE)); + + form.setFieldVisible(isZoom, ProctoringServiceSettings.ATTR_SDK_KEY); + form.setFieldVisible(isZoom, ProctoringServiceSettings.ATTR_SDK_SECRET); + } } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamAdminServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamAdminServiceImpl.java index bacadf7b..9bef43bd 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamAdminServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamAdminServiceImpl.java @@ -213,7 +213,9 @@ public class ExamAdminServiceImpl implements ExamAdminService { getEnabledFeatures(mapping), this.remoteProctoringRoomDAO.isServiceInUse(examId).getOr(true), getString(mapping, ProctoringServiceSettings.ATTR_APP_KEY), - getString(mapping, ProctoringServiceSettings.ATTR_APP_SECRET)); + getString(mapping, ProctoringServiceSettings.ATTR_APP_SECRET), + getString(mapping, ProctoringServiceSettings.ATTR_SDK_KEY), + getString(mapping, ProctoringServiceSettings.ATTR_SDK_SECRET)); }); } @@ -263,6 +265,22 @@ public class ExamAdminServiceImpl implements ExamAdminService { .getOrThrow() .toString()); + if (StringUtils.isNotBlank(proctoringServiceSettings.appKey)) { + this.additionalAttributesDAO.saveAdditionalAttribute( + EntityType.EXAM, + examId, + ProctoringServiceSettings.ATTR_SDK_KEY, + proctoringServiceSettings.sdkKey); + + this.additionalAttributesDAO.saveAdditionalAttribute( + EntityType.EXAM, + examId, + ProctoringServiceSettings.ATTR_SDK_SECRET, + this.cryptor.encrypt(proctoringServiceSettings.sdkSecret) + .getOrThrow() + .toString()); + } + this.additionalAttributesDAO.saveAdditionalAttribute( EntityType.EXAM, examId, 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 3adb9627..df4b3fc8 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 @@ -378,6 +378,7 @@ public class JitsiProctoringService implements ExamProctoringService { null, null, null, + null, 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 20e38042..836bcc91 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 @@ -214,6 +214,9 @@ public class ZoomProctoringService implements ExamProctoringService { attributes.put( ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.ZOOM_TOKEN, String.valueOf(proctoringConnection.accessToken)); + attributes.put( + ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.ZOOM_SDK_TOKEN, + String.valueOf(proctoringConnection.sdkToken)); if (StringUtils.isNotBlank(proctoringConnection.apiKey)) { attributes.put( ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.ZOOM_API_KEY, @@ -262,6 +265,19 @@ public class ZoomProctoringService implements ExamProctoringService { String.valueOf(additionalZoomRoomData.meeting_id), true); + String sdkJWT = null; + if (StringUtils.isNotBlank(proctoringSettings.sdkKey)) { + + final ClientCredentials sdkCredentials = new ClientCredentials( + proctoringSettings.sdkKey, + proctoringSettings.sdkSecret, + remoteProctoringRoom.joinKey); + + sdkJWT = this.createJWTForSDKAccess( + sdkCredentials, + String.valueOf(additionalZoomRoomData.meeting_id)); + } + return new ProctoringRoomConnection( ProctoringServerType.ZOOM, null, @@ -270,6 +286,7 @@ public class ZoomProctoringService implements ExamProctoringService { roomName, subject, jwt, + sdkJWT, credentials.accessToken, credentials.clientId, String.valueOf(additionalZoomRoomData.meeting_id), @@ -308,6 +325,19 @@ public class ZoomProctoringService implements ExamProctoringService { .getConnectionData(connectionToken) .getOrThrow(); + String sdkJWT = null; + if (StringUtils.isNotBlank(proctoringSettings.sdkKey)) { + + final ClientCredentials sdkCredentials = new ClientCredentials( + proctoringSettings.sdkKey, + proctoringSettings.sdkSecret, + remoteProctoringRoom.joinKey); + + sdkJWT = this.createJWTForSDKAccess( + sdkCredentials, + String.valueOf(additionalZoomRoomData.meeting_id)); + } + return new ProctoringRoomConnection( ProctoringServerType.ZOOM, connectionToken, @@ -316,6 +346,7 @@ public class ZoomProctoringService implements ExamProctoringService { roomName, subject, jwt, + sdkJWT, credentials.accessToken, credentials.clientId, String.valueOf(additionalZoomRoomData.meeting_id), @@ -613,6 +644,13 @@ public class ZoomProctoringService implements ExamProctoringService { } } + private String createJWTForSDKAccess( + final ClientCredentials sdkCredentials, + final String meetingId) { + + return createJWTForMeetingAccess(sdkCredentials, meetingId, false); + } + private String createJWTForMeetingAccess( final ClientCredentials credentials, final String meetingId, diff --git a/src/main/resources/config/application-dev-gui.properties b/src/main/resources/config/application-dev-gui.properties index a22c98bc..8069b264 100644 --- a/src/main/resources/config/application-dev-gui.properties +++ b/src/main/resources/config/application-dev-gui.properties @@ -1,11 +1,11 @@ server.address=localhost -server.port=8090 +server.port=8080 sebserver.gui.http.external.scheme=http sebserver.gui.entrypoint=/gui sebserver.gui.webservice.protocol=http sebserver.gui.webservice.address=localhost -sebserver.gui.webservice.port=8090 +sebserver.gui.webservice.port=8080 sebserver.gui.webservice.apipath=/admin-api/v1 # defines the polling interval that is used to poll the webservice for client connection data on a monitored exam page sebserver.gui.webservice.poll-interval=1000 diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 99e790a8..a37f09cd 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -658,10 +658,14 @@ sebserver.exam.proctoring.form.url=Server URL sebserver.exam.proctoring.form.url.tooltip=The proctoring server URL sebserver.exam.proctoring.form.collectingRoomSize=Collecting Room Size sebserver.exam.proctoring.form.collectingRoomSize.tooltip=The size of proctor rooms to collect connecting SEB clients into. -sebserver.exam.proctoring.form.appkey=Application Key +sebserver.exam.proctoring.form.appkey=App Key sebserver.exam.proctoring.form.appkey.tooltip=The application key of the proctoring service server -sebserver.exam.proctoring.form.secret=Secret +sebserver.exam.proctoring.form.secret=App Secret sebserver.exam.proctoring.form.secret.tooltip=The secret used to access the proctoring service +sebserver.exam.proctoring.form.sdkkey=SDK Key (Zoom) +sebserver.exam.proctoring.form.sdkkey.tooltip=The SDK key and secret are used for live proctoring with SEB clients for iOS and/or MacOS +sebserver.exam.proctoring.form.sdksecret=SDK Secret (Zoom) +sebserver.exam.proctoring.form.sdksecret.tooltip=The SDK key and secret are used for live proctoring with SEB clients for iOS and/or MacOS sebserver.exam.proctoring.form.features=Enabled Features sebserver.exam.proctoring.form.features.TOWN_HALL=Town-Hall Room sebserver.exam.proctoring.form.features.ONE_TO_ONE=One to One Room diff --git a/src/test/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/JitsiWindowScriptResolverTest.java b/src/test/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/JitsiWindowScriptResolverTest.java index bda05e45..749c24a4 100644 --- a/src/test/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/JitsiWindowScriptResolverTest.java +++ b/src/test/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/JitsiWindowScriptResolverTest.java @@ -37,6 +37,7 @@ public class JitsiWindowScriptResolverTest { "ROOM", "SUBJECT", "ACCESS_TOKEN", + null, "API_KEY", "ROOM_KEY", "MEETING_ID", @@ -53,6 +54,7 @@ public class JitsiWindowScriptResolverTest { "ROOM", "SUBJECT", "ACCESS_TOKEN", + null, "API_KEY", "ROOM_KEY", "MEETING_ID", diff --git a/src/test/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/ZoomWindowScriptResolverTest.java b/src/test/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/ZoomWindowScriptResolverTest.java index a2a20887..5c503575 100644 --- a/src/test/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/ZoomWindowScriptResolverTest.java +++ b/src/test/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/ZoomWindowScriptResolverTest.java @@ -37,6 +37,7 @@ public class ZoomWindowScriptResolverTest { "ROOM", "SUBJECT", "ACCESS_TOKEN", + "SDK_TOKEN", "API_KEY", "ROOM_KEY", "MEETING_ID", @@ -53,6 +54,7 @@ public class ZoomWindowScriptResolverTest { "ROOM", "SUBJECT", "ACCESS_TOKEN", + "SDK_TOKEN", "API_KEY", "ROOM_KEY", "MEETING_ID", diff --git a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/ExamProctoringRoomServiceTest.java b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/ExamProctoringRoomServiceTest.java index ca0e9cee..282352c5 100644 --- a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/ExamProctoringRoomServiceTest.java +++ b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/ExamProctoringRoomServiceTest.java @@ -63,7 +63,7 @@ public class ExamProctoringRoomServiceTest extends AdministrationAPIIntegrationT 2L, new ProctoringServiceSettings( 2L, true, ProctoringServerType.JITSI_MEET, "http://jitsi.ch", 1, null, false, - "app-key", "app.secret")); + "app-key", "app.secret", "sdk-key", "sdk.secret")); assertTrue(this.examAdminService.isProctoringEnabled(2L).get()); }