SEBSERV-363 finished integration adaption and testing
This commit is contained in:
parent
edce7275ca
commit
4671e682a3
13 changed files with 209 additions and 124 deletions
|
@ -126,7 +126,7 @@ public class ProctoringServiceSettings implements Entity {
|
|||
|
||||
this.examId = examId;
|
||||
this.enableProctoring = BooleanUtils.isTrue(enableProctoring);
|
||||
this.serverType = (serverType != null) ? serverType : ProctoringServerType.JITSI_MEET;
|
||||
this.serverType = (serverType != null) ? serverType : ProctoringServerType.ZOOM;
|
||||
this.serverURL = serverURL;
|
||||
this.collectingRoomSize = (collectingRoomSize != null) ? collectingRoomSize : 20;
|
||||
this.enabledFeatures = enabledFeatures != null ? enabledFeatures : EnumSet.allOf(ProctoringFeature.class);
|
||||
|
@ -144,7 +144,7 @@ public class ProctoringServiceSettings implements Entity {
|
|||
public ProctoringServiceSettings(final Long examId) {
|
||||
this.examId = examId;
|
||||
this.enableProctoring = false;
|
||||
this.serverType = null;
|
||||
this.serverType = ProctoringServerType.ZOOM;
|
||||
this.serverURL = null;
|
||||
this.collectingRoomSize = 20;
|
||||
this.enabledFeatures = EnumSet.allOf(ProctoringFeature.class);
|
||||
|
|
|
@ -256,7 +256,6 @@ public class ExamSignatureKeyForm implements TemplateComposer {
|
|||
|
||||
.newAction(ActionDefinition.EXAM_SECURITY_KEY_BACK_MODIFY)
|
||||
.withEntityKey(entityKey)
|
||||
//.withExec(this.pageService.backToCurrentFunction())
|
||||
.publishIf(() -> readonly)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_SECURITY_KEY_SHOW_ADD_GRANT_POPUP)
|
||||
|
|
|
@ -111,6 +111,8 @@ public class ProctoringSettingsPopup {
|
|||
new LocTextKey("sebserver.exam.proctoring.form.resetSettings");
|
||||
private final static LocTextKey RESET_CONFIRM_KEY =
|
||||
new LocTextKey("sebserver.exam.proctoring.form.resetConfirm");
|
||||
private final static LocTextKey RESET_ACTIVE_CON_KEY =
|
||||
new LocTextKey("sebserver.exam.proctoring.form.resetActive");
|
||||
|
||||
Function<PageAction, PageAction> settingsFunction(final PageService pageService, final boolean modifyGrant) {
|
||||
|
||||
|
@ -171,7 +173,7 @@ public class ProctoringSettingsPopup {
|
|||
pc -> new SEBProctoringPropertiesForm(
|
||||
pageService,
|
||||
pageContext,
|
||||
resetButtonHandler));
|
||||
resetButtonHandler).compose(pc.getParent()));
|
||||
}
|
||||
|
||||
return action;
|
||||
|
@ -207,8 +209,14 @@ public class ProctoringSettingsPopup {
|
|||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||
.call()
|
||||
.onError(error -> {
|
||||
log.error("Failed to rest proctoring settings for exam: {}", entityKey, error);
|
||||
pageContext.notifyUnexpectedError(error);
|
||||
if (error.getMessage().contains("active connections") ||
|
||||
(error.getCause() != null &&
|
||||
error.getCause().getMessage().contains("active connections"))) {
|
||||
pageContext.publishInfo(RESET_ACTIVE_CON_KEY);
|
||||
} else {
|
||||
log.error("Failed to rest proctoring settings for exam: {}", entityKey, error);
|
||||
pageContext.notifyUnexpectedError(error);
|
||||
}
|
||||
}).map(settings -> true).getOr(false);
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.dao;
|
|||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
@ -102,10 +103,37 @@ public interface AdditionalAttributesDAO {
|
|||
final Long entityId,
|
||||
final Map<String, String> attributes) {
|
||||
|
||||
return saveAdditionalAttributes(type, entityId, attributes, false);
|
||||
}
|
||||
|
||||
/** Use this to save an additional attributes for a specific entity.
|
||||
* If an additional attribute with specified name already exists for the specified entity
|
||||
* this updates just the value for this additional attribute. Otherwise create a new instance
|
||||
* of additional attribute with the given data
|
||||
*
|
||||
* @param type the entity type
|
||||
* @param entityId the entity identifier (primary key)
|
||||
* @param attributes Map of attributes to save for
|
||||
* @param deleteNullValues indicates if null values shall be deleted or not */
|
||||
default Result<Collection<AdditionalAttributeRecord>> saveAdditionalAttributes(
|
||||
final EntityType type,
|
||||
final Long entityId,
|
||||
final Map<String, String> attributes,
|
||||
final boolean deleteNullValues) {
|
||||
|
||||
return Result.tryCatch(() -> attributes.entrySet()
|
||||
.stream()
|
||||
.map(attr -> saveAdditionalAttribute(type, entityId, attr.getKey(), attr.getValue())
|
||||
.onError(error -> log.warn("Failed to save additional attribute: {}", error.getMessage())))
|
||||
.map(attr -> {
|
||||
if (deleteNullValues && attr.getValue() == null) {
|
||||
delete(type, entityId, attr.getKey());
|
||||
return null;
|
||||
} else {
|
||||
return saveAdditionalAttribute(type, entityId, attr.getKey(), attr.getValue())
|
||||
.onError(error -> log.warn("Failed to save additional attribute: {}",
|
||||
error.getMessage()));
|
||||
}
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.flatMap(Result::skipOnError)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
|
|
@ -116,6 +116,10 @@ public interface ExamAdminService {
|
|||
* @return ExamProctoringService instance */
|
||||
Result<ExamProctoringService> getExamProctoringService(final Long examId);
|
||||
|
||||
/** This resets the proctoring settings for a given exam and stores the default settings.
|
||||
*
|
||||
* @param exam The exam reference
|
||||
* @return Result refer to the given exam or to an error when happened */
|
||||
Result<Exam> resetProctoringSettings(Exam exam);
|
||||
|
||||
/** This archives a finished exam and set it to archived state as well as the assigned
|
||||
|
|
|
@ -245,6 +245,9 @@ public class ExamAdminServiceImpl implements ExamAdminService {
|
|||
|
||||
@Override
|
||||
public Result<Exam> resetProctoringSettings(final Exam exam) {
|
||||
|
||||
// first delete all proctoring settings
|
||||
|
||||
return getProctoringServiceSettings(exam.id)
|
||||
.map(settings -> {
|
||||
ProctoringServiceSettings resetSettings;
|
||||
|
|
|
@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.exam.impl;
|
|||
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
|
@ -100,7 +101,6 @@ public class ProctoringAdminServiceImpl implements ProctoringAdminService {
|
|||
ProctoringServiceSettings.ATTR_USE_ZOOM_APP_CLIENT_COLLECTING_ROOM));
|
||||
})
|
||||
.getOrThrow();
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -118,95 +118,53 @@ public class ProctoringAdminServiceImpl implements ProctoringAdminService {
|
|||
testExamProctoring(proctoringServiceSettings).getOrThrow();
|
||||
}
|
||||
|
||||
this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||
parentEntityKey.entityType,
|
||||
entityId,
|
||||
final Map<String, String> attributes = new HashMap<>();
|
||||
attributes.put(
|
||||
ProctoringServiceSettings.ATTR_ENABLE_PROCTORING,
|
||||
String.valueOf(proctoringServiceSettings.enableProctoring));
|
||||
|
||||
this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||
parentEntityKey.entityType,
|
||||
entityId,
|
||||
attributes.put(
|
||||
ProctoringServiceSettings.ATTR_SERVER_TYPE,
|
||||
proctoringServiceSettings.serverType.name());
|
||||
|
||||
this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||
parentEntityKey.entityType,
|
||||
entityId,
|
||||
attributes.put(
|
||||
ProctoringServiceSettings.ATTR_SERVER_URL,
|
||||
StringUtils.trim(proctoringServiceSettings.serverURL));
|
||||
|
||||
this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||
parentEntityKey.entityType,
|
||||
entityId,
|
||||
attributes.put(
|
||||
ProctoringServiceSettings.ATTR_COLLECTING_ROOM_SIZE,
|
||||
String.valueOf(proctoringServiceSettings.collectingRoomSize));
|
||||
|
||||
if (StringUtils.isNotBlank(proctoringServiceSettings.appKey)) {
|
||||
this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||
parentEntityKey.entityType,
|
||||
entityId,
|
||||
ProctoringServiceSettings.ATTR_APP_KEY,
|
||||
StringUtils.trim(proctoringServiceSettings.appKey));
|
||||
|
||||
this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||
parentEntityKey.entityType,
|
||||
entityId,
|
||||
ProctoringServiceSettings.ATTR_APP_SECRET,
|
||||
this.cryptor.encrypt(Utils.trim(proctoringServiceSettings.appSecret))
|
||||
.getOrThrow()
|
||||
.toString());
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(proctoringServiceSettings.accountId)) {
|
||||
this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||
parentEntityKey.entityType,
|
||||
entityId,
|
||||
ProctoringServiceSettings.ATTR_ACCOUNT_ID,
|
||||
StringUtils.trim(proctoringServiceSettings.accountId));
|
||||
this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||
parentEntityKey.entityType,
|
||||
entityId,
|
||||
ProctoringServiceSettings.ATTR_ACCOUNT_CLIENT_ID,
|
||||
StringUtils.trim(proctoringServiceSettings.clientId));
|
||||
|
||||
this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||
parentEntityKey.entityType,
|
||||
entityId,
|
||||
ProctoringServiceSettings.ATTR_ACCOUNT_CLIENT_SECRET,
|
||||
this.cryptor.encrypt(Utils.trim(proctoringServiceSettings.clientSecret))
|
||||
.getOrThrow()
|
||||
.toString());
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(proctoringServiceSettings.sdkKey)) {
|
||||
this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||
parentEntityKey.entityType,
|
||||
entityId,
|
||||
ProctoringServiceSettings.ATTR_SDK_KEY,
|
||||
StringUtils.trim(proctoringServiceSettings.sdkKey));
|
||||
|
||||
this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||
parentEntityKey.entityType,
|
||||
entityId,
|
||||
ProctoringServiceSettings.ATTR_SDK_SECRET,
|
||||
this.cryptor.encrypt(Utils.trim(proctoringServiceSettings.sdkSecret))
|
||||
.getOrThrow()
|
||||
.toString());
|
||||
}
|
||||
|
||||
this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||
parentEntityKey.entityType,
|
||||
entityId,
|
||||
attributes.put(
|
||||
ProctoringServiceSettings.ATTR_APP_KEY,
|
||||
StringUtils.trim(proctoringServiceSettings.appKey));
|
||||
attributes.put(
|
||||
ProctoringServiceSettings.ATTR_APP_SECRET,
|
||||
encryptSecret(Utils.trim(proctoringServiceSettings.appSecret)));
|
||||
attributes.put(
|
||||
ProctoringServiceSettings.ATTR_ACCOUNT_ID,
|
||||
StringUtils.trim(proctoringServiceSettings.accountId));
|
||||
attributes.put(
|
||||
ProctoringServiceSettings.ATTR_ACCOUNT_CLIENT_ID,
|
||||
StringUtils.trim(proctoringServiceSettings.clientId));
|
||||
attributes.put(
|
||||
ProctoringServiceSettings.ATTR_ACCOUNT_CLIENT_SECRET,
|
||||
encryptSecret(Utils.trim(proctoringServiceSettings.clientSecret)));
|
||||
attributes.put(
|
||||
ProctoringServiceSettings.ATTR_SDK_KEY,
|
||||
StringUtils.trim(proctoringServiceSettings.sdkKey));
|
||||
attributes.put(
|
||||
ProctoringServiceSettings.ATTR_SDK_SECRET,
|
||||
encryptSecret(Utils.trim(proctoringServiceSettings.sdkSecret)));
|
||||
attributes.put(
|
||||
ProctoringServiceSettings.ATTR_ENABLED_FEATURES,
|
||||
StringUtils.join(proctoringServiceSettings.enabledFeatures, Constants.LIST_SEPARATOR));
|
||||
|
||||
this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||
parentEntityKey.entityType,
|
||||
entityId,
|
||||
attributes.put(
|
||||
ProctoringServiceSettings.ATTR_USE_ZOOM_APP_CLIENT_COLLECTING_ROOM,
|
||||
String.valueOf(proctoringServiceSettings.useZoomAppClientForCollectingRoom));
|
||||
|
||||
this.additionalAttributesDAO.saveAdditionalAttributes(
|
||||
parentEntityKey.entityType,
|
||||
entityId,
|
||||
attributes,
|
||||
true);
|
||||
|
||||
return proctoringServiceSettings;
|
||||
});
|
||||
}
|
||||
|
@ -293,4 +251,13 @@ public class ProctoringAdminServiceImpl implements ProctoringAdminService {
|
|||
}
|
||||
}
|
||||
|
||||
private String encryptSecret(final CharSequence secret) {
|
||||
if (StringUtils.isBlank(secret)) {
|
||||
return null;
|
||||
}
|
||||
return this.cryptor.encrypt(Utils.trim(secret))
|
||||
.getOrThrow()
|
||||
.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -139,6 +139,11 @@ public interface ExamProctoringRoomService {
|
|||
* @return Result refer to void or to an error when happened */
|
||||
Result<Void> notifyRoomOpened(Long examId, String roomName);
|
||||
|
||||
/** Disposes all open proctoring rooms for a given exam and removes the references on the persistent storage.
|
||||
* First checks if there are active SEB client connections on for the given exam and if so, throws error.
|
||||
*
|
||||
* @param exam The exam to cleanup the rooms
|
||||
* @return Result refer to the given exam or to an error when happened */
|
||||
Result<Exam> cleanupAllRooms(Exam exam);
|
||||
|
||||
}
|
||||
|
|
|
@ -125,4 +125,6 @@ public interface ExamProctoringService {
|
|||
RemoteProctoringRoom room,
|
||||
Collection<ClientConnection> clientConnections);
|
||||
|
||||
public void clearRestTemplateCache(final Long examId);
|
||||
|
||||
}
|
||||
|
|
|
@ -351,6 +351,11 @@ public class JitsiProctoringService implements ExamProctoringService {
|
|||
return Result.EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearRestTemplateCache(final Long examId) {
|
||||
// Nothing to do here
|
||||
}
|
||||
|
||||
protected Result<ProctoringRoomConnection> createProctoringConnection(
|
||||
final String connectionToken,
|
||||
final String url,
|
||||
|
|
|
@ -15,7 +15,9 @@ import java.util.Base64;
|
|||
import java.util.Base64.Encoder;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
@ -38,11 +40,22 @@ import org.springframework.http.HttpMethod;
|
|||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext;
|
||||
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
|
||||
import org.springframework.security.oauth2.client.resource.BaseOAuth2ProtectedResourceDetails;
|
||||
import org.springframework.security.oauth2.client.token.DefaultAccessTokenRequest;
|
||||
import org.springframework.security.oauth2.client.resource.OAuth2AccessDeniedException;
|
||||
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
|
||||
import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException;
|
||||
import org.springframework.security.oauth2.client.token.AccessTokenProvider;
|
||||
import org.springframework.security.oauth2.client.token.AccessTokenRequest;
|
||||
import org.springframework.security.oauth2.client.token.OAuth2AccessTokenSupport;
|
||||
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;
|
||||
import org.springframework.security.oauth2.common.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.client.RestClientResponseException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
@ -196,13 +209,10 @@ public class ZoomProctoringService implements ExamProctoringService {
|
|||
final ResponseEntity<String> result = newRestTemplate.testServiceConnection();
|
||||
|
||||
if (result.getStatusCode() != HttpStatus.OK) {
|
||||
throw new APIMessageException(Arrays.asList(
|
||||
APIMessage.fieldValidationError(ProctoringServiceSettings.ATTR_SERVER_URL,
|
||||
"proctoringSettings:serverURL:url.invalid"),
|
||||
APIMessage.ErrorMessage.EXTERNAL_SERVICE_BINDING_ERROR.of()));
|
||||
throw new RuntimeException("Invalid Zoom Service response: " + result);
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to access Zoom service at: {}", proctoringSettings.serverURL, e.getMessage());
|
||||
log.error("Failed to access Zoom service at: {}", proctoringSettings.serverURL, e);
|
||||
throw new APIMessageException(Arrays.asList(
|
||||
APIMessage.fieldValidationError(ProctoringServiceSettings.ATTR_SERVER_URL,
|
||||
"proctoringSettings:serverURL:url.noservice"),
|
||||
|
@ -655,35 +665,17 @@ public class ZoomProctoringService implements ExamProctoringService {
|
|||
|
||||
// NOTE: following is the original code that includes the exam end time but seems to make trouble for OLAT
|
||||
final long nowInSeconds = Utils.getSecondsNow();
|
||||
// final long nowPlus30MinInSeconds = nowInSeconds + Utils.toSeconds(30 * Constants.MINUTE_IN_MILLIS);
|
||||
final long nowPlusOneDayInSeconds = nowInSeconds + Utils.toSeconds(Constants.DAY_IN_MILLIS);
|
||||
// final long nowPlusTwoDayInSeconds = nowInSeconds + Utils.toSeconds(2 * Constants.DAY_IN_MILLIS);
|
||||
|
||||
// long expTime = nowPlusOneDayInSeconds;
|
||||
// if (examProctoring.examId == null && this.examSessionService.isExamRunning(examProctoring.examId)) {
|
||||
// final Exam exam = this.examSessionService.getRunningExam(examProctoring.examId)
|
||||
// .getOrThrow();
|
||||
// if (exam.endTime != null) {
|
||||
// expTime = Utils.toSeconds(exam.endTime.getMillis());
|
||||
// }
|
||||
// }
|
||||
// // refer to https://marketplace.zoom.us/docs/sdk/native-sdks/auth
|
||||
// // "exp": 0, //JWT expiration date (Min:1800 seconds greater than iat value, Max: 48 hours greater than iat value) in epoch format.
|
||||
// if (expTime >= nowPlusTwoDayInSeconds) {
|
||||
// expTime = nowPlusTwoDayInSeconds - 10; // Do not set to max because it is not well defined if max is included or not
|
||||
// } else if (expTime < nowPlus30MinInSeconds) {
|
||||
// expTime = nowPlusOneDayInSeconds;
|
||||
// }
|
||||
//
|
||||
// log.debug("**** SDK Token exp time with exam-end-time inclusion would be: {}", expTime);
|
||||
//
|
||||
//
|
||||
// NOTE: Set this to the maximum according to https://marketplace.zoom.us/docs/sdk/native-sdks/auth
|
||||
//return nowPlusTwoDayInSeconds - 1000; // Do not set to max because it is not well defined if max is included or not;
|
||||
// NOTE: It seems that since the update of web sdk to SDKToken to 1.7.0, the max is new + one day
|
||||
return nowPlusOneDayInSeconds - 10;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void clearRestTemplateCache(final Long examId) {
|
||||
this.restTemplatesCache.remove(examId);
|
||||
}
|
||||
|
||||
private final LinkedHashMap<Long, ZoomRestTemplate> restTemplatesCache = new LinkedHashMap<>();
|
||||
|
||||
private synchronized ZoomRestTemplate getZoomRestTemplate(final ProctoringServiceSettings proctoringSettings) {
|
||||
|
@ -828,7 +820,7 @@ public class ZoomProctoringService implements ExamProctoringService {
|
|||
final HttpHeaders headers = getHeaders();
|
||||
|
||||
headers.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
|
||||
final ResponseEntity<String> exchange = exchange(url, HttpMethod.PATCH, body, headers);
|
||||
final ResponseEntity<String> exchange = exchange(url, HttpMethod.POST, body, headers);
|
||||
return exchange;
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to apply user settings for Zoom user: {}", userId, e);
|
||||
|
@ -1006,17 +998,19 @@ public class ZoomProctoringService implements ExamProctoringService {
|
|||
.decrypt(this.credentials.secret)
|
||||
.getOrThrow();
|
||||
|
||||
this.resource = new BaseOAuth2ProtectedResourceDetails();
|
||||
this.resource = new ClientCredentialsResourceDetails();
|
||||
this.resource.setAccessTokenUri(this.proctoringSettings.serverURL + "/oauth/token");
|
||||
this.resource.setClientId(this.credentials.clientIdAsString());
|
||||
this.resource.setClientSecret(decryptedSecret.toString());
|
||||
this.resource.setGrantType("account_credentials");
|
||||
this.resource.setId(this.proctoringSettings.accountId);
|
||||
|
||||
final DefaultAccessTokenRequest defaultAccessTokenRequest = new DefaultAccessTokenRequest();
|
||||
defaultAccessTokenRequest.set("account_id", this.proctoringSettings.accountId);
|
||||
this.restTemplate = new OAuth2RestTemplate(
|
||||
this.resource,
|
||||
new DefaultOAuth2ClientContext(defaultAccessTokenRequest));
|
||||
final SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
|
||||
requestFactory.setOutputStreaming(false);
|
||||
final OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(this.resource);
|
||||
oAuth2RestTemplate.setRequestFactory(requestFactory);
|
||||
oAuth2RestTemplate.setAccessTokenProvider(new ZoomCredentialsAccessTokenProvider());
|
||||
this.restTemplate = oAuth2RestTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1116,4 +1110,63 @@ public class ZoomProctoringService implements ExamProctoringService {
|
|||
}
|
||||
}
|
||||
|
||||
private static final class ZoomCredentialsAccessTokenProvider extends OAuth2AccessTokenSupport
|
||||
implements AccessTokenProvider {
|
||||
|
||||
@Override
|
||||
public boolean supportsResource(final OAuth2ProtectedResourceDetails resource) {
|
||||
return resource instanceof ClientCredentialsResourceDetails
|
||||
&& "account_credentials".equals(resource.getGrantType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRefresh(final OAuth2ProtectedResourceDetails resource) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2AccessToken refreshAccessToken(final OAuth2ProtectedResourceDetails resource,
|
||||
final OAuth2RefreshToken refreshToken, final AccessTokenRequest request)
|
||||
throws UserRedirectRequiredException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2AccessToken obtainAccessToken(final OAuth2ProtectedResourceDetails details,
|
||||
final AccessTokenRequest request)
|
||||
throws UserRedirectRequiredException, AccessDeniedException, OAuth2AccessDeniedException {
|
||||
|
||||
final ClientCredentialsResourceDetails resource = (ClientCredentialsResourceDetails) details;
|
||||
return retrieveToken(request, resource, getParametersForTokenRequest(resource), new HttpHeaders());
|
||||
|
||||
}
|
||||
|
||||
private MultiValueMap<String, String> getParametersForTokenRequest(
|
||||
final ClientCredentialsResourceDetails resource) {
|
||||
|
||||
final MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
|
||||
form.set("grant_type", "account_credentials");
|
||||
form.set("account_id", resource.getId());
|
||||
|
||||
if (resource.isScoped()) {
|
||||
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
final List<String> scope = resource.getScope();
|
||||
|
||||
if (scope != null) {
|
||||
final Iterator<String> scopeIt = scope.iterator();
|
||||
while (scopeIt.hasNext()) {
|
||||
builder.append(scopeIt.next());
|
||||
if (scopeIt.hasNext()) {
|
||||
builder.append(' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
form.set("scope", builder.toString());
|
||||
}
|
||||
return form;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -22,6 +22,8 @@ import javax.validation.Valid;
|
|||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.joda.time.DateTime;
|
||||
import org.mybatis.dynamic.sql.SqlTable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
|
@ -80,6 +82,8 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationSe
|
|||
@RequestMapping("${sebserver.webservice.api.admin.endpoint}" + API.EXAM_ADMINISTRATION_ENDPOINT)
|
||||
public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ExamAdministrationController.class);
|
||||
|
||||
private final ExamDAO examDAO;
|
||||
private final UserDAO userDAO;
|
||||
private final ExamAdminService examAdminService;
|
||||
|
@ -508,9 +512,16 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
|||
return this.entityDAO
|
||||
.byPK(examId)
|
||||
.flatMap(this.examProctoringRoomService::cleanupAllRooms)
|
||||
.map(exam -> {
|
||||
this.examAdminService.getExamProctoringService(exam.id)
|
||||
.onSuccess(service -> service.clearRestTemplateCache(exam.id))
|
||||
.onError(error -> log.warn(
|
||||
"Failed to clear proctoring rest template cache for exam: {}",
|
||||
error.getMessage()));
|
||||
return exam;
|
||||
})
|
||||
.flatMap(this.examAdminService::resetProctoringSettings)
|
||||
.getOrThrow();
|
||||
|
||||
}
|
||||
|
||||
// **** Proctoring
|
||||
|
|
|
@ -831,7 +831,7 @@ sebserver.exam.proctoring.form.useZoomAppClient.tooltip=If this is set SEB Serve
|
|||
sebserver.exam.proctoring.form.saveSettings=Save Settings
|
||||
sebserver.exam.proctoring.form.resetSettings=Reset Settings
|
||||
sebserver.exam.proctoring.form.resetConfirm=Reset will first cleanup and remove all existing proctoring rooms for this exam and then reset to default or template settings.<br/>Please make sure there are no active SEB client connections for this exam, otherwise this reset will be denied.<br/><br/>Do you want to reset proctoring for this exam now?
|
||||
|
||||
sebserver.exam.proctoring.form.resetActive=There are still active SEB client connection for this exam. Please disconnect or cancel them first.<br/>This action is only possible when there are no active SEB client connection within this exam.
|
||||
|
||||
sebserver.exam.proctoring.type.servertype.JITSI_MEET=Jitsi Meet Server
|
||||
sebserver.exam.proctoring.type.servertype.JITSI_MEET.tooltip=Use a Jitsi Meet server for proctoring
|
||||
|
|
Loading…
Reference in a new issue