From e88d2d9edd039a9842efd5b687a08dad726d704a Mon Sep 17 00:00:00 2001 From: anhefti Date: Thu, 17 Jun 2021 14:07:17 +0200 Subject: [PATCH] Jitsi integration: new access token creation for JaaS integration --- .../proctoring/JitsiProctoringService.java | 133 +++++++++++++++--- .../ExamJITSIProctoringServiceTest.java | 5 +- 2 files changed, 118 insertions(+), 20 deletions(-) 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 f29560ce..aa69eb15 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 @@ -34,12 +34,18 @@ import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; + import ch.ethz.seb.sebserver.ClientHttpRequestFactoryService; import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.APIMessage; import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException; import ch.ethz.seb.sebserver.gbl.api.APIMessage.FieldValidationException; +import ch.ethz.seb.sebserver.gbl.api.JSONMapper; import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringRoomConnection; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings; @@ -69,9 +75,6 @@ public class JitsiProctoringService implements ExamProctoringService { private static final String JITSI_ACCESS_TOKEN_HEADER = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}"; - private static final String JITSI_ACCESS_TOKEN_PAYLOAD = - "{\"context\":{\"user\":{\"name\":\"%s\"}},\"iss\":\"%s\",\"aud\":\"%s\",\"sub\":\"%s\",\"room\":\"%s\"%s%s}"; - private static final Map SEB_API_NAME_INSTRUCTION_NAME_MAPPING = Utils.immutableMapOf(Arrays.asList( new Tuple<>( API.EXAM_PROCTORING_ATTR_RECEIVE_AUDIO, @@ -100,17 +103,20 @@ public class JitsiProctoringService implements ExamProctoringService { private final ExamSessionService examSessionService; private final Cryptor cryptor; private final ClientHttpRequestFactoryService clientHttpRequestFactoryService; + private final JSONMapper jsonMapper; protected JitsiProctoringService( final AuthorizationService authorizationService, final ExamSessionService examSessionService, final Cryptor cryptor, - final ClientHttpRequestFactoryService clientHttpRequestFactoryService) { + final ClientHttpRequestFactoryService clientHttpRequestFactoryService, + final JSONMapper jsonMapper) { this.authorizationService = authorizationService; this.examSessionService = examSessionService; this.cryptor = cryptor; this.clientHttpRequestFactoryService = clientHttpRequestFactoryService; + this.jsonMapper = jsonMapper; } @Override @@ -393,20 +399,22 @@ public class JitsiProctoringService implements ExamProctoringService { final String host, final boolean moderator) { - final String jwtPayload = String.format( - JITSI_ACCESS_TOKEN_PAYLOAD.replaceAll(" ", "").replaceAll("\n", ""), - clientName, - appKey, - clientKey, - host, - roomName, - (moderator) - ? ",\"moderator\":true" - : ",\"moderator\":false", - (expTime != null) - ? String.format(",\"exp\":%s", String.valueOf(expTime)) - : ""); - return jwtPayload; + try { + + final JWTContext jwtContext = new JWTContext( + clientKey, + appKey, + host, + new Context(new User(clientName, clientName, String.valueOf(moderator))), + roomName, + expTime); + + final String content = this.jsonMapper.writeValueAsString(jwtContext); + + return content; + } catch (final Exception e) { + throw new RuntimeException("Unexpected error while trying to create JWT payload: ", e); + } } private long forExam(final ProctoringServiceSettings examProctoring) { @@ -425,4 +433,93 @@ public class JitsiProctoringService implements ExamProctoringService { return expTime; } +// @formatter:off + + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(Include.NON_NULL) + private class JWTContext { + @JsonProperty final String aud; + @JsonProperty final String iss; + @JsonProperty final String sub; + @JsonProperty final Context context; + @JsonProperty final Long exp; + @JsonProperty final Long nbf; + @JsonProperty final String room; + @JsonProperty final Boolean moderator; + + public JWTContext(final String aud, final String iss, final String sub, final Context context, final String room, final Long exp) { + this.aud = aud; + this.iss = iss; + this.sub = sub; + this.context = context; + this.room = room; + this.exp = exp; + this.nbf = null; + this.moderator = BooleanUtils.toBooleanObject(context.user.moderator); + } + } + + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(Include.NON_NULL) + private class Context { + @JsonProperty final User user; + @JsonProperty final Features features; + + @SuppressWarnings("unused") + public Context(final User user, final Features features) { + this.user = user; + this.features = features; + } + public Context(final User user) { + this.user = user; + this.features = null; + } + } + + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(Include.NON_NULL) + private class User { + @JsonProperty final String id; + @JsonProperty final String name; + @JsonProperty final String avatar; + @JsonProperty final String email; + @JsonProperty final String moderator; + + @SuppressWarnings("unused") + public User(final String id, final String name, final String avatar, final String email, final String moderator) { + this.id = id; + this.name = name; + this.avatar = avatar; + this.email = email; + this.moderator = moderator; + } + + public User( + final String id, + final String name, + final String moderator) { + + this.id = id; + this.name = name; + this.avatar = null; + this.email = null; + this.moderator = moderator; + } + } + + private class Features { + @JsonProperty final Boolean livestreaming; + @JsonProperty("outbound-call") final Boolean outboundcall; + @JsonProperty final Boolean transcription; + @JsonProperty final Boolean recording; + + @SuppressWarnings("unused") + public Features(final Boolean livestreaming, final Boolean outboundcall, final Boolean transcription, final Boolean recording) { + this.livestreaming = livestreaming; + this.outboundcall = outboundcall; + this.transcription = transcription; + this.recording = recording; + } + } + } diff --git a/src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamJITSIProctoringServiceTest.java b/src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamJITSIProctoringServiceTest.java index 91f31231..0ec94298 100644 --- a/src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamJITSIProctoringServiceTest.java +++ b/src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamJITSIProctoringServiceTest.java @@ -17,6 +17,7 @@ import java.security.NoSuchAlgorithmException; import org.junit.Test; import org.mockito.Mockito; +import ch.ethz.seb.sebserver.gbl.api.JSONMapper; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringRoomConnection; import ch.ethz.seb.sebserver.gbl.util.Cryptor; import ch.ethz.seb.sebserver.gbl.util.Result; @@ -28,7 +29,7 @@ public class ExamJITSIProctoringServiceTest { final Cryptor cryptorMock = Mockito.mock(Cryptor.class); Mockito.when(cryptorMock.decrypt(Mockito.any())).thenReturn(Result.of("fbvgeghergrgrthrehreg123")); final JitsiProctoringService examJITSIProctoringService = - new JitsiProctoringService(null, null, cryptorMock, null); + new JitsiProctoringService(null, null, cryptorMock, null, new JSONMapper()); String accessToken = examJITSIProctoringService.createPayload( "test-app", @@ -62,7 +63,7 @@ public class ExamJITSIProctoringServiceTest { final Cryptor cryptorMock = Mockito.mock(Cryptor.class); Mockito.when(cryptorMock.decrypt(Mockito.any())).thenReturn(Result.of("fbvgeghergrgrthrehreg123")); final JitsiProctoringService examJITSIProctoringService = - new JitsiProctoringService(null, null, cryptorMock, null); + new JitsiProctoringService(null, null, cryptorMock, null, new JSONMapper()); final ProctoringRoomConnection data = examJITSIProctoringService.createProctoringConnection( "connectionToken", "https://seb-jitsi.example.ch",