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.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<String, String> 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;
}
}
}

View file

@ -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",