diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java index 202ca6dc..2f0ed7dd 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java @@ -56,6 +56,7 @@ public final class API { public static final String OAUTH_ENDPOINT = "/oauth"; public static final String OAUTH_TOKEN_ENDPOINT = OAUTH_ENDPOINT + "/token"; + public static final String OAUTH_JWTTOKEN_ENDPOINT = OAUTH_ENDPOINT + "/jwttoken"; public static final String OAUTH_REVOKE_TOKEN_ENDPOINT = OAUTH_ENDPOINT + "/revoke-token"; public static final String CURRENT_USER_PATH_SEGMENT = "/me"; diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ScreenProctoringSettings.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ScreenProctoringSettings.java index c99a1209..9d5a3ef5 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ScreenProctoringSettings.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ScreenProctoringSettings.java @@ -34,6 +34,8 @@ public class ScreenProctoringSettings { public static final String ATTR_SPS_ACCOUNT_ID = "spsAccountId"; public static final String ATTR_SPS_ACCOUNT_PASSWORD = "spsAccountPassword"; + public static final String ATTR_SPS_BUNDLED = "bundled"; + @JsonProperty(Domain.EXAM.ATTR_ID) public final Long examId; @@ -62,6 +64,9 @@ public class ScreenProctoringSettings { @JsonProperty(ATTR_COLLECTING_GROUP_SIZE) public final Integer collectingGroupSize; + @JsonProperty(ATTR_SPS_BUNDLED) + public final boolean bundled; + @JsonCreator public ScreenProctoringSettings( @JsonProperty(Domain.EXAM.ATTR_ID) final Long examId, @@ -72,7 +77,8 @@ public class ScreenProctoringSettings { @JsonProperty(ATTR_SPS_ACCOUNT_ID) final String spsAccountId, @JsonProperty(ATTR_SPS_ACCOUNT_PASSWORD) final CharSequence spsAccountPassword, @JsonProperty(ATTR_COLLECTING_STRATEGY) final CollectingStrategy collectingStrategy, - @JsonProperty(ATTR_COLLECTING_GROUP_SIZE) final Integer collectingGroupSize) { + @JsonProperty(ATTR_COLLECTING_GROUP_SIZE) final Integer collectingGroupSize, + @JsonProperty(ATTR_SPS_BUNDLED) final boolean bundled) { this.examId = examId; this.enableScreenProctoring = enableScreenProctoring; @@ -83,6 +89,30 @@ public class ScreenProctoringSettings { this.spsAccountPassword = spsAccountPassword; this.collectingStrategy = collectingStrategy; this.collectingGroupSize = collectingGroupSize; + this.bundled = bundled; + } + + public ScreenProctoringSettings( + final Long examId, + final Boolean enableScreenProctoring, + final String spsServiceURL, + final String spsAPIKey, + final CharSequence spsAPISecret, + final String spsAccountId, + final CharSequence spsAccountPassword, + final CollectingStrategy collectingStrategy, + final Integer collectingGroupSize) { + + this.examId = examId; + this.enableScreenProctoring = enableScreenProctoring; + this.spsServiceURL = spsServiceURL; + this.spsAPIKey = spsAPIKey; + this.spsAPISecret = spsAPISecret; + this.spsAccountId = spsAccountId; + this.spsAccountPassword = spsAccountPassword; + this.collectingStrategy = collectingStrategy; + this.collectingGroupSize = collectingGroupSize; + this.bundled = false; } public ScreenProctoringSettings(final Exam exam) { @@ -108,6 +138,7 @@ public class ScreenProctoringSettings { this.collectingGroupSize = Integer.parseInt(exam.additionalAttributes.getOrDefault( ATTR_COLLECTING_GROUP_SIZE, "-1")); + this.bundled = false; } public Long getExamId() { @@ -151,6 +182,10 @@ public class ScreenProctoringSettings { return Objects.hash(this.examId); } + public boolean isBundled() { + return this.bundled; + } + @Override public boolean equals(final Object obj) { if (this == obj) diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/util/Utils.java b/src/main/java/ch/ethz/seb/sebserver/gbl/util/Utils.java index 7e0d736b..af87fc40 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/util/Utils.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/util/Utils.java @@ -39,6 +39,7 @@ import java.util.stream.Collectors; import javax.validation.constraints.NotNull; +import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Hex; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; @@ -919,4 +920,12 @@ public final class Utils { .replaceAll("[^A-Za-z0-9_]", ""); } + public static String createBasicAuthHeader(final String clientname, final CharSequence clientsecret) { + final String plainCreds = clientname + Constants.COLON + clientsecret; + final byte[] plainCredsBytes = plainCreds.getBytes(); + final byte[] base64CredsBytes = Base64.encodeBase64(plainCredsBytes); + final String base64Creds = new String(base64CredsBytes); + return "Basic " + base64Creds; + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/ProctoringServlet.java b/src/main/java/ch/ethz/seb/sebserver/gui/ProctoringServlet.java index 209eca4c..73321a3e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/ProctoringServlet.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/ProctoringServlet.java @@ -18,7 +18,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; -import org.apache.commons.lang3.BooleanUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; @@ -26,21 +25,17 @@ import org.springframework.stereotype.Component; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; -import ch.ethz.seb.sebserver.gbl.model.user.UserInfo; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.AuthorizationContextHolder; import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.SEBServerAuthorizationContext; import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService; import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService.ProctoringWindowData; -import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService.ScreenProctoringWindowData; import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringWindowScriptResolver; @Component @GuiProfile public class ProctoringServlet extends HttpServlet { - public static final String SCREEN_PROCOTRING_FLAG_PARAM = "screenproctoring"; - private static final long serialVersionUID = 3475978419653411800L; private static final Logger log = LoggerFactory.getLogger(ProctoringServlet.class); @@ -63,54 +58,12 @@ public class ProctoringServlet extends HttpServlet { final WebApplicationContext webApplicationContext = WebApplicationContextUtils .getRequiredWebApplicationContext(servletContext); - UserInfo user; - try { - user = isAuthenticated(httpSession, webApplicationContext); - } catch (final Exception e) { + final boolean authenticated = isAuthenticated(httpSession, webApplicationContext); + if (!authenticated) { resp.setStatus(HttpStatus.FORBIDDEN.value()); return; } - final String parameter = req.getParameter(SCREEN_PROCOTRING_FLAG_PARAM); - if (BooleanUtils.toBoolean(parameter)) { - openScreenProctoring(req, resp, user, httpSession); - } else { - openRemoteProctoring(resp, httpSession); - } - } - - private void openScreenProctoring( - final HttpServletRequest req, - final HttpServletResponse resp, - final UserInfo user, - final HttpSession httpSession) throws IOException { - - final ScreenProctoringWindowData data = (ScreenProctoringWindowData) httpSession - .getAttribute(ProctoringGUIService.SESSION_ATTR_SCREEN_PROCTORING_DATA); - - // NOTE: POST on data.loginLocation seems not to work for automated login - // TODO discuss with Nadim how to make a direct login POST on the GUI client - // maybe there is a way to expose /login endpoint for directly POST credentials for login. - - // https://stackoverflow.com/questions/46582/response-redirect-with-post-instead-of-get - final StringBuilder sb = new StringBuilder(); -// sb.append(""); -// sb.append(""); -// sb.append("
"); -// sb.append(""); -// sb.append(""); -// sb.append("
"); -// sb.append(""); -// sb.append(""); - - resp.getOutputStream().println(sb.toString()); - } - - private void openRemoteProctoring( - final HttpServletResponse resp, - final HttpSession httpSession) throws IOException { - final ProctoringWindowData proctoringData = (ProctoringWindowData) httpSession .getAttribute(ProctoringGUIService.SESSION_ATTR_PROCTORING_DATA); @@ -136,7 +89,7 @@ public class ProctoringServlet extends HttpServlet { resp.setStatus(HttpServletResponse.SC_OK); } - private UserInfo isAuthenticated( + private boolean isAuthenticated( final HttpSession httpSession, final WebApplicationContext webApplicationContext) { @@ -144,11 +97,7 @@ public class ProctoringServlet extends HttpServlet { .getBean(AuthorizationContextHolder.class); final SEBServerAuthorizationContext authorizationContext = authorizationContextHolder .getAuthorizationContext(httpSession); - if (!authorizationContext.isValid() || !authorizationContext.isLoggedIn()) { - throw new RuntimeException("No authentication found"); - } - - return authorizationContext.getLoggedInUser().getOrThrow(); + return authorizationContext.isValid() && authorizationContext.isLoggedIn(); } } \ No newline at end of file diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ScreenProctoringSettingsPopup.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ScreenProctoringSettingsPopup.java index 5cf42840..dddbc0db 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ScreenProctoringSettingsPopup.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ScreenProctoringSettingsPopup.java @@ -199,7 +199,13 @@ public class ScreenProctoringSettingsPopup { new ActionEvent(action), action.pageContext()); return true; + } else { + final String bundled = formHandle.getForm().getStaticValue(ScreenProctoringSettings.ATTR_SPS_BUNDLED); + if (bundled != null) { + pageContext.notifyActivationError(EntityType.SCREEN_PROCTORING_GROUP, saveRequest.getError()); + } } + return false; } @@ -243,11 +249,14 @@ public class ScreenProctoringSettingsPopup { this.pageContext.getAttribute(PageContext.AttributeKeys.FORCE_READ_ONLY)); final FormHandle form = this.pageService.formBuilder(formContext) + .putStaticValueIf( + () -> settings.bundled, + ScreenProctoringSettings.ATTR_SPS_BUNDLED, + Constants.TRUE_STRING) .withDefaultSpanInput(5) .withEmptyCellSeparation(true) .withDefaultSpanEmptyCell(1) .readonly(isReadonly) - .addField(FormBuilder.text( "Info", FORM_INFO_TITLE, @@ -265,33 +274,39 @@ public class ScreenProctoringSettingsPopup { ScreenProctoringSettings.ATTR_SPS_SERVICE_URL, FORM_URL, settings.spsServiceURL) - .mandatory()) + .mandatory() + .readonly(settings.bundled)) .addField(FormBuilder.text( ScreenProctoringSettings.ATTR_SPS_API_KEY, FORM_APPKEY_SPS, - settings.spsAPIKey)) + settings.spsAPIKey) + .readonly(settings.bundled)) .withEmptyCellSeparation(false) - .addField(FormBuilder.password( - ScreenProctoringSettings.ATTR_SPS_API_SECRET, - FORM_APPSECRET_SPS, - (settings.spsAPISecret != null) - ? String.valueOf(settings.spsAPISecret) - : null)) + .addFieldIf( + () -> !settings.bundled, + () -> FormBuilder.password( + ScreenProctoringSettings.ATTR_SPS_API_SECRET, + FORM_APPSECRET_SPS, + (settings.spsAPISecret != null) + ? String.valueOf(settings.spsAPISecret) + : null)) .addField(FormBuilder.text( ScreenProctoringSettings.ATTR_SPS_ACCOUNT_ID, FORM_ACCOUNT_ID_SPS, - settings.spsAccountId)) + settings.spsAccountId) + .readonly(settings.bundled)) .withEmptyCellSeparation(false) - - .addField(FormBuilder.password( - ScreenProctoringSettings.ATTR_SPS_ACCOUNT_PASSWORD, - FORM_ACCOUNT_SECRET_SPS, - (settings.spsAccountPassword != null) - ? String.valueOf(settings.spsAccountPassword) - : null)) + .addFieldIf( + () -> !settings.bundled, + () -> FormBuilder.password( + ScreenProctoringSettings.ATTR_SPS_ACCOUNT_PASSWORD, + FORM_ACCOUNT_SECRET_SPS, + (settings.spsAccountPassword != null) + ? String.valueOf(settings.spsAccountPassword) + : null)) .build(); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/form/Form.java b/src/main/java/ch/ethz/seb/sebserver/gui/form/Form.java index 3e4a6a57..af133611 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/form/Form.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/form/Form.java @@ -108,6 +108,10 @@ public final class Form implements FormBinding { } } + public String getStaticValue(final String name) { + return this.staticValues.get(name); + } + public void addToGroup(final String groupName, final String fieldName) { if (this.formFields.containsKey(fieldName)) { this.groups.computeIfAbsent(groupName, k -> new HashSet<>()) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/OAuth2AuthorizationContextHolder.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/OAuth2AuthorizationContextHolder.java index 29b160de..6a720e11 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/OAuth2AuthorizationContextHolder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/OAuth2AuthorizationContextHolder.java @@ -212,6 +212,14 @@ public class OAuth2AuthorizationContextHolder implements AuthorizationContextHol return true; } + @Override + public CharSequence getUserPassword() { + if (isLoggedIn()) { + return this.resource.getPassword(); + } + return null; + } + @Override public boolean login(final String username, final CharSequence password) { if (!this.valid || this.isLoggedIn()) { @@ -363,6 +371,5 @@ public class OAuth2AuthorizationContextHolder implements AuthorizationContextHol } } } - } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/SEBServerAuthorizationContext.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/SEBServerAuthorizationContext.java index 7db177c2..292f25b0 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/SEBServerAuthorizationContext.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/SEBServerAuthorizationContext.java @@ -63,4 +63,6 @@ public interface SEBServerAuthorizationContext { * @return the underling RestTemplate to connect and communicate with the SEB Server webservice */ RestTemplate getRestTemplate(); + CharSequence getUserPassword(); + } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/MonitoringProctoringService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/MonitoringProctoringService.java index 131ffda5..491e6b1f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/MonitoringProctoringService.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/MonitoringProctoringService.java @@ -27,7 +27,10 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; import org.springframework.core.io.Resource; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; @@ -47,10 +50,10 @@ import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData; import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom; import ch.ethz.seb.sebserver.gbl.model.session.ScreenProctoringGroup; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gbl.util.Cryptor; import ch.ethz.seb.sebserver.gbl.util.Tuple; import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.gui.GuiServiceInfo; -import ch.ethz.seb.sebserver.gui.ProctoringServlet; import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition; import ch.ethz.seb.sebserver.gui.content.action.ActionPane; import ch.ethz.seb.sebserver.gui.content.monitoring.ProctorRoomConnectionsPopup; @@ -98,16 +101,16 @@ public class MonitoringProctoringService { private final JSONMapper jsonMapper; private final Resource openRoomScriptRes; private final String remoteProctoringEndpoint; - private final String remoteProctoringViewServletEndpoint; + private final Cryptor cryptor; public MonitoringProctoringService( final PageService pageService, final GuiServiceInfo guiServiceInfo, final ProctorRoomConnectionsPopup proctorRoomConnectionsPopup, final JSONMapper jsonMapper, + final Cryptor cryptor, @Value(OPEN_ROOM_SCRIPT_RES) final Resource openRoomScript, - @Value("${sebserver.gui.remote.proctoring.entrypoint:/remote-proctoring}") final String remoteProctoringEndpoint, - @Value("${sebserver.gui.remote.proctoring.api-servler.endpoint:/remote-view-servlet}") final String remoteProctoringViewServletEndpoint) { + @Value("${sebserver.gui.remote.proctoring.entrypoint:/remote-proctoring}") final String remoteProctoringEndpoint) { this.pageService = pageService; this.guiServiceInfo = guiServiceInfo; @@ -115,7 +118,7 @@ public class MonitoringProctoringService { this.jsonMapper = jsonMapper; this.openRoomScriptRes = openRoomScript; this.remoteProctoringEndpoint = remoteProctoringEndpoint; - this.remoteProctoringViewServletEndpoint = remoteProctoringViewServletEndpoint; + this.cryptor = cryptor; } public boolean isTownhallRoomActive(final String examModelId) { @@ -314,34 +317,55 @@ public class MonitoringProctoringService { final ScreenProctoringGroup group, final PageAction _action) { - // TODO make this configurable or static - final String serviceRedirect = settings.spsServiceURL + "/gui-redirect-location"; - final ResponseEntity redirect = new RestTemplate().exchange( - serviceRedirect, - HttpMethod.GET, - null, - String.class); + try { + // Get login Token for user login from SPS service + final RestTemplate restTemplate = new RestTemplate(); + final String serviceRedirect = settings.spsServiceURL + "/gui-redirect-location"; + final ResponseEntity redirect = restTemplate.exchange( + serviceRedirect, + HttpMethod.GET, + null, + String.class); - final String redirectLocation = redirect.getBody(); - final CurrentUser currentUser = this.pageService.getCurrentUser(); + // JWT token request URL + final String jwtTokenURL = settings.spsServiceURL + API.OAUTH_JWTTOKEN_ENDPOINT; - ProctoringGUIService.setCurrentScreenProctoringWindowData( - group.uuid, - redirectLocation, - currentUser.get().username, - "admin"); + // Basic Auth header and content type header + final HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.add( + HttpHeaders.AUTHORIZATION, + Utils.createBasicAuthHeader( + settings.spsAPIKey, + this.cryptor.decrypt(settings.getSpsAPISecret()).getOrThrow())); + httpHeaders.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE); - final UrlLauncher launcher = RWT.getClient().getService(UrlLauncher.class); - final String url = this.guiServiceInfo.getExternalServerURIBuilder().toUriString() - + this.remoteProctoringEndpoint - + this.remoteProctoringViewServletEndpoint - + Constants.SLASH - + Constants.QUERY - + ProctoringServlet.SCREEN_PROCOTRING_FLAG_PARAM - + Constants.EQUALITY_SIGN - + Constants.TRUE_STRING; + // user credential and redirect info for jwt token request in body - form URL encoded format + final CurrentUser currentUser = this.pageService.getCurrentUser(); + final CharSequence userPassword = currentUser + .getAuthorizationContextHolder() + .getAuthorizationContext() + .getUserPassword(); + final String body = "username=" + currentUser.get().username + + "&password=" + userPassword.toString() + + "&redirect=/galleryView/" + group.uuid; - launcher.openURL(url); + // apply jwt token request + final HttpEntity httpEntity = new HttpEntity<>(body, httpHeaders); + final ResponseEntity tokenRequest = restTemplate.exchange( + jwtTokenURL, + HttpMethod.POST, + httpEntity, + String.class); + + // Open SPS Gui redirect URL with login token (jwt token) in new browser tab + final String redirectLocation = redirect.getBody() + "/jwt?token=" + tokenRequest.getBody(); + final UrlLauncher launcher = RWT.getClient().getService(UrlLauncher.class); + launcher.openURL(redirectLocation); + } catch (final Exception e) { + log.error("Failed to open screen proctoring service group gallery view: ", e); + _action.pageContext() + .notifyError(new LocTextKey("Failed to open screen proctoring service group gallery view"), e); + } return _action; } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInfo.java b/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInfo.java index 340cca32..26976b85 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInfo.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInfo.java @@ -30,6 +30,7 @@ import org.springframework.web.util.UriComponentsBuilder; import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; +import ch.ethz.seb.sebserver.gbl.util.Cryptor; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.WebserviceInfoDAO; @Lazy @@ -80,9 +81,12 @@ public class WebserviceInfo { @Value("${sebserver.webservice.api.exam.accessTokenValiditySeconds:43200}") private int examAPITokenValiditySeconds; + private final ScreenProctoringServiceBundle screenProctoringServiceBundle; + public WebserviceInfo( final WebserviceInfoDAO webserviceInfoDAO, - final Environment environment) { + final Environment environment, + final Cryptor cryptor) { this.webserviceInfoDAO = webserviceInfoDAO; this.sebServerVersion = environment.getRequiredProperty(VERSION_KEY); @@ -145,6 +149,24 @@ public class WebserviceInfo { } else { this.lmsExternalAddressAlias = Collections.emptyMap(); } + + final boolean spsBundled = BooleanUtils.toBoolean(environment.getProperty( + "sebserver.feature.seb.screenProctoring.bundled", + Constants.FALSE_STRING)); + if (spsBundled) { + this.screenProctoringServiceBundle = new ScreenProctoringServiceBundle( + environment.getProperty("sebserver.feature.seb.screenProctoring.bundled.url"), + environment.getProperty("sebserver.feature.seb.screenProctoring.bundled.clientId"), + cryptor.encrypt( + environment.getProperty("sebserver.feature.seb.screenProctoring.bundled.clientPassword")) + .getOrThrow(), + environment.getProperty("sebserver.feature.seb.screenProctoring.bundled.sebserveraccount.username"), + cryptor.encrypt(environment + .getProperty("sebserver.feature.seb.screenProctoring.bundled.sebserveraccount.password")) + .getOrThrow()); + } else { + this.screenProctoringServiceBundle = new ScreenProctoringServiceBundle(); + } } public boolean isMaster() { @@ -207,6 +229,10 @@ public class WebserviceInfo { return this.distributedUpdateInterval; } + public ScreenProctoringServiceBundle getScreenProctoringServiceBundle() { + return this.screenProctoringServiceBundle; + } + public String getLocalHostName() { try { return InetAddress.getLocalHost().getHostName(); @@ -300,4 +326,53 @@ public class WebserviceInfo { return builder.toString(); } + public static final class ScreenProctoringServiceBundle { + + public final boolean bundled; + public final String serviceURL; + public final String clientId; + public final CharSequence clientSecret; + public final String apiAccountName; + public final CharSequence apiAccountPassword; + + public ScreenProctoringServiceBundle( + final String serviceURL, + final String clientId, + final CharSequence clientSecret, + final String apiAccountName, + final CharSequence apiAccountPassword) { + + this.bundled = true; + this.serviceURL = serviceURL; + this.clientId = clientId; + this.clientSecret = clientSecret; + this.apiAccountName = apiAccountName; + this.apiAccountPassword = apiAccountPassword; + } + + public ScreenProctoringServiceBundle() { + this.bundled = false; + this.serviceURL = null; + this.clientId = null; + this.clientSecret = null; + this.apiAccountName = null; + this.apiAccountPassword = null; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("ScreenProctoringServiceBundle [bundled="); + builder.append(this.bundled); + builder.append(", serviceURL="); + builder.append(this.serviceURL); + builder.append(", clientId="); + builder.append(this.clientId); + builder.append(", apiAccountName="); + builder.append(this.apiAccountName); + builder.append("]"); + return builder.toString(); + } + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInit.java b/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInit.java index b6f9c046..31723aa5 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInit.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInit.java @@ -22,8 +22,8 @@ import org.springframework.stereotype.Component; import ch.ethz.seb.sebserver.SEBServerInit; import ch.ethz.seb.sebserver.SEBServerInitEvent; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; +import ch.ethz.seb.sebserver.webservice.WebserviceInfo.ScreenProctoringServiceBundle; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.WebserviceInfoDAO; -import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.SEBClientPingServiceFactory; @Component @WebServiceProfile @@ -39,7 +39,6 @@ public class WebserviceInit implements ApplicationListener "); SEBServerInit.INIT_LOGGER.info("----> Working with ping service: {}", - this.sebClientPingServiceFactory.getWorkingServiceType()); + this.environment.getProperty("sebserver.webservice.ping.service.strategy")); SEBServerInit.INIT_LOGGER.info("----> "); SEBServerInit.INIT_LOGGER.info("----> Server address: {}", this.environment.getProperty("server.address")); @@ -153,6 +150,14 @@ public class WebserviceInit implements ApplicationListener admin API refresh token validity: " + this.webserviceInfo.getAdminRefreshTokenValSec() + "s"); SEBServerInit.INIT_LOGGER.info( "----> exam API access token validity: " + this.webserviceInfo.getExamAPITokenValiditySeconds() + "s"); + + final ScreenProctoringServiceBundle spsBundle = this.webserviceInfo.getScreenProctoringServiceBundle(); + SEBServerInit.INIT_LOGGER.info("----> "); + SEBServerInit.INIT_LOGGER.info("----> Screen Proctoring Bundle enabled: {}", spsBundle.bundled); + if (spsBundle.bundled) { + SEBServerInit.INIT_LOGGER.info("------> {}", spsBundle); + } + SEBServerInit.INIT_LOGGER.info("----> "); SEBServerInit.INIT_LOGGER.info("----> Property Override Test: {}", this.webserviceInfo.getTestProperty()); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ProctoringSettingsDAO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ProctoringSettingsDAO.java index 011ccaa5..d20d7b9d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ProctoringSettingsDAO.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ProctoringSettingsDAO.java @@ -21,7 +21,7 @@ public interface ProctoringSettingsDAO { EntityKey entityKey, ProctoringServiceSettings proctoringServiceSettings); - Result getScreenProctoringSettings(EntityKey entityKey); + Result getScreenProctoringSettings(EntityKey entityKeyp); Result storeScreenProctoringSettings( final EntityKey entityKey, diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ProctoringAdminServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ProctoringAdminServiceImpl.java index 240cd90a..7ea720de 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ProctoringAdminServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ProctoringAdminServiceImpl.java @@ -18,7 +18,10 @@ import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringServerType; import ch.ethz.seb.sebserver.gbl.model.exam.ScreenProctoringSettings; 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.webservice.WebserviceInfo; +import ch.ethz.seb.sebserver.webservice.WebserviceInfo.ScreenProctoringServiceBundle; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ProctoringSettingsDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.impl.ProctoringSettingsDAOImpl; import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ProctoringAdminService; @@ -36,17 +39,23 @@ public class ProctoringAdminServiceImpl implements ProctoringAdminService { private final RemoteProctoringServiceFactory remoteProctoringServiceFactory; private final ScreenProctoringService screenProctoringService; private final ExamSessionCacheService examSessionCacheService; + private final ScreenProctoringServiceBundle screenProctoringServiceBundle; + private final Cryptor cryptor; public ProctoringAdminServiceImpl( final ProctoringSettingsDAOImpl proctoringSettingsDAO, final RemoteProctoringServiceFactory remoteProctoringServiceFactory, final ScreenProctoringService screenProctoringService, - final ExamSessionCacheService examSessionCacheService) { + final ExamSessionCacheService examSessionCacheService, + final WebserviceInfo webserviceInfo, + final Cryptor cryptor) { this.proctoringSettingsDAO = proctoringSettingsDAO; this.remoteProctoringServiceFactory = remoteProctoringServiceFactory; this.screenProctoringService = screenProctoringService; this.examSessionCacheService = examSessionCacheService; + this.screenProctoringServiceBundle = webserviceInfo.getScreenProctoringServiceBundle(); + this.cryptor = cryptor; } @Override @@ -91,9 +100,25 @@ public class ProctoringAdminServiceImpl implements ProctoringAdminService { checkType(parentEntityKey); - return this.proctoringSettingsDAO + ScreenProctoringSettings settings = this.proctoringSettingsDAO .getScreenProctoringSettings(parentEntityKey) .getOrThrow(); + + if (this.screenProctoringServiceBundle.bundled) { + settings = new ScreenProctoringSettings( + settings.examId, + settings.enableScreenProctoring, + this.screenProctoringServiceBundle.serviceURL, + this.screenProctoringServiceBundle.clientId, + null, + this.screenProctoringServiceBundle.apiAccountName, + null, + settings.collectingStrategy, + settings.collectingGroupSize, + true); + } + + return settings; }); } @@ -106,17 +131,30 @@ public class ProctoringAdminServiceImpl implements ProctoringAdminService { checkType(parentEntityKey); + ScreenProctoringSettings settings = screenProctoringSettings; + if (this.screenProctoringServiceBundle.bundled) { + settings = new ScreenProctoringSettings( + screenProctoringSettings.examId, + screenProctoringSettings.enableScreenProctoring, + this.screenProctoringServiceBundle.serviceURL, + this.screenProctoringServiceBundle.clientId, + this.cryptor.decrypt(this.screenProctoringServiceBundle.clientSecret).getOrThrow(), + this.screenProctoringServiceBundle.apiAccountName, + this.cryptor.decrypt(this.screenProctoringServiceBundle.apiAccountPassword).getOrThrow(), + screenProctoringSettings.collectingStrategy, + screenProctoringSettings.collectingGroupSize, + true); + } + this.screenProctoringService - .testSettings(screenProctoringSettings) - .flatMap(settings -> this.proctoringSettingsDAO.storeScreenProctoringSettings( - parentEntityKey, - screenProctoringSettings)) + .testSettings(settings) + .flatMap(s -> this.proctoringSettingsDAO.storeScreenProctoringSettings(parentEntityKey, s)) .getOrThrow(); if (parentEntityKey.entityType == EntityType.EXAM) { this.screenProctoringService - .applyScreenProctoingForExam(screenProctoringSettings.examId) + .applyScreenProctoingForExam(settings.examId) .onError(error -> this.proctoringSettingsDAO .disableScreenProctoring(screenProctoringSettings.examId)) .getOrThrow(); @@ -128,7 +166,7 @@ public class ProctoringAdminServiceImpl implements ProctoringAdminService { } } - return screenProctoringSettings; + return settings; }); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingBatchService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingBatchService.java index 804c75e6..bb723318 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingBatchService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingBatchService.java @@ -21,6 +21,7 @@ import org.ehcache.impl.internal.concurrent.ConcurrentHashMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.context.annotation.Lazy; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.stereotype.Component; @@ -33,6 +34,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientPingServic @Lazy @Component @WebServiceProfile +@ConditionalOnExpression("'${sebserver.webservice.ping.service.strategy}'.equals('BATCH')") public class SEBClientPingBatchService implements SEBClientPingService { private static final Logger log = LoggerFactory.getLogger(SEBClientPingBatchService.class); @@ -116,9 +118,9 @@ public class SEBClientPingBatchService implements SEBClientPingService { + this.instructions); this.pings.put(connectionToken, instructionConfirm); // // TODO is this a good idea or is there another better way to deal with instruction confirm synchronization? -// if (instruction != null && instruction.contains("\"instruction-confirm\":\"" + instructionConfirm + "\"")) { -// return null; -// } + if (instruction != null && instruction.contains("\"instruction-confirm\":\"" + instructionConfirm + "\"")) { + return null; + } } else if (!this.pings.containsKey(connectionToken)) { this.pings.put(connectionToken, StringUtils.EMPTY); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingBlockingService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingBlockingService.java index 3d5c451a..471f8928 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingBlockingService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingBlockingService.java @@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; @@ -22,6 +23,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientPingServic @Lazy @Component @WebServiceProfile +@ConditionalOnExpression("'${sebserver.webservice.ping.service.strategy}'.equals('BLOCKING')") public class SEBClientPingBlockingService implements SEBClientPingService { private static final Logger log = LoggerFactory.getLogger(SEBClientPingBlockingService.class); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingServiceFactory.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingServiceFactory.java deleted file mode 100644 index 31cd6ea7..00000000 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingServiceFactory.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2023 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.webservice.servicelayer.session.impl; - -import java.util.Collection; -import java.util.EnumMap; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Lazy; -import org.springframework.stereotype.Component; - -import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; -import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientPingService; - -@Lazy -@Component -@WebServiceProfile -public class SEBClientPingServiceFactory { - - private static final Logger log = LoggerFactory.getLogger(SEBClientPingServiceFactory.class); - - private final EnumMap serviceMapping = - new EnumMap<>(SEBClientPingService.PingServiceType.class); - private final SEBClientPingService.PingServiceType workingServiceType; - - public SEBClientPingServiceFactory( - final Collection serviceBeans, - @Value("${sebserver.webservice.api.exam.session.ping.service.strategy:BLOCKING}") final String serviceType) { - - SEBClientPingService.PingServiceType serviceTypeToSet = SEBClientPingService.PingServiceType.BLOCKING; - try { - serviceTypeToSet = SEBClientPingService.PingServiceType.valueOf(serviceType); - } catch (final Exception e) { - serviceTypeToSet = SEBClientPingService.PingServiceType.BLOCKING; - } - this.workingServiceType = serviceTypeToSet; - - serviceBeans.stream().forEach(service -> this.serviceMapping.putIfAbsent(service.pingServiceType(), service)); - } - - public SEBClientPingService.PingServiceType getWorkingServiceType() { - return this.workingServiceType; - } - - public SEBClientPingService getSEBClientPingService() { - - log.info("Work with SEBClientPingService of type: {}", this.workingServiceType); - - switch (this.workingServiceType) { - case BATCH: { - final SEBClientPingService service = - this.serviceMapping.get(SEBClientPingService.PingServiceType.BATCH); - if (service != null) { - ((SEBClientPingBatchService) service).init(); - return service; - } else { - return this.serviceMapping.get(SEBClientPingService.PingServiceType.BLOCKING); - } - } - default: - return this.serviceMapping.get(SEBClientPingService.PingServiceType.BLOCKING); - } - } - -} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientSessionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientSessionServiceImpl.java index 9d668706..2c34a1be 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientSessionServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientSessionServiceImpl.java @@ -60,7 +60,7 @@ public class SEBClientSessionServiceImpl implements SEBClientSessionService { final InternalClientConnectionDataFactory internalClientConnectionDataFactory, final SecurityKeyService securityKeyService, final SEBClientVersionService sebClientVersionService, - final SEBClientPingServiceFactory sebClientPingServiceFactory) { + final SEBClientPingService sebClientPingService) { this.clientConnectionDAO = clientConnectionDAO; this.examSessionService = examSessionService; @@ -70,7 +70,7 @@ public class SEBClientSessionServiceImpl implements SEBClientSessionService { this.internalClientConnectionDataFactory = internalClientConnectionDataFactory; this.securityKeyService = securityKeyService; this.sebClientVersionService = sebClientVersionService; - this.sebClientPingService = sebClientPingServiceFactory.getSEBClientPingService(); + this.sebClientPingService = sebClientPingService; } @Override diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ScreenProctoringAPIBinding.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ScreenProctoringAPIBinding.java index b041355b..3c57d397 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ScreenProctoringAPIBinding.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ScreenProctoringAPIBinding.java @@ -223,6 +223,10 @@ class ScreenProctoringAPIBinding { if (result.getStatusCode() != HttpStatus.OK) { if (result.getStatusCode().is4xxClientError()) { + log.warn( + "Failed to establish REST connection to: {}. status: {}", + screenProctoringSettings.spsServiceURL, result.getStatusCode()); + throw new FieldValidationException( "serverURL", "screenProctoringSettings:spsServiceURL:url.noAccess"); @@ -302,7 +306,6 @@ class ScreenProctoringAPIBinding { final SPSData spsData = this.getSPSData(exam.id); // re-activate all needed entities on SPS side activation(exam, SPS_API.SEB_ACCESS_ENDPOINT, spsData.spsSEBAccesUUID, true, apiTemplate); - activation(exam, SPS_API.EXAM_ENDPOINT, spsData.spsExamUUID, true, apiTemplate); // mark successfully activated on SPS side this.additionalAttributesDAO.saveAdditionalAttribute( @@ -367,7 +370,7 @@ class ScreenProctoringAPIBinding { final ScreenProctoringServiceOAuthTemplate apiTemplate = this.getAPITemplate(exam.id); final String uri = UriComponentsBuilder - .fromUriString(this.apiTemplate.screenProctoringSettings.spsServiceURL) + .fromUriString(apiTemplate.screenProctoringSettings.spsServiceURL) .path(SPS_API.EXAM_ENDPOINT) .pathSegment(spsData.spsExamUUID) .build() @@ -410,8 +413,8 @@ class ScreenProctoringAPIBinding { } final SPSData spsData = this.getSPSData(exam.id); - activation(exam, SPS_API.EXAM_ENDPOINT, spsData.spsExamUUID, false, this.apiTemplate); - activation(exam, SPS_API.SEB_ACCESS_ENDPOINT, spsData.spsSEBAccesUUID, false, this.apiTemplate); + final ScreenProctoringServiceOAuthTemplate apiTemplate = this.getAPITemplate(exam.id); + activation(exam, SPS_API.SEB_ACCESS_ENDPOINT, spsData.spsSEBAccesUUID, false, apiTemplate); // mark successfully dispose on SPS side this.additionalAttributesDAO.saveAdditionalAttribute( @@ -490,7 +493,7 @@ class ScreenProctoringAPIBinding { final String token = clientConnection.getConnectionToken(); final ScreenProctoringServiceOAuthTemplate apiTemplate = this.getAPITemplate(examId); final String uri = UriComponentsBuilder - .fromUriString(this.apiTemplate.screenProctoringSettings.spsServiceURL) + .fromUriString(apiTemplate.screenProctoringSettings.spsServiceURL) .path(SPS_API.SESSION_ENDPOINT) .build() @@ -567,7 +570,7 @@ class ScreenProctoringAPIBinding { userInfo.roles); final String uri = UriComponentsBuilder - .fromUriString(this.apiTemplate.screenProctoringSettings.spsServiceURL) + .fromUriString(apiTemplate.screenProctoringSettings.spsServiceURL) .path(SPS_API.USERSYNC_SEBSERVER_ENDPOINT) .build() .toUriString(); @@ -603,7 +606,7 @@ class ScreenProctoringAPIBinding { .getOrThrow(); final String uri = UriComponentsBuilder - .fromUriString(this.apiTemplate.screenProctoringSettings.spsServiceURL) + .fromUriString(apiTemplate.screenProctoringSettings.spsServiceURL) .path(SPS_API.ENTIY_PRIVILEGES_ENDPOINT) .build() .toUriString(); @@ -696,7 +699,7 @@ class ScreenProctoringAPIBinding { throws JsonMappingException, JsonProcessingException { final String uri = UriComponentsBuilder - .fromUriString(this.apiTemplate.screenProctoringSettings.spsServiceURL) + .fromUriString(apiTemplate.screenProctoringSettings.spsServiceURL) .path(SPS_API.GROUP_ENDPOINT) .build() .toUriString(); @@ -729,7 +732,7 @@ class ScreenProctoringAPIBinding { try { final String uri = UriComponentsBuilder - .fromUriString(this.apiTemplate.screenProctoringSettings.spsServiceURL) + .fromUriString(apiTemplate.screenProctoringSettings.spsServiceURL) .path(SPS_API.EXAM_ENDPOINT) .build().toUriString(); @@ -773,7 +776,7 @@ class ScreenProctoringAPIBinding { final String description = "This SEB access was auto-generated by SEB Server"; final String uri = UriComponentsBuilder - .fromUriString(this.apiTemplate.screenProctoringSettings.spsServiceURL) + .fromUriString(apiTemplate.screenProctoringSettings.spsServiceURL) .path(SPS_API.SEB_ACCESS_ENDPOINT) .build() .toUriString(); @@ -816,7 +819,7 @@ class ScreenProctoringAPIBinding { try { final String uri = UriComponentsBuilder - .fromUriString(this.apiTemplate.screenProctoringSettings.spsServiceURL) + .fromUriString(apiTemplate.screenProctoringSettings.spsServiceURL) .path(domainPath) .pathSegment(uuid) .pathSegment(activate ? SPS_API.ACTIVE_PATH_SEGMENT : SPS_API.INACTIVE_PATH_SEGMENT) @@ -840,7 +843,7 @@ class ScreenProctoringAPIBinding { try { final String uri = UriComponentsBuilder - .fromUriString(this.apiTemplate.screenProctoringSettings.spsServiceURL) + .fromUriString(apiTemplate.screenProctoringSettings.spsServiceURL) .path(domainPath) .pathSegment(uuid) .build() diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/APIExceptionHandler.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/APIExceptionHandler.java index 7e47c800..2ccf8d5d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/APIExceptionHandler.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/APIExceptionHandler.java @@ -14,6 +14,7 @@ import java.util.List; import java.util.concurrent.CompletionException; import java.util.stream.Collectors; +import org.apache.catalina.connector.ClientAbortException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.Ordered; @@ -281,4 +282,14 @@ public class APIExceptionHandler extends ResponseEntityExceptionHandler { } + @ExceptionHandler(ClientAbortException.class) + public ResponseEntity handleClientAbortException( + final ClientAbortException ex, + final WebRequest request) { + + log.warn("Client aborted: {}", ex.getMessage()); + + return null; + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ControllerConfig.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ControllerConfig.java deleted file mode 100644 index 436cb042..00000000 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ControllerConfig.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2019 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.webservice.weblayer.api; - -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -//@EnableAsync -//@Configuration -//@WebServiceProfile -@Deprecated -public class ControllerConfig implements WebMvcConfigurer { - -// @Override -// public void configureAsyncSupport(final AsyncSupportConfigurer configurer) { -// configurer.setTaskExecutor(threadPoolTaskExecutor()); -// configurer.setDefaultTimeout(30000); -// } -// -// public AsyncTaskExecutor threadPoolTaskExecutor() { -// final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); -// executor.setCorePoolSize(7); -// executor.setMaxPoolSize(42); -// executor.setQueueCapacity(11); -// executor.setThreadNamePrefix("mvc-"); -// executor.initialize(); -// return executor; -// } - -} diff --git a/src/main/resources/config/application-dev-ws.properties b/src/main/resources/config/application-dev-ws.properties index a76d7f65..e483adf8 100644 --- a/src/main/resources/config/application-dev-ws.properties +++ b/src/main/resources/config/application-dev-ws.properties @@ -23,7 +23,7 @@ sebserver.webservice.distributed.updateInterval=1000 sebserver.webservice.distributed.connectionUpdate=2000 sebserver.webservice.clean-db-on-startup=false -# webservice configuration +# webservice setup configuration sebserver.init.adminaccount.gen-on-init=false sebserver.webservice.distributed=true #sebserver.webservice.master.delay.threshold=10000 @@ -31,6 +31,7 @@ sebserver.webservice.http.external.scheme=http sebserver.webservice.http.external.servername=localhost sebserver.webservice.http.external.port=${server.port} sebserver.webservice.http.redirect.gui=/gui +sebserver.webservice.ping.service.strategy=BATCH sebserver.webservice.api.admin.endpoint=/admin-api/v1 @@ -44,8 +45,6 @@ sebserver.webservice.api.exam.time-suffix=0 sebserver.webservice.api.exam.endpoint=/exam-api sebserver.webservice.api.exam.endpoint.discovery=${sebserver.webservice.api.exam.endpoint}/discovery sebserver.webservice.api.exam.endpoint.v1=${sebserver.webservice.api.exam.endpoint}/v1 -sebserver.webservice.api.exam.event-handling-strategy=ASYNC_BATCH_STORE_STRATEGY -sebserver.webservice.api.exam.session.ping.service.strategy=BATCH sebserver.webservice.api.exam.enable-indicator-cache=true sebserver.webservice.api.exam.defaultPingInterval=1000 sebserver.webservice.api.pagination.maxPageSize=500 @@ -62,4 +61,8 @@ springdoc.swagger-ui.enabled=true management.server.port=${server.port} management.endpoints.web.base-path=/management management.endpoints.web.exposure.include=logfile,loggers,jolokia -management.endpoints.web.path-mapping.jolokia=jmx \ No newline at end of file +management.endpoints.web.path-mapping.jolokia=jmx + +sebserver.feature.seb.screenProctoring.bundled.url=localhost:8090 +sebserver.feature.seb.screenProctoring.bundled.clientId=sebserverClient +sebserver.feature.seb.screenProctoring.bundled.sebserveraccount.username=SEBServerAPIAccount \ No newline at end of file diff --git a/src/main/resources/config/application-ws.properties b/src/main/resources/config/application-ws.properties index ef44f5c5..1ca60782 100644 --- a/src/main/resources/config/application-ws.properties +++ b/src/main/resources/config/application-ws.properties @@ -37,7 +37,7 @@ spring.datasource.password=${sebserver.mariadb.password} sebserver.webservice.api.admin.clientSecret=${sebserver.password} sebserver.webservice.internalSecret=${sebserver.password} -### webservice networking +### webservice setup configuration sebserver.webservice.forceMaster=false sebserver.webservice.distributed=false sebserver.webservice.distributed.updateInterval=2000 @@ -45,6 +45,8 @@ sebserver.webservice.http.external.scheme=https sebserver.webservice.http.external.servername= sebserver.webservice.http.external.port= sebserver.webservice.http.redirect.gui=/gui +sebserver.webservice.ping.service.strategy=BLOCKING + ### Open API Documentation springdoc.api-docs.enabled=false @@ -55,6 +57,8 @@ springdoc.swagger-ui.oauth.clientSecret=${sebserver.password} #springdoc.default-consumes-media-type=application/x-www-form-urlencoded springdoc.paths-to-exclude=/exam-api,/exam-api/discovery,/sebserver/error,/sebserver/check,/oauth,/exam-api/v1/* + + ### webservice API sebserver.webservice.api.admin.clientId=guiClient sebserver.webservice.api.admin.endpoint=/admin-api/v1 @@ -72,8 +76,7 @@ sebserver.webservice.api.exam.config.init.prohibitedProcesses=config/initialProh sebserver.webservice.api.exam.endpoint=/exam-api sebserver.webservice.api.exam.endpoint.discovery=${sebserver.webservice.api.exam.endpoint}/discovery sebserver.webservice.api.exam.endpoint.v1=${sebserver.webservice.api.exam.endpoint}/v1 -sebserver.webservice.api.exam.accessTokenValiditySeconds=43200 -sebserver.webservice.api.exam.event-handling-strategy=SINGLE_EVENT_STORE_STRATEGY +sebserver.webservice.api.exam.accessTokenValiditySeconds=43200 sebserver.webservice.api.exam.enable-indicator-cache=true sebserver.webservice.api.pagination.maxPageSize=500 # comma separated list of known possible OpenEdX API access token request endpoints diff --git a/src/main/resources/config/application.properties b/src/main/resources/config/application.properties index 142d4a3a..dcd3c8ea 100644 --- a/src/main/resources/config/application.properties +++ b/src/main/resources/config/application.properties @@ -66,6 +66,10 @@ sebserver.ssl.redirect.html.port=8080 # features sebserver.feature.seb.screenProctoring=false sebserver.feature.seb.screenProctoring.bundled=true +sebserver.feature.seb.screenProctoring.bundled.url=sps-service:8090 sebserver.feature.seb.screenProctoring.bundled.clientId=sebserverClient -sebserver.feature.seb.screenProctoring.bundled.clientPassword=${sebserver.password} +sebserver.feature.seb.screenProctoring.bundled.clientPassword=${sps.sebserver.client.secret} +sebserver.feature.seb.screenProctoring.bundled.sebserveraccount.username=SEBServerAPIAccount +sebserver.feature.seb.screenProctoring.bundled.sebserveraccount.password=${sps.sebserver.password} + sebserver.feature.CollectingRoomStrategy.SEB-GROUP=false