SEBSERV-139 integration of token generation
This commit is contained in:
parent
548d4d132f
commit
7335547341
8 changed files with 209 additions and 57 deletions
|
@ -0,0 +1,79 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.ethz.seb.sebserver.gbl.model.exam;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class SEBClientProctoringConnectionData {
|
||||||
|
|
||||||
|
public static final String ATTR_SERVER_URL = "serverURL";
|
||||||
|
public static final String ATTR_ROOM_NAME = "roomName";
|
||||||
|
public static final String ATTR_ACCESS_TOKEN = "accessToken";
|
||||||
|
public static final String ATTR_CONNECTION_URL = "connectionURL";
|
||||||
|
|
||||||
|
@JsonProperty(ATTR_SERVER_URL)
|
||||||
|
public final String serverURL;
|
||||||
|
|
||||||
|
@JsonProperty(ATTR_ROOM_NAME)
|
||||||
|
public final String roomName;
|
||||||
|
|
||||||
|
@JsonProperty(ATTR_ACCESS_TOKEN)
|
||||||
|
public final String accessToken;
|
||||||
|
|
||||||
|
@JsonProperty(ATTR_CONNECTION_URL)
|
||||||
|
public final String connectionURL;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public SEBClientProctoringConnectionData(
|
||||||
|
@JsonProperty(ATTR_SERVER_URL) final String serverURL,
|
||||||
|
@JsonProperty(ATTR_ROOM_NAME) final String roomName,
|
||||||
|
@JsonProperty(ATTR_ACCESS_TOKEN) final String accessToken,
|
||||||
|
@JsonProperty(ATTR_CONNECTION_URL) final String connectionURL) {
|
||||||
|
|
||||||
|
this.serverURL = serverURL;
|
||||||
|
this.roomName = roomName;
|
||||||
|
this.accessToken = accessToken;
|
||||||
|
this.connectionURL = connectionURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAccessToken() {
|
||||||
|
return this.accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getServerURL() {
|
||||||
|
return this.serverURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getConnectionURL() {
|
||||||
|
return this.connectionURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRoomName() {
|
||||||
|
return this.roomName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
final StringBuilder builder = new StringBuilder();
|
||||||
|
builder.append("SEBClientProctoringConnectionData [serverURL=");
|
||||||
|
builder.append(this.serverURL);
|
||||||
|
builder.append(", roomName=");
|
||||||
|
builder.append(this.roomName);
|
||||||
|
builder.append(", accessToken=");
|
||||||
|
builder.append(this.accessToken);
|
||||||
|
builder.append(", connectionURL=");
|
||||||
|
builder.append(this.connectionURL);
|
||||||
|
builder.append("]");
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -25,6 +25,7 @@ import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||||
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.Indicator;
|
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings;
|
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.exam.SEBClientProctoringConnectionData;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
|
||||||
|
@ -285,16 +286,17 @@ public class MonitoringClientConnection implements TemplateComposer {
|
||||||
// urlLauncher.openURL(
|
// urlLauncher.openURL(
|
||||||
// "https://seb-jitsi.ethz.ch/TestRoomABC?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb250ZXh0Ijp7InVzZXIiOnsiYXZhdGFyIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9qb2huLWRvZSIsIm5hbWUiOiJEaXNwbGF5IE5hbWUiLCJlbWFpbCI6Im5hbWVAZXhhbXBsZS5jb20ifX0sImF1ZCI6InNlYi1qaXRzaSIsImlzcyI6InNlYi1qaXRzaSIsInN1YiI6Im1lZXQuaml0c2kiLCJyb29tIjoiKiJ9.SD9Zs78mMFqxS1tpalPTykYYaubIYsj_406WAOhcqxQ");
|
// "https://seb-jitsi.ethz.ch/TestRoomABC?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb250ZXh0Ijp7InVzZXIiOnsiYXZhdGFyIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9qb2huLWRvZSIsIm5hbWUiOiJEaXNwbGF5IE5hbWUiLCJlbWFpbCI6Im5hbWVAZXhhbXBsZS5jb20ifX0sImF1ZCI6InNlYi1qaXRzaSIsImlzcyI6InNlYi1qaXRzaSIsInN1YiI6Im1lZXQuaml0c2kiLCJyb29tIjoiKiJ9.SD9Zs78mMFqxS1tpalPTykYYaubIYsj_406WAOhcqxQ");
|
||||||
|
|
||||||
final String proctorURL = this.pageService.getRestService().getBuilder(GetProctorURLForClient.class)
|
final SEBClientProctoringConnectionData proctoringConnectionData =
|
||||||
.withURIVariable(API.PARAM_MODEL_ID, action.getEntityKey().modelId)
|
this.pageService.getRestService().getBuilder(GetProctorURLForClient.class)
|
||||||
.withURIVariable(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionToken)
|
.withURIVariable(API.PARAM_MODEL_ID, action.getEntityKey().modelId)
|
||||||
.call()
|
.withURIVariable(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionToken)
|
||||||
.getOrThrow();
|
.call()
|
||||||
|
.getOrThrow();
|
||||||
|
|
||||||
final JavaScriptExecutor javaScriptExecutor = RWT.getClient().getService(JavaScriptExecutor.class);
|
final JavaScriptExecutor javaScriptExecutor = RWT.getClient().getService(JavaScriptExecutor.class);
|
||||||
javaScriptExecutor.execute(
|
javaScriptExecutor.execute(
|
||||||
"window.open("
|
"window.open("
|
||||||
+ "'https://seb-jitsi.ethz.ch/TestRoomABC?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb250ZXh0Ijp7InVzZXIiOnsiYXZhdGFyIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9qb2huLWRvZSIsIm5hbWUiOiJEaXNwbGF5IE5hbWUiLCJlbWFpbCI6Im5hbWVAZXhhbXBsZS5jb20ifX0sImF1ZCI6InNlYi1qaXRzaSIsImlzcyI6InNlYi1qaXRzaSIsInN1YiI6Im1lZXQuaml0c2kiLCJyb29tIjoiKiJ9.SD9Zs78mMFqxS1tpalPTykYYaubIYsj_406WAOhcqxQ',"
|
+ "'" + proctoringConnectionData.connectionURL + "',"
|
||||||
+ "'proctoring',"
|
+ "'proctoring',"
|
||||||
+ "'height=400,width=800,location=no,scrollbars=yes,status=no,menubar=yes,toolbar=yes,titlebar=yes');");
|
+ "'height=400,width=800,location=no,scrollbars=yes,status=no,menubar=yes,toolbar=yes,titlebar=yes');");
|
||||||
|
|
||||||
|
|
|
@ -17,22 +17,23 @@ import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.exam.SEBClientProctoringConnectionData;
|
||||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
@Component
|
@Component
|
||||||
@GuiProfile
|
@GuiProfile
|
||||||
public class GetProctorURLForClient extends RestCall<String> {
|
public class GetProctorURLForClient extends RestCall<SEBClientProctoringConnectionData> {
|
||||||
|
|
||||||
public GetProctorURLForClient() {
|
public GetProctorURLForClient() {
|
||||||
super(new TypeKey<>(
|
super(new TypeKey<>(
|
||||||
CallType.GET_SINGLE,
|
CallType.GET_SINGLE,
|
||||||
EntityType.EXAM_PROCTOR_DATA,
|
EntityType.EXAM_PROCTOR_DATA,
|
||||||
new TypeReference<String>() {
|
new TypeReference<SEBClientProctoringConnectionData>() {
|
||||||
}),
|
}),
|
||||||
HttpMethod.GET,
|
HttpMethod.GET,
|
||||||
MediaType.APPLICATION_JSON_UTF8,
|
MediaType.APPLICATION_FORM_URLENCODED,
|
||||||
API.EXAM_ADMINISTRATION_ENDPOINT
|
API.EXAM_ADMINISTRATION_ENDPOINT
|
||||||
+ API.MODEL_ID_VAR_PATH_SEGMENT
|
+ API.MODEL_ID_VAR_PATH_SEGMENT
|
||||||
+ API.EXAM_ADMINISTRATION_PROCTOR_PATH_SEGMENT
|
+ API.EXAM_ADMINISTRATION_PROCTOR_PATH_SEGMENT
|
||||||
|
|
|
@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.exam;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings;
|
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings.ServerType;
|
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings.ServerType;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.exam.SEBClientProctoringConnectionData;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
|
|
||||||
|
@ -19,12 +20,12 @@ public interface ExamProctoringService {
|
||||||
|
|
||||||
Result<Boolean> testExamProctoring(final ProctoringSettings examProctoring);
|
Result<Boolean> testExamProctoring(final ProctoringSettings examProctoring);
|
||||||
|
|
||||||
public Result<String> createProctoringURL(
|
public Result<SEBClientProctoringConnectionData> createProctoringConnectionData(
|
||||||
final ProctoringSettings examProctoring,
|
final ProctoringSettings examProctoring,
|
||||||
final String connectionToken,
|
final String connectionToken,
|
||||||
final boolean server);
|
final boolean server);
|
||||||
|
|
||||||
Result<String> createProctoringURL(
|
Result<SEBClientProctoringConnectionData> createProctoringConnectionData(
|
||||||
final ProctoringSettings examProctoring,
|
final ProctoringSettings examProctoring,
|
||||||
ClientConnection clientConnection,
|
ClientConnection clientConnection,
|
||||||
boolean server);
|
boolean server);
|
||||||
|
|
|
@ -9,6 +9,8 @@
|
||||||
package ch.ethz.seb.sebserver.webservice.servicelayer.exam.impl;
|
package ch.ethz.seb.sebserver.webservice.servicelayer.exam.impl;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.Base64.Encoder;
|
import java.util.Base64.Encoder;
|
||||||
|
|
||||||
|
@ -23,8 +25,10 @@ import ch.ethz.seb.sebserver.gbl.Constants;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings;
|
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings.ServerType;
|
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings.ServerType;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.exam.SEBClientProctoringConnectionData;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
||||||
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.Result;
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamProctoringService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamProctoringService;
|
||||||
|
@ -42,9 +46,14 @@ public class ExamJITSIProctoringService implements ExamProctoringService {
|
||||||
"{\"context\":{\"user\":{\"name\":\"%s\"}},\"iss\":\"%s\",\"aud\":\"%s\",\"sub\":\"%s\",\"room\":\"%s\"%s}";
|
"{\"context\":{\"user\":{\"name\":\"%s\"}},\"iss\":\"%s\",\"aud\":\"%s\",\"sub\":\"%s\",\"room\":\"%s\"%s}";
|
||||||
|
|
||||||
private final ExamSessionService examSessionService;
|
private final ExamSessionService examSessionService;
|
||||||
|
private final Cryptor cryptor;
|
||||||
|
|
||||||
|
protected ExamJITSIProctoringService(
|
||||||
|
final ExamSessionService examSessionService,
|
||||||
|
final Cryptor cryptor) {
|
||||||
|
|
||||||
protected ExamJITSIProctoringService(final ExamSessionService examSessionService) {
|
|
||||||
this.examSessionService = examSessionService;
|
this.examSessionService = examSessionService;
|
||||||
|
this.cryptor = cryptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -59,13 +68,14 @@ public class ExamJITSIProctoringService implements ExamProctoringService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<String> createProctoringURL(
|
public Result<SEBClientProctoringConnectionData> createProctoringConnectionData(
|
||||||
final ProctoringSettings examProctoring,
|
final ProctoringSettings examProctoring,
|
||||||
final String connectionToken,
|
final String connectionToken,
|
||||||
final boolean server) {
|
final boolean server) {
|
||||||
|
|
||||||
return Result.tryCatch(() -> {
|
return Result.tryCatch(() -> {
|
||||||
return createProctoringURL(examProctoring,
|
return createProctoringConnectionData(
|
||||||
|
examProctoring,
|
||||||
this.examSessionService
|
this.examSessionService
|
||||||
.getConnectionData(connectionToken)
|
.getConnectionData(connectionToken)
|
||||||
.getOrThrow().clientConnection,
|
.getOrThrow().clientConnection,
|
||||||
|
@ -75,7 +85,7 @@ public class ExamJITSIProctoringService implements ExamProctoringService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<String> createProctoringURL(
|
public Result<SEBClientProctoringConnectionData> createProctoringConnectionData(
|
||||||
final ProctoringSettings examProctoring,
|
final ProctoringSettings examProctoring,
|
||||||
final ClientConnection clientConnection,
|
final ClientConnection clientConnection,
|
||||||
final boolean server) {
|
final boolean server) {
|
||||||
|
@ -90,23 +100,29 @@ public class ExamJITSIProctoringService implements ExamProctoringService {
|
||||||
if (this.examSessionService.isExamRunning(examProctoring.examId)) {
|
if (this.examSessionService.isExamRunning(examProctoring.examId)) {
|
||||||
final Exam exam = this.examSessionService.getRunningExam(examProctoring.examId)
|
final Exam exam = this.examSessionService.getRunningExam(examProctoring.examId)
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
expTime = exam.endTime.getMillis();
|
if (exam.endTime != null) {
|
||||||
|
expTime = exam.endTime.getMillis();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return createProctoringURL(
|
final Encoder urlEncoder = Base64.getUrlEncoder().withoutPadding();
|
||||||
|
final String roomName = urlEncoder.encodeToString(
|
||||||
|
Utils.toByteArray(clientConnection.connectionToken));
|
||||||
|
|
||||||
|
return createProctoringConnectionData(
|
||||||
examProctoring.serverURL,
|
examProctoring.serverURL,
|
||||||
examProctoring.appKey,
|
examProctoring.appKey,
|
||||||
examProctoring.getAppSecret(),
|
examProctoring.getAppSecret(),
|
||||||
clientConnection.userSessionId,
|
clientConnection.userSessionId,
|
||||||
(server) ? "seb-server" : "seb-client",
|
(server) ? "seb-server" : "seb-client",
|
||||||
clientConnection.connectionToken,
|
roomName,
|
||||||
expTime)
|
expTime)
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result<String> createProctoringURL(
|
public Result<SEBClientProctoringConnectionData> createProctoringConnectionData(
|
||||||
final String url,
|
final String url,
|
||||||
final String appKey,
|
final String appKey,
|
||||||
final CharSequence appSecret,
|
final CharSequence appSecret,
|
||||||
|
@ -117,46 +133,84 @@ public class ExamJITSIProctoringService implements ExamProctoringService {
|
||||||
|
|
||||||
return Result.tryCatch(() -> {
|
return Result.tryCatch(() -> {
|
||||||
|
|
||||||
final Encoder urlEncoder = Base64.getUrlEncoder().withoutPadding();
|
final String roomUrl = createServerConnectionURL(url, roomName);
|
||||||
|
|
||||||
final String host = UriComponentsBuilder.fromHttpUrl(url)
|
final String host = UriComponentsBuilder.fromHttpUrl(url)
|
||||||
.build()
|
.build()
|
||||||
.getHost();
|
.getHost();
|
||||||
|
|
||||||
final StringBuilder builder = new StringBuilder();
|
final CharSequence decryptedSecret = this.cryptor.decrypt(appSecret);
|
||||||
builder.append(url)
|
final String token = createAccessToken(
|
||||||
.append("/")
|
|
||||||
.append(roomName)
|
|
||||||
.append("?jwt=");
|
|
||||||
|
|
||||||
final String jwtHeaderPart = urlEncoder
|
|
||||||
.encodeToString(JITSI_ACCESS_TOKEN_HEADER.getBytes(StandardCharsets.UTF_8));
|
|
||||||
final String jwtPayload = String.format(
|
|
||||||
JITSI_ACCESS_TOKEN_PAYLOAD.replaceAll(" ", "").replaceAll("\n", ""),
|
|
||||||
clientName,
|
|
||||||
appKey,
|
appKey,
|
||||||
|
decryptedSecret,
|
||||||
|
clientName,
|
||||||
clientKey,
|
clientKey,
|
||||||
host,
|
|
||||||
roomName,
|
roomName,
|
||||||
(expTime != null)
|
expTime,
|
||||||
? String.format(",\"exp\":%s", String.valueOf(expTime))
|
host);
|
||||||
: "");
|
|
||||||
final String jwtPayloadPart = urlEncoder
|
|
||||||
.encodeToString(jwtPayload.getBytes(StandardCharsets.UTF_8));
|
|
||||||
final String message = jwtHeaderPart + "." + jwtPayloadPart;
|
|
||||||
|
|
||||||
final Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
|
final StringBuilder builder = new StringBuilder();
|
||||||
final SecretKeySpec secret_key =
|
final String connectionURL = builder.append(roomUrl)
|
||||||
new SecretKeySpec(Utils.toByteArray(appSecret), "HmacSHA256");
|
.append("?jwt=")
|
||||||
sha256_HMAC.init(secret_key);
|
.append(token).toString();
|
||||||
final String hash = urlEncoder.encodeToString(sha256_HMAC.doFinal(Utils.toByteArray(message)));
|
|
||||||
|
|
||||||
builder.append(message)
|
return new SEBClientProctoringConnectionData(
|
||||||
.append(".")
|
roomUrl,
|
||||||
.append(hash);
|
roomName,
|
||||||
|
token,
|
||||||
return builder.toString();
|
connectionURL);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String createServerConnectionURL(
|
||||||
|
final String url,
|
||||||
|
final String roomName) {
|
||||||
|
|
||||||
|
final StringBuilder builder = new StringBuilder();
|
||||||
|
return builder.append(url)
|
||||||
|
.append("/")
|
||||||
|
.append(roomName)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String createAccessToken(
|
||||||
|
final String appKey,
|
||||||
|
final CharSequence appSecret,
|
||||||
|
final String clientName,
|
||||||
|
final String clientKey,
|
||||||
|
final String roomName,
|
||||||
|
final Long expTime,
|
||||||
|
final String host) throws NoSuchAlgorithmException, InvalidKeyException {
|
||||||
|
|
||||||
|
final StringBuilder builder = new StringBuilder();
|
||||||
|
final Encoder urlEncoder = Base64.getUrlEncoder().withoutPadding();
|
||||||
|
|
||||||
|
final String jwtHeaderPart = urlEncoder
|
||||||
|
.encodeToString(JITSI_ACCESS_TOKEN_HEADER.getBytes(StandardCharsets.UTF_8));
|
||||||
|
final String jwtPayload = String.format(
|
||||||
|
JITSI_ACCESS_TOKEN_PAYLOAD.replaceAll(" ", "").replaceAll("\n", ""),
|
||||||
|
clientName,
|
||||||
|
appKey,
|
||||||
|
clientKey,
|
||||||
|
host,
|
||||||
|
roomName,
|
||||||
|
(expTime != null)
|
||||||
|
? String.format(",\"exp\":%s", String.valueOf(expTime))
|
||||||
|
: "");
|
||||||
|
final String jwtPayloadPart = urlEncoder
|
||||||
|
.encodeToString(jwtPayload.getBytes(StandardCharsets.UTF_8));
|
||||||
|
final String message = jwtHeaderPart + "." + jwtPayloadPart;
|
||||||
|
|
||||||
|
final Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
|
||||||
|
final SecretKeySpec secret_key =
|
||||||
|
new SecretKeySpec(Utils.toByteArray(appSecret), "HmacSHA256");
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,7 @@ import ch.ethz.seb.sebserver.gbl.model.exam.Chapters;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings;
|
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.exam.SEBClientProctoringConnectionData;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.SEBRestriction;
|
import ch.ethz.seb.sebserver.gbl.model.exam.SEBRestriction;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.Features;
|
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.Features;
|
||||||
|
@ -429,8 +430,8 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
||||||
+ API.EXAM_ADMINISTRATION_PROCTOR_PATH_SEGMENT
|
+ API.EXAM_ADMINISTRATION_PROCTOR_PATH_SEGMENT
|
||||||
+ API.EXAM_MONITORING_SEB_CONNECTION_TOKEN_PATH_SEGMENT,
|
+ API.EXAM_MONITORING_SEB_CONNECTION_TOKEN_PATH_SEGMENT,
|
||||||
method = RequestMethod.GET,
|
method = RequestMethod.GET,
|
||||||
produces = MediaType.TEXT_PLAIN_VALUE)
|
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||||
public String getExamProctoringURL(
|
public SEBClientProctoringConnectionData getExamProctoringURL(
|
||||||
@RequestParam(
|
@RequestParam(
|
||||||
name = API.PARAM_INSTITUTION_ID,
|
name = API.PARAM_INSTITUTION_ID,
|
||||||
required = true,
|
required = true,
|
||||||
|
@ -444,8 +445,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
||||||
.flatMap(this.examAdminService::getExamProctoring)
|
.flatMap(this.examAdminService::getExamProctoring)
|
||||||
.flatMap(proc -> this.examAdminService
|
.flatMap(proc -> this.examAdminService
|
||||||
.getExamProctoringService(proc.serverType)
|
.getExamProctoringService(proc.serverType)
|
||||||
.map(s -> s.createProctoringURL(proc, connectionToken, true))
|
.flatMap(s -> s.createProctoringConnectionData(proc, connectionToken, true)))
|
||||||
.getOrThrow())
|
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,5 +5,5 @@ server.port=8080
|
||||||
server.servlet.context-path=/
|
server.servlet.context-path=/
|
||||||
server.tomcat.uri-encoding=UTF-8
|
server.tomcat.uri-encoding=UTF-8
|
||||||
|
|
||||||
logging.level.ch=DEBUG
|
logging.level.ch=ERROR
|
||||||
|
|
||||||
|
|
|
@ -9,15 +9,22 @@
|
||||||
package ch.ethz.seb.sebserver.webservice.servicelayer.exam.impl;
|
package ch.ethz.seb.sebserver.webservice.servicelayer.exam.impl;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.exam.SEBClientProctoringConnectionData;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.util.Cryptor;
|
||||||
|
|
||||||
public class ExamJITSIProctoringServiceTest {
|
public class ExamJITSIProctoringServiceTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreateProctoringURL() {
|
public void testCreateProctoringURL() {
|
||||||
final ExamJITSIProctoringService examJITSIProctoringService = new ExamJITSIProctoringService(null);
|
Cryptor cryptorMock = Mockito.mock(Cryptor.class);
|
||||||
final String jwt = examJITSIProctoringService.createProctoringURL(
|
Mockito.when(cryptorMock.decrypt(Mockito.any())).thenReturn("fbvgeghergrgrthrehreg123");
|
||||||
|
final ExamJITSIProctoringService examJITSIProctoringService = new ExamJITSIProctoringService(null, cryptorMock);
|
||||||
|
final SEBClientProctoringConnectionData data = examJITSIProctoringService.createProctoringConnectionData(
|
||||||
"https://seb-jitsi.example.ch",
|
"https://seb-jitsi.example.ch",
|
||||||
"test-app",
|
"test-app",
|
||||||
"fbvgeghergrgrthrehreg123",
|
"fbvgeghergrgrthrehreg123",
|
||||||
|
@ -27,9 +34,17 @@ public class ExamJITSIProctoringServiceTest {
|
||||||
1609459200L)
|
1609459200L)
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
|
||||||
|
assertNotNull(data);
|
||||||
|
assertEquals(
|
||||||
|
"https://seb-jitsi.example.ch/SomeRoom",
|
||||||
|
data.serverURL);
|
||||||
|
assertEquals(
|
||||||
|
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb250ZXh0Ijp7InVzZXIiOnsibmFtZSI6IlRlc3QgTmFtZSJ9fSwiaXNzIjoidGVzdC1hcHAiLCJhdWQiOiJ0ZXN0LWNsaWVudCIsInN1YiI6InNlYi1qaXRzaS5leGFtcGxlLmNoIiwicm9vbSI6IlNvbWVSb29tIiwiZXhwIjoxNjA5NDU5MjAwfQ.4ovqUkG6jrLvkDEZNdhbtFI_DFLDFsM2eBJHhcYq7a4",
|
||||||
|
data.accessToken);
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"https://seb-jitsi.example.ch/SomeRoom?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb250ZXh0Ijp7InVzZXIiOnsibmFtZSI6IlRlc3QgTmFtZSJ9fSwiaXNzIjoidGVzdC1hcHAiLCJhdWQiOiJ0ZXN0LWNsaWVudCIsInN1YiI6InNlYi1qaXRzaS5leGFtcGxlLmNoIiwicm9vbSI6IlNvbWVSb29tIiwiZXhwIjoxNjA5NDU5MjAwfQ.4ovqUkG6jrLvkDEZNdhbtFI_DFLDFsM2eBJHhcYq7a4",
|
"https://seb-jitsi.example.ch/SomeRoom?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb250ZXh0Ijp7InVzZXIiOnsibmFtZSI6IlRlc3QgTmFtZSJ9fSwiaXNzIjoidGVzdC1hcHAiLCJhdWQiOiJ0ZXN0LWNsaWVudCIsInN1YiI6InNlYi1qaXRzaS5leGFtcGxlLmNoIiwicm9vbSI6IlNvbWVSb29tIiwiZXhwIjoxNjA5NDU5MjAwfQ.4ovqUkG6jrLvkDEZNdhbtFI_DFLDFsM2eBJHhcYq7a4",
|
||||||
jwt);
|
data.connectionURL);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue