Jitsi integration: new access token creation for JaaS integration

This commit is contained in:
anhefti 2021-06-17 14:07:17 +02:00
parent 7360d8b99b
commit e88d2d9edd
2 changed files with 118 additions and 20 deletions

View file

@ -34,12 +34,18 @@ import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder; 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.ClientHttpRequestFactoryService;
import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.APIMessage; 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.APIMessageException;
import ch.ethz.seb.sebserver.gbl.api.APIMessage.FieldValidationException; 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.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;
@ -69,9 +75,6 @@ public class JitsiProctoringService implements ExamProctoringService {
private static final String JITSI_ACCESS_TOKEN_HEADER = private static final String JITSI_ACCESS_TOKEN_HEADER =
"{\"alg\":\"HS256\",\"typ\":\"JWT\"}"; "{\"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<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<>(
API.EXAM_PROCTORING_ATTR_RECEIVE_AUDIO, API.EXAM_PROCTORING_ATTR_RECEIVE_AUDIO,
@ -100,17 +103,20 @@ public class JitsiProctoringService implements ExamProctoringService {
private final ExamSessionService examSessionService; private final ExamSessionService examSessionService;
private final Cryptor cryptor; private final Cryptor cryptor;
private final ClientHttpRequestFactoryService clientHttpRequestFactoryService; private final ClientHttpRequestFactoryService clientHttpRequestFactoryService;
private final JSONMapper jsonMapper;
protected JitsiProctoringService( protected JitsiProctoringService(
final AuthorizationService authorizationService, final AuthorizationService authorizationService,
final ExamSessionService examSessionService, final ExamSessionService examSessionService,
final Cryptor cryptor, final Cryptor cryptor,
final ClientHttpRequestFactoryService clientHttpRequestFactoryService) { final ClientHttpRequestFactoryService clientHttpRequestFactoryService,
final JSONMapper jsonMapper) {
this.authorizationService = authorizationService; this.authorizationService = authorizationService;
this.examSessionService = examSessionService; this.examSessionService = examSessionService;
this.cryptor = cryptor; this.cryptor = cryptor;
this.clientHttpRequestFactoryService = clientHttpRequestFactoryService; this.clientHttpRequestFactoryService = clientHttpRequestFactoryService;
this.jsonMapper = jsonMapper;
} }
@Override @Override
@ -393,20 +399,22 @@ public class JitsiProctoringService implements ExamProctoringService {
final String host, final String host,
final boolean moderator) { final boolean moderator) {
final String jwtPayload = String.format( try {
JITSI_ACCESS_TOKEN_PAYLOAD.replaceAll(" ", "").replaceAll("\n", ""),
clientName, final JWTContext jwtContext = new JWTContext(
appKey,
clientKey, clientKey,
appKey,
host, host,
new Context(new User(clientName, clientName, String.valueOf(moderator))),
roomName, roomName,
(moderator) expTime);
? ",\"moderator\":true"
: ",\"moderator\":false", final String content = this.jsonMapper.writeValueAsString(jwtContext);
(expTime != null)
? String.format(",\"exp\":%s", String.valueOf(expTime)) return content;
: ""); } catch (final Exception e) {
return jwtPayload; throw new RuntimeException("Unexpected error while trying to create JWT payload: ", e);
}
} }
private long forExam(final ProctoringServiceSettings examProctoring) { private long forExam(final ProctoringServiceSettings examProctoring) {
@ -425,4 +433,93 @@ public class JitsiProctoringService implements ExamProctoringService {
return expTime; 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;
}
}
} }

View file

@ -17,6 +17,7 @@ import java.security.NoSuchAlgorithmException;
import org.junit.Test; import org.junit.Test;
import org.mockito.Mockito; 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.model.exam.ProctoringRoomConnection;
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;
@ -28,7 +29,7 @@ public class ExamJITSIProctoringServiceTest {
final Cryptor cryptorMock = Mockito.mock(Cryptor.class); final Cryptor cryptorMock = Mockito.mock(Cryptor.class);
Mockito.when(cryptorMock.decrypt(Mockito.any())).thenReturn(Result.of("fbvgeghergrgrthrehreg123")); Mockito.when(cryptorMock.decrypt(Mockito.any())).thenReturn(Result.of("fbvgeghergrgrthrehreg123"));
final JitsiProctoringService examJITSIProctoringService = final JitsiProctoringService examJITSIProctoringService =
new JitsiProctoringService(null, null, cryptorMock, null); new JitsiProctoringService(null, null, cryptorMock, null, new JSONMapper());
String accessToken = examJITSIProctoringService.createPayload( String accessToken = examJITSIProctoringService.createPayload(
"test-app", "test-app",
@ -62,7 +63,7 @@ public class ExamJITSIProctoringServiceTest {
final Cryptor cryptorMock = Mockito.mock(Cryptor.class); final Cryptor cryptorMock = Mockito.mock(Cryptor.class);
Mockito.when(cryptorMock.decrypt(Mockito.any())).thenReturn(Result.of("fbvgeghergrgrthrehreg123")); Mockito.when(cryptorMock.decrypt(Mockito.any())).thenReturn(Result.of("fbvgeghergrgrthrehreg123"));
final JitsiProctoringService examJITSIProctoringService = final JitsiProctoringService examJITSIProctoringService =
new JitsiProctoringService(null, null, cryptorMock, null); new JitsiProctoringService(null, null, cryptorMock, null, new JSONMapper());
final ProctoringRoomConnection data = examJITSIProctoringService.createProctoringConnection( final ProctoringRoomConnection data = examJITSIProctoringService.createProctoringConnection(
"connectionToken", "connectionToken",
"https://seb-jitsi.example.ch", "https://seb-jitsi.example.ch",