SEBSERV-435 finished up bundle and auto login

This commit is contained in:
anhefti 2023-11-09 13:50:16 +01:00
parent 6e5d5e7710
commit 2af659dd33
23 changed files with 336 additions and 252 deletions

View file

@ -56,6 +56,7 @@ public final class API {
public static final String OAUTH_ENDPOINT = "/oauth"; public static final String OAUTH_ENDPOINT = "/oauth";
public static final String OAUTH_TOKEN_ENDPOINT = OAUTH_ENDPOINT + "/token"; 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 OAUTH_REVOKE_TOKEN_ENDPOINT = OAUTH_ENDPOINT + "/revoke-token";
public static final String CURRENT_USER_PATH_SEGMENT = "/me"; public static final String CURRENT_USER_PATH_SEGMENT = "/me";

View file

@ -34,6 +34,8 @@ public class ScreenProctoringSettings {
public static final String ATTR_SPS_ACCOUNT_ID = "spsAccountId"; public static final String ATTR_SPS_ACCOUNT_ID = "spsAccountId";
public static final String ATTR_SPS_ACCOUNT_PASSWORD = "spsAccountPassword"; public static final String ATTR_SPS_ACCOUNT_PASSWORD = "spsAccountPassword";
public static final String ATTR_SPS_BUNDLED = "bundled";
@JsonProperty(Domain.EXAM.ATTR_ID) @JsonProperty(Domain.EXAM.ATTR_ID)
public final Long examId; public final Long examId;
@ -62,6 +64,9 @@ public class ScreenProctoringSettings {
@JsonProperty(ATTR_COLLECTING_GROUP_SIZE) @JsonProperty(ATTR_COLLECTING_GROUP_SIZE)
public final Integer collectingGroupSize; public final Integer collectingGroupSize;
@JsonProperty(ATTR_SPS_BUNDLED)
public final boolean bundled;
@JsonCreator @JsonCreator
public ScreenProctoringSettings( public ScreenProctoringSettings(
@JsonProperty(Domain.EXAM.ATTR_ID) final Long examId, @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_ID) final String spsAccountId,
@JsonProperty(ATTR_SPS_ACCOUNT_PASSWORD) final CharSequence spsAccountPassword, @JsonProperty(ATTR_SPS_ACCOUNT_PASSWORD) final CharSequence spsAccountPassword,
@JsonProperty(ATTR_COLLECTING_STRATEGY) final CollectingStrategy collectingStrategy, @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.examId = examId;
this.enableScreenProctoring = enableScreenProctoring; this.enableScreenProctoring = enableScreenProctoring;
@ -83,6 +89,30 @@ public class ScreenProctoringSettings {
this.spsAccountPassword = spsAccountPassword; this.spsAccountPassword = spsAccountPassword;
this.collectingStrategy = collectingStrategy; this.collectingStrategy = collectingStrategy;
this.collectingGroupSize = collectingGroupSize; 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) { public ScreenProctoringSettings(final Exam exam) {
@ -108,6 +138,7 @@ public class ScreenProctoringSettings {
this.collectingGroupSize = Integer.parseInt(exam.additionalAttributes.getOrDefault( this.collectingGroupSize = Integer.parseInt(exam.additionalAttributes.getOrDefault(
ATTR_COLLECTING_GROUP_SIZE, ATTR_COLLECTING_GROUP_SIZE,
"-1")); "-1"));
this.bundled = false;
} }
public Long getExamId() { public Long getExamId() {
@ -151,6 +182,10 @@ public class ScreenProctoringSettings {
return Objects.hash(this.examId); return Objects.hash(this.examId);
} }
public boolean isBundled() {
return this.bundled;
}
@Override @Override
public boolean equals(final Object obj) { public boolean equals(final Object obj) {
if (this == obj) if (this == obj)

View file

@ -39,6 +39,7 @@ import java.util.stream.Collectors;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex; import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -919,4 +920,12 @@ public final class Utils {
.replaceAll("[^A-Za-z0-9_]", ""); .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;
}
} }

View file

@ -18,7 +18,6 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSession;
import org.apache.commons.lang3.BooleanUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus; 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.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils; 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.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.AuthorizationContextHolder; 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.remote.webservice.auth.SEBServerAuthorizationContext;
import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService; 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.ProctoringWindowData;
import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService.ScreenProctoringWindowData;
import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringWindowScriptResolver; import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringWindowScriptResolver;
@Component @Component
@GuiProfile @GuiProfile
public class ProctoringServlet extends HttpServlet { public class ProctoringServlet extends HttpServlet {
public static final String SCREEN_PROCOTRING_FLAG_PARAM = "screenproctoring";
private static final long serialVersionUID = 3475978419653411800L; private static final long serialVersionUID = 3475978419653411800L;
private static final Logger log = LoggerFactory.getLogger(ProctoringServlet.class); private static final Logger log = LoggerFactory.getLogger(ProctoringServlet.class);
@ -63,54 +58,12 @@ public class ProctoringServlet extends HttpServlet {
final WebApplicationContext webApplicationContext = WebApplicationContextUtils final WebApplicationContext webApplicationContext = WebApplicationContextUtils
.getRequiredWebApplicationContext(servletContext); .getRequiredWebApplicationContext(servletContext);
UserInfo user; final boolean authenticated = isAuthenticated(httpSession, webApplicationContext);
try { if (!authenticated) {
user = isAuthenticated(httpSession, webApplicationContext);
} catch (final Exception e) {
resp.setStatus(HttpStatus.FORBIDDEN.value()); resp.setStatus(HttpStatus.FORBIDDEN.value());
return; 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("<html>");
// sb.append("<body onload='document.forms[\"form\"].submit()'>");
// sb.append("<form name='form' action='");
// sb.append( "" /* data.loginLocation */).append("' method='post'>");
// sb.append("</input type='hidden' name='username' value='").append("super-admin").append("'>");
// sb.append("</input type='hidden' name='password' type='password' value='").append("admin").append("'>");
// sb.append("</form>");
// sb.append("</body>");
// sb.append("</html>");
resp.getOutputStream().println(sb.toString());
}
private void openRemoteProctoring(
final HttpServletResponse resp,
final HttpSession httpSession) throws IOException {
final ProctoringWindowData proctoringData = final ProctoringWindowData proctoringData =
(ProctoringWindowData) httpSession (ProctoringWindowData) httpSession
.getAttribute(ProctoringGUIService.SESSION_ATTR_PROCTORING_DATA); .getAttribute(ProctoringGUIService.SESSION_ATTR_PROCTORING_DATA);
@ -136,7 +89,7 @@ public class ProctoringServlet extends HttpServlet {
resp.setStatus(HttpServletResponse.SC_OK); resp.setStatus(HttpServletResponse.SC_OK);
} }
private UserInfo isAuthenticated( private boolean isAuthenticated(
final HttpSession httpSession, final HttpSession httpSession,
final WebApplicationContext webApplicationContext) { final WebApplicationContext webApplicationContext) {
@ -144,11 +97,7 @@ public class ProctoringServlet extends HttpServlet {
.getBean(AuthorizationContextHolder.class); .getBean(AuthorizationContextHolder.class);
final SEBServerAuthorizationContext authorizationContext = authorizationContextHolder final SEBServerAuthorizationContext authorizationContext = authorizationContextHolder
.getAuthorizationContext(httpSession); .getAuthorizationContext(httpSession);
if (!authorizationContext.isValid() || !authorizationContext.isLoggedIn()) { return authorizationContext.isValid() && authorizationContext.isLoggedIn();
throw new RuntimeException("No authentication found");
}
return authorizationContext.getLoggedInUser().getOrThrow();
} }
} }

View file

@ -199,7 +199,13 @@ public class ScreenProctoringSettingsPopup {
new ActionEvent(action), new ActionEvent(action),
action.pageContext()); action.pageContext());
return true; 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; return false;
} }
@ -243,11 +249,14 @@ public class ScreenProctoringSettingsPopup {
this.pageContext.getAttribute(PageContext.AttributeKeys.FORCE_READ_ONLY)); this.pageContext.getAttribute(PageContext.AttributeKeys.FORCE_READ_ONLY));
final FormHandle<Entity> form = this.pageService.formBuilder(formContext) final FormHandle<Entity> form = this.pageService.formBuilder(formContext)
.putStaticValueIf(
() -> settings.bundled,
ScreenProctoringSettings.ATTR_SPS_BUNDLED,
Constants.TRUE_STRING)
.withDefaultSpanInput(5) .withDefaultSpanInput(5)
.withEmptyCellSeparation(true) .withEmptyCellSeparation(true)
.withDefaultSpanEmptyCell(1) .withDefaultSpanEmptyCell(1)
.readonly(isReadonly) .readonly(isReadonly)
.addField(FormBuilder.text( .addField(FormBuilder.text(
"Info", "Info",
FORM_INFO_TITLE, FORM_INFO_TITLE,
@ -265,33 +274,39 @@ public class ScreenProctoringSettingsPopup {
ScreenProctoringSettings.ATTR_SPS_SERVICE_URL, ScreenProctoringSettings.ATTR_SPS_SERVICE_URL,
FORM_URL, FORM_URL,
settings.spsServiceURL) settings.spsServiceURL)
.mandatory()) .mandatory()
.readonly(settings.bundled))
.addField(FormBuilder.text( .addField(FormBuilder.text(
ScreenProctoringSettings.ATTR_SPS_API_KEY, ScreenProctoringSettings.ATTR_SPS_API_KEY,
FORM_APPKEY_SPS, FORM_APPKEY_SPS,
settings.spsAPIKey)) settings.spsAPIKey)
.readonly(settings.bundled))
.withEmptyCellSeparation(false) .withEmptyCellSeparation(false)
.addField(FormBuilder.password( .addFieldIf(
ScreenProctoringSettings.ATTR_SPS_API_SECRET, () -> !settings.bundled,
FORM_APPSECRET_SPS, () -> FormBuilder.password(
(settings.spsAPISecret != null) ScreenProctoringSettings.ATTR_SPS_API_SECRET,
? String.valueOf(settings.spsAPISecret) FORM_APPSECRET_SPS,
: null)) (settings.spsAPISecret != null)
? String.valueOf(settings.spsAPISecret)
: null))
.addField(FormBuilder.text( .addField(FormBuilder.text(
ScreenProctoringSettings.ATTR_SPS_ACCOUNT_ID, ScreenProctoringSettings.ATTR_SPS_ACCOUNT_ID,
FORM_ACCOUNT_ID_SPS, FORM_ACCOUNT_ID_SPS,
settings.spsAccountId)) settings.spsAccountId)
.readonly(settings.bundled))
.withEmptyCellSeparation(false) .withEmptyCellSeparation(false)
.addFieldIf(
.addField(FormBuilder.password( () -> !settings.bundled,
ScreenProctoringSettings.ATTR_SPS_ACCOUNT_PASSWORD, () -> FormBuilder.password(
FORM_ACCOUNT_SECRET_SPS, ScreenProctoringSettings.ATTR_SPS_ACCOUNT_PASSWORD,
(settings.spsAccountPassword != null) FORM_ACCOUNT_SECRET_SPS,
? String.valueOf(settings.spsAccountPassword) (settings.spsAccountPassword != null)
: null)) ? String.valueOf(settings.spsAccountPassword)
: null))
.build(); .build();

View file

@ -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) { public void addToGroup(final String groupName, final String fieldName) {
if (this.formFields.containsKey(fieldName)) { if (this.formFields.containsKey(fieldName)) {
this.groups.computeIfAbsent(groupName, k -> new HashSet<>()) this.groups.computeIfAbsent(groupName, k -> new HashSet<>())

View file

@ -212,6 +212,14 @@ public class OAuth2AuthorizationContextHolder implements AuthorizationContextHol
return true; return true;
} }
@Override
public CharSequence getUserPassword() {
if (isLoggedIn()) {
return this.resource.getPassword();
}
return null;
}
@Override @Override
public boolean login(final String username, final CharSequence password) { public boolean login(final String username, final CharSequence password) {
if (!this.valid || this.isLoggedIn()) { if (!this.valid || this.isLoggedIn()) {
@ -363,6 +371,5 @@ public class OAuth2AuthorizationContextHolder implements AuthorizationContextHol
} }
} }
} }
} }
} }

View file

@ -63,4 +63,6 @@ public interface SEBServerAuthorizationContext {
* @return the underling RestTemplate to connect and communicate with the SEB Server webservice */ * @return the underling RestTemplate to connect and communicate with the SEB Server webservice */
RestTemplate getRestTemplate(); RestTemplate getRestTemplate();
CharSequence getUserPassword();
} }

View file

@ -27,7 +27,10 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.core.io.Resource; 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.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate; 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.RemoteProctoringRoom;
import ch.ethz.seb.sebserver.gbl.model.session.ScreenProctoringGroup; import ch.ethz.seb.sebserver.gbl.model.session.ScreenProctoringGroup;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; 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.Tuple;
import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.gui.GuiServiceInfo; 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.ActionDefinition;
import ch.ethz.seb.sebserver.gui.content.action.ActionPane; import ch.ethz.seb.sebserver.gui.content.action.ActionPane;
import ch.ethz.seb.sebserver.gui.content.monitoring.ProctorRoomConnectionsPopup; import ch.ethz.seb.sebserver.gui.content.monitoring.ProctorRoomConnectionsPopup;
@ -98,16 +101,16 @@ public class MonitoringProctoringService {
private final JSONMapper jsonMapper; private final JSONMapper jsonMapper;
private final Resource openRoomScriptRes; private final Resource openRoomScriptRes;
private final String remoteProctoringEndpoint; private final String remoteProctoringEndpoint;
private final String remoteProctoringViewServletEndpoint; private final Cryptor cryptor;
public MonitoringProctoringService( public MonitoringProctoringService(
final PageService pageService, final PageService pageService,
final GuiServiceInfo guiServiceInfo, final GuiServiceInfo guiServiceInfo,
final ProctorRoomConnectionsPopup proctorRoomConnectionsPopup, final ProctorRoomConnectionsPopup proctorRoomConnectionsPopup,
final JSONMapper jsonMapper, final JSONMapper jsonMapper,
final Cryptor cryptor,
@Value(OPEN_ROOM_SCRIPT_RES) final Resource openRoomScript, @Value(OPEN_ROOM_SCRIPT_RES) final Resource openRoomScript,
@Value("${sebserver.gui.remote.proctoring.entrypoint:/remote-proctoring}") final String remoteProctoringEndpoint, @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) {
this.pageService = pageService; this.pageService = pageService;
this.guiServiceInfo = guiServiceInfo; this.guiServiceInfo = guiServiceInfo;
@ -115,7 +118,7 @@ public class MonitoringProctoringService {
this.jsonMapper = jsonMapper; this.jsonMapper = jsonMapper;
this.openRoomScriptRes = openRoomScript; this.openRoomScriptRes = openRoomScript;
this.remoteProctoringEndpoint = remoteProctoringEndpoint; this.remoteProctoringEndpoint = remoteProctoringEndpoint;
this.remoteProctoringViewServletEndpoint = remoteProctoringViewServletEndpoint; this.cryptor = cryptor;
} }
public boolean isTownhallRoomActive(final String examModelId) { public boolean isTownhallRoomActive(final String examModelId) {
@ -314,34 +317,55 @@ public class MonitoringProctoringService {
final ScreenProctoringGroup group, final ScreenProctoringGroup group,
final PageAction _action) { final PageAction _action) {
// TODO make this configurable or static try {
final String serviceRedirect = settings.spsServiceURL + "/gui-redirect-location"; // Get login Token for user login from SPS service
final ResponseEntity<String> redirect = new RestTemplate().exchange( final RestTemplate restTemplate = new RestTemplate();
serviceRedirect, final String serviceRedirect = settings.spsServiceURL + "/gui-redirect-location";
HttpMethod.GET, final ResponseEntity<String> redirect = restTemplate.exchange(
null, serviceRedirect,
String.class); HttpMethod.GET,
null,
String.class);
final String redirectLocation = redirect.getBody(); // JWT token request URL
final CurrentUser currentUser = this.pageService.getCurrentUser(); final String jwtTokenURL = settings.spsServiceURL + API.OAUTH_JWTTOKEN_ENDPOINT;
ProctoringGUIService.setCurrentScreenProctoringWindowData( // Basic Auth header and content type header
group.uuid, final HttpHeaders httpHeaders = new HttpHeaders();
redirectLocation, httpHeaders.add(
currentUser.get().username, HttpHeaders.AUTHORIZATION,
"admin"); 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); // user credential and redirect info for jwt token request in body - form URL encoded format
final String url = this.guiServiceInfo.getExternalServerURIBuilder().toUriString() final CurrentUser currentUser = this.pageService.getCurrentUser();
+ this.remoteProctoringEndpoint final CharSequence userPassword = currentUser
+ this.remoteProctoringViewServletEndpoint .getAuthorizationContextHolder()
+ Constants.SLASH .getAuthorizationContext()
+ Constants.QUERY .getUserPassword();
+ ProctoringServlet.SCREEN_PROCOTRING_FLAG_PARAM final String body = "username=" + currentUser.get().username
+ Constants.EQUALITY_SIGN + "&password=" + userPassword.toString()
+ Constants.TRUE_STRING; + "&redirect=/galleryView/" + group.uuid;
launcher.openURL(url); // apply jwt token request
final HttpEntity<String> httpEntity = new HttpEntity<>(body, httpHeaders);
final ResponseEntity<String> 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; return _action;
} }

View file

@ -30,6 +30,7 @@ import org.springframework.web.util.UriComponentsBuilder;
import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; 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; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.WebserviceInfoDAO;
@Lazy @Lazy
@ -80,9 +81,12 @@ public class WebserviceInfo {
@Value("${sebserver.webservice.api.exam.accessTokenValiditySeconds:43200}") @Value("${sebserver.webservice.api.exam.accessTokenValiditySeconds:43200}")
private int examAPITokenValiditySeconds; private int examAPITokenValiditySeconds;
private final ScreenProctoringServiceBundle screenProctoringServiceBundle;
public WebserviceInfo( public WebserviceInfo(
final WebserviceInfoDAO webserviceInfoDAO, final WebserviceInfoDAO webserviceInfoDAO,
final Environment environment) { final Environment environment,
final Cryptor cryptor) {
this.webserviceInfoDAO = webserviceInfoDAO; this.webserviceInfoDAO = webserviceInfoDAO;
this.sebServerVersion = environment.getRequiredProperty(VERSION_KEY); this.sebServerVersion = environment.getRequiredProperty(VERSION_KEY);
@ -145,6 +149,24 @@ public class WebserviceInfo {
} else { } else {
this.lmsExternalAddressAlias = Collections.emptyMap(); 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() { public boolean isMaster() {
@ -207,6 +229,10 @@ public class WebserviceInfo {
return this.distributedUpdateInterval; return this.distributedUpdateInterval;
} }
public ScreenProctoringServiceBundle getScreenProctoringServiceBundle() {
return this.screenProctoringServiceBundle;
}
public String getLocalHostName() { public String getLocalHostName() {
try { try {
return InetAddress.getLocalHost().getHostName(); return InetAddress.getLocalHost().getHostName();
@ -300,4 +326,53 @@ public class WebserviceInfo {
return builder.toString(); 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();
}
}
} }

View file

@ -22,8 +22,8 @@ import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.SEBServerInit; import ch.ethz.seb.sebserver.SEBServerInit;
import ch.ethz.seb.sebserver.SEBServerInitEvent; import ch.ethz.seb.sebserver.SEBServerInitEvent;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; 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.dao.WebserviceInfoDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.SEBClientPingServiceFactory;
@Component @Component
@WebServiceProfile @WebServiceProfile
@ -39,7 +39,6 @@ public class WebserviceInit implements ApplicationListener<ApplicationReadyEvent
private final WebserviceInfoDAO webserviceInfoDAO; private final WebserviceInfoDAO webserviceInfoDAO;
private final DBIntegrityChecker dbIntegrityChecker; private final DBIntegrityChecker dbIntegrityChecker;
private final SEBServerMigrationStrategy sebServerMigrationStrategy; private final SEBServerMigrationStrategy sebServerMigrationStrategy;
private final SEBClientPingServiceFactory sebClientPingServiceFactory;
protected WebserviceInit( protected WebserviceInit(
final SEBServerInit sebServerInit, final SEBServerInit sebServerInit,
@ -49,8 +48,7 @@ public class WebserviceInit implements ApplicationListener<ApplicationReadyEvent
final WebserviceInfoDAO webserviceInfoDAO, final WebserviceInfoDAO webserviceInfoDAO,
final DBIntegrityChecker dbIntegrityChecker, final DBIntegrityChecker dbIntegrityChecker,
final ApplicationContext applicationContext, final ApplicationContext applicationContext,
final SEBServerMigrationStrategy sebServerMigrationStrategy, final SEBServerMigrationStrategy sebServerMigrationStrategy) {
final SEBClientPingServiceFactory sebClientPingServiceFactory) {
this.applicationContext = applicationContext; this.applicationContext = applicationContext;
this.sebServerInit = sebServerInit; this.sebServerInit = sebServerInit;
@ -61,7 +59,6 @@ public class WebserviceInit implements ApplicationListener<ApplicationReadyEvent
this.webserviceInfoDAO = webserviceInfoDAO; this.webserviceInfoDAO = webserviceInfoDAO;
this.dbIntegrityChecker = dbIntegrityChecker; this.dbIntegrityChecker = dbIntegrityChecker;
this.sebServerMigrationStrategy = sebServerMigrationStrategy; this.sebServerMigrationStrategy = sebServerMigrationStrategy;
this.sebClientPingServiceFactory = sebClientPingServiceFactory;
} }
public ApplicationContext getApplicationContext() { public ApplicationContext getApplicationContext() {
@ -126,7 +123,7 @@ public class WebserviceInit implements ApplicationListener<ApplicationReadyEvent
SEBServerInit.INIT_LOGGER.info("----> "); SEBServerInit.INIT_LOGGER.info("----> ");
SEBServerInit.INIT_LOGGER.info("----> Working with ping service: {}", 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("----> ");
SEBServerInit.INIT_LOGGER.info("----> Server address: {}", this.environment.getProperty("server.address")); SEBServerInit.INIT_LOGGER.info("----> Server address: {}", this.environment.getProperty("server.address"));
@ -153,6 +150,14 @@ public class WebserviceInit implements ApplicationListener<ApplicationReadyEvent
"----> admin API refresh token validity: " + this.webserviceInfo.getAdminRefreshTokenValSec() + "s"); "----> admin API refresh token validity: " + this.webserviceInfo.getAdminRefreshTokenValSec() + "s");
SEBServerInit.INIT_LOGGER.info( SEBServerInit.INIT_LOGGER.info(
"----> exam API access token validity: " + this.webserviceInfo.getExamAPITokenValiditySeconds() + "s"); "----> 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("----> ");
SEBServerInit.INIT_LOGGER.info("----> Property Override Test: {}", this.webserviceInfo.getTestProperty()); SEBServerInit.INIT_LOGGER.info("----> Property Override Test: {}", this.webserviceInfo.getTestProperty());

View file

@ -21,7 +21,7 @@ public interface ProctoringSettingsDAO {
EntityKey entityKey, EntityKey entityKey,
ProctoringServiceSettings proctoringServiceSettings); ProctoringServiceSettings proctoringServiceSettings);
Result<ScreenProctoringSettings> getScreenProctoringSettings(EntityKey entityKey); Result<ScreenProctoringSettings> getScreenProctoringSettings(EntityKey entityKeyp);
Result<ScreenProctoringSettings> storeScreenProctoringSettings( Result<ScreenProctoringSettings> storeScreenProctoringSettings(
final EntityKey entityKey, final EntityKey entityKey,

View file

@ -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.ProctoringServiceSettings.ProctoringServerType;
import ch.ethz.seb.sebserver.gbl.model.exam.ScreenProctoringSettings; import ch.ethz.seb.sebserver.gbl.model.exam.ScreenProctoringSettings;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; 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.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.ProctoringSettingsDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.impl.ProctoringSettingsDAOImpl; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.impl.ProctoringSettingsDAOImpl;
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ProctoringAdminService; import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ProctoringAdminService;
@ -36,17 +39,23 @@ public class ProctoringAdminServiceImpl implements ProctoringAdminService {
private final RemoteProctoringServiceFactory remoteProctoringServiceFactory; private final RemoteProctoringServiceFactory remoteProctoringServiceFactory;
private final ScreenProctoringService screenProctoringService; private final ScreenProctoringService screenProctoringService;
private final ExamSessionCacheService examSessionCacheService; private final ExamSessionCacheService examSessionCacheService;
private final ScreenProctoringServiceBundle screenProctoringServiceBundle;
private final Cryptor cryptor;
public ProctoringAdminServiceImpl( public ProctoringAdminServiceImpl(
final ProctoringSettingsDAOImpl proctoringSettingsDAO, final ProctoringSettingsDAOImpl proctoringSettingsDAO,
final RemoteProctoringServiceFactory remoteProctoringServiceFactory, final RemoteProctoringServiceFactory remoteProctoringServiceFactory,
final ScreenProctoringService screenProctoringService, final ScreenProctoringService screenProctoringService,
final ExamSessionCacheService examSessionCacheService) { final ExamSessionCacheService examSessionCacheService,
final WebserviceInfo webserviceInfo,
final Cryptor cryptor) {
this.proctoringSettingsDAO = proctoringSettingsDAO; this.proctoringSettingsDAO = proctoringSettingsDAO;
this.remoteProctoringServiceFactory = remoteProctoringServiceFactory; this.remoteProctoringServiceFactory = remoteProctoringServiceFactory;
this.screenProctoringService = screenProctoringService; this.screenProctoringService = screenProctoringService;
this.examSessionCacheService = examSessionCacheService; this.examSessionCacheService = examSessionCacheService;
this.screenProctoringServiceBundle = webserviceInfo.getScreenProctoringServiceBundle();
this.cryptor = cryptor;
} }
@Override @Override
@ -91,9 +100,25 @@ public class ProctoringAdminServiceImpl implements ProctoringAdminService {
checkType(parentEntityKey); checkType(parentEntityKey);
return this.proctoringSettingsDAO ScreenProctoringSettings settings = this.proctoringSettingsDAO
.getScreenProctoringSettings(parentEntityKey) .getScreenProctoringSettings(parentEntityKey)
.getOrThrow(); .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); 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 this.screenProctoringService
.testSettings(screenProctoringSettings) .testSettings(settings)
.flatMap(settings -> this.proctoringSettingsDAO.storeScreenProctoringSettings( .flatMap(s -> this.proctoringSettingsDAO.storeScreenProctoringSettings(parentEntityKey, s))
parentEntityKey,
screenProctoringSettings))
.getOrThrow(); .getOrThrow();
if (parentEntityKey.entityType == EntityType.EXAM) { if (parentEntityKey.entityType == EntityType.EXAM) {
this.screenProctoringService this.screenProctoringService
.applyScreenProctoingForExam(screenProctoringSettings.examId) .applyScreenProctoingForExam(settings.examId)
.onError(error -> this.proctoringSettingsDAO .onError(error -> this.proctoringSettingsDAO
.disableScreenProctoring(screenProctoringSettings.examId)) .disableScreenProctoring(screenProctoringSettings.examId))
.getOrThrow(); .getOrThrow();
@ -128,7 +166,7 @@ public class ProctoringAdminServiceImpl implements ProctoringAdminService {
} }
} }
return screenProctoringSettings; return settings;
}); });
} }

View file

@ -21,6 +21,7 @@ import org.ehcache.impl.internal.concurrent.ConcurrentHashMap;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -33,6 +34,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientPingServic
@Lazy @Lazy
@Component @Component
@WebServiceProfile @WebServiceProfile
@ConditionalOnExpression("'${sebserver.webservice.ping.service.strategy}'.equals('BATCH')")
public class SEBClientPingBatchService implements SEBClientPingService { public class SEBClientPingBatchService implements SEBClientPingService {
private static final Logger log = LoggerFactory.getLogger(SEBClientPingBatchService.class); private static final Logger log = LoggerFactory.getLogger(SEBClientPingBatchService.class);
@ -116,9 +118,9 @@ public class SEBClientPingBatchService implements SEBClientPingService {
+ this.instructions); + this.instructions);
this.pings.put(connectionToken, instructionConfirm); this.pings.put(connectionToken, instructionConfirm);
// // TODO is this a good idea or is there another better way to deal with instruction confirm synchronization? // // 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 + "\"")) { if (instruction != null && instruction.contains("\"instruction-confirm\":\"" + instructionConfirm + "\"")) {
// return null; return null;
// } }
} else if (!this.pings.containsKey(connectionToken)) { } else if (!this.pings.containsKey(connectionToken)) {
this.pings.put(connectionToken, StringUtils.EMPTY); this.pings.put(connectionToken, StringUtils.EMPTY);
} }

View file

@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -22,6 +23,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientPingServic
@Lazy @Lazy
@Component @Component
@WebServiceProfile @WebServiceProfile
@ConditionalOnExpression("'${sebserver.webservice.ping.service.strategy}'.equals('BLOCKING')")
public class SEBClientPingBlockingService implements SEBClientPingService { public class SEBClientPingBlockingService implements SEBClientPingService {
private static final Logger log = LoggerFactory.getLogger(SEBClientPingBlockingService.class); private static final Logger log = LoggerFactory.getLogger(SEBClientPingBlockingService.class);

View file

@ -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<SEBClientPingService.PingServiceType, SEBClientPingService> serviceMapping =
new EnumMap<>(SEBClientPingService.PingServiceType.class);
private final SEBClientPingService.PingServiceType workingServiceType;
public SEBClientPingServiceFactory(
final Collection<SEBClientPingService> 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);
}
}
}

View file

@ -60,7 +60,7 @@ public class SEBClientSessionServiceImpl implements SEBClientSessionService {
final InternalClientConnectionDataFactory internalClientConnectionDataFactory, final InternalClientConnectionDataFactory internalClientConnectionDataFactory,
final SecurityKeyService securityKeyService, final SecurityKeyService securityKeyService,
final SEBClientVersionService sebClientVersionService, final SEBClientVersionService sebClientVersionService,
final SEBClientPingServiceFactory sebClientPingServiceFactory) { final SEBClientPingService sebClientPingService) {
this.clientConnectionDAO = clientConnectionDAO; this.clientConnectionDAO = clientConnectionDAO;
this.examSessionService = examSessionService; this.examSessionService = examSessionService;
@ -70,7 +70,7 @@ public class SEBClientSessionServiceImpl implements SEBClientSessionService {
this.internalClientConnectionDataFactory = internalClientConnectionDataFactory; this.internalClientConnectionDataFactory = internalClientConnectionDataFactory;
this.securityKeyService = securityKeyService; this.securityKeyService = securityKeyService;
this.sebClientVersionService = sebClientVersionService; this.sebClientVersionService = sebClientVersionService;
this.sebClientPingService = sebClientPingServiceFactory.getSEBClientPingService(); this.sebClientPingService = sebClientPingService;
} }
@Override @Override

View file

@ -223,6 +223,10 @@ class ScreenProctoringAPIBinding {
if (result.getStatusCode() != HttpStatus.OK) { if (result.getStatusCode() != HttpStatus.OK) {
if (result.getStatusCode().is4xxClientError()) { if (result.getStatusCode().is4xxClientError()) {
log.warn(
"Failed to establish REST connection to: {}. status: {}",
screenProctoringSettings.spsServiceURL, result.getStatusCode());
throw new FieldValidationException( throw new FieldValidationException(
"serverURL", "serverURL",
"screenProctoringSettings:spsServiceURL:url.noAccess"); "screenProctoringSettings:spsServiceURL:url.noAccess");
@ -302,7 +306,6 @@ class ScreenProctoringAPIBinding {
final SPSData spsData = this.getSPSData(exam.id); final SPSData spsData = this.getSPSData(exam.id);
// re-activate all needed entities on SPS side // re-activate all needed entities on SPS side
activation(exam, SPS_API.SEB_ACCESS_ENDPOINT, spsData.spsSEBAccesUUID, true, apiTemplate); 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 // mark successfully activated on SPS side
this.additionalAttributesDAO.saveAdditionalAttribute( this.additionalAttributesDAO.saveAdditionalAttribute(
@ -367,7 +370,7 @@ class ScreenProctoringAPIBinding {
final ScreenProctoringServiceOAuthTemplate apiTemplate = this.getAPITemplate(exam.id); final ScreenProctoringServiceOAuthTemplate apiTemplate = this.getAPITemplate(exam.id);
final String uri = UriComponentsBuilder final String uri = UriComponentsBuilder
.fromUriString(this.apiTemplate.screenProctoringSettings.spsServiceURL) .fromUriString(apiTemplate.screenProctoringSettings.spsServiceURL)
.path(SPS_API.EXAM_ENDPOINT) .path(SPS_API.EXAM_ENDPOINT)
.pathSegment(spsData.spsExamUUID) .pathSegment(spsData.spsExamUUID)
.build() .build()
@ -410,8 +413,8 @@ class ScreenProctoringAPIBinding {
} }
final SPSData spsData = this.getSPSData(exam.id); final SPSData spsData = this.getSPSData(exam.id);
activation(exam, SPS_API.EXAM_ENDPOINT, spsData.spsExamUUID, false, this.apiTemplate); final ScreenProctoringServiceOAuthTemplate apiTemplate = this.getAPITemplate(exam.id);
activation(exam, SPS_API.SEB_ACCESS_ENDPOINT, spsData.spsSEBAccesUUID, false, this.apiTemplate); activation(exam, SPS_API.SEB_ACCESS_ENDPOINT, spsData.spsSEBAccesUUID, false, apiTemplate);
// mark successfully dispose on SPS side // mark successfully dispose on SPS side
this.additionalAttributesDAO.saveAdditionalAttribute( this.additionalAttributesDAO.saveAdditionalAttribute(
@ -490,7 +493,7 @@ class ScreenProctoringAPIBinding {
final String token = clientConnection.getConnectionToken(); final String token = clientConnection.getConnectionToken();
final ScreenProctoringServiceOAuthTemplate apiTemplate = this.getAPITemplate(examId); final ScreenProctoringServiceOAuthTemplate apiTemplate = this.getAPITemplate(examId);
final String uri = UriComponentsBuilder final String uri = UriComponentsBuilder
.fromUriString(this.apiTemplate.screenProctoringSettings.spsServiceURL) .fromUriString(apiTemplate.screenProctoringSettings.spsServiceURL)
.path(SPS_API.SESSION_ENDPOINT) .path(SPS_API.SESSION_ENDPOINT)
.build() .build()
@ -567,7 +570,7 @@ class ScreenProctoringAPIBinding {
userInfo.roles); userInfo.roles);
final String uri = UriComponentsBuilder final String uri = UriComponentsBuilder
.fromUriString(this.apiTemplate.screenProctoringSettings.spsServiceURL) .fromUriString(apiTemplate.screenProctoringSettings.spsServiceURL)
.path(SPS_API.USERSYNC_SEBSERVER_ENDPOINT) .path(SPS_API.USERSYNC_SEBSERVER_ENDPOINT)
.build() .build()
.toUriString(); .toUriString();
@ -603,7 +606,7 @@ class ScreenProctoringAPIBinding {
.getOrThrow(); .getOrThrow();
final String uri = UriComponentsBuilder final String uri = UriComponentsBuilder
.fromUriString(this.apiTemplate.screenProctoringSettings.spsServiceURL) .fromUriString(apiTemplate.screenProctoringSettings.spsServiceURL)
.path(SPS_API.ENTIY_PRIVILEGES_ENDPOINT) .path(SPS_API.ENTIY_PRIVILEGES_ENDPOINT)
.build() .build()
.toUriString(); .toUriString();
@ -696,7 +699,7 @@ class ScreenProctoringAPIBinding {
throws JsonMappingException, JsonProcessingException { throws JsonMappingException, JsonProcessingException {
final String uri = UriComponentsBuilder final String uri = UriComponentsBuilder
.fromUriString(this.apiTemplate.screenProctoringSettings.spsServiceURL) .fromUriString(apiTemplate.screenProctoringSettings.spsServiceURL)
.path(SPS_API.GROUP_ENDPOINT) .path(SPS_API.GROUP_ENDPOINT)
.build() .build()
.toUriString(); .toUriString();
@ -729,7 +732,7 @@ class ScreenProctoringAPIBinding {
try { try {
final String uri = UriComponentsBuilder final String uri = UriComponentsBuilder
.fromUriString(this.apiTemplate.screenProctoringSettings.spsServiceURL) .fromUriString(apiTemplate.screenProctoringSettings.spsServiceURL)
.path(SPS_API.EXAM_ENDPOINT) .path(SPS_API.EXAM_ENDPOINT)
.build().toUriString(); .build().toUriString();
@ -773,7 +776,7 @@ class ScreenProctoringAPIBinding {
final String description = "This SEB access was auto-generated by SEB Server"; final String description = "This SEB access was auto-generated by SEB Server";
final String uri = UriComponentsBuilder final String uri = UriComponentsBuilder
.fromUriString(this.apiTemplate.screenProctoringSettings.spsServiceURL) .fromUriString(apiTemplate.screenProctoringSettings.spsServiceURL)
.path(SPS_API.SEB_ACCESS_ENDPOINT) .path(SPS_API.SEB_ACCESS_ENDPOINT)
.build() .build()
.toUriString(); .toUriString();
@ -816,7 +819,7 @@ class ScreenProctoringAPIBinding {
try { try {
final String uri = UriComponentsBuilder final String uri = UriComponentsBuilder
.fromUriString(this.apiTemplate.screenProctoringSettings.spsServiceURL) .fromUriString(apiTemplate.screenProctoringSettings.spsServiceURL)
.path(domainPath) .path(domainPath)
.pathSegment(uuid) .pathSegment(uuid)
.pathSegment(activate ? SPS_API.ACTIVE_PATH_SEGMENT : SPS_API.INACTIVE_PATH_SEGMENT) .pathSegment(activate ? SPS_API.ACTIVE_PATH_SEGMENT : SPS_API.INACTIVE_PATH_SEGMENT)
@ -840,7 +843,7 @@ class ScreenProctoringAPIBinding {
try { try {
final String uri = UriComponentsBuilder final String uri = UriComponentsBuilder
.fromUriString(this.apiTemplate.screenProctoringSettings.spsServiceURL) .fromUriString(apiTemplate.screenProctoringSettings.spsServiceURL)
.path(domainPath) .path(domainPath)
.pathSegment(uuid) .pathSegment(uuid)
.build() .build()

View file

@ -14,6 +14,7 @@ import java.util.List;
import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionException;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.apache.catalina.connector.ClientAbortException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
@ -281,4 +282,14 @@ public class APIExceptionHandler extends ResponseEntityExceptionHandler {
} }
@ExceptionHandler(ClientAbortException.class)
public ResponseEntity<Object> handleClientAbortException(
final ClientAbortException ex,
final WebRequest request) {
log.warn("Client aborted: {}", ex.getMessage());
return null;
}
} }

View file

@ -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;
// }
}

View file

@ -23,7 +23,7 @@ sebserver.webservice.distributed.updateInterval=1000
sebserver.webservice.distributed.connectionUpdate=2000 sebserver.webservice.distributed.connectionUpdate=2000
sebserver.webservice.clean-db-on-startup=false sebserver.webservice.clean-db-on-startup=false
# webservice configuration # webservice setup configuration
sebserver.init.adminaccount.gen-on-init=false sebserver.init.adminaccount.gen-on-init=false
sebserver.webservice.distributed=true sebserver.webservice.distributed=true
#sebserver.webservice.master.delay.threshold=10000 #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.servername=localhost
sebserver.webservice.http.external.port=${server.port} sebserver.webservice.http.external.port=${server.port}
sebserver.webservice.http.redirect.gui=/gui sebserver.webservice.http.redirect.gui=/gui
sebserver.webservice.ping.service.strategy=BATCH
sebserver.webservice.api.admin.endpoint=/admin-api/v1 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=/exam-api
sebserver.webservice.api.exam.endpoint.discovery=${sebserver.webservice.api.exam.endpoint}/discovery 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.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.enable-indicator-cache=true
sebserver.webservice.api.exam.defaultPingInterval=1000 sebserver.webservice.api.exam.defaultPingInterval=1000
sebserver.webservice.api.pagination.maxPageSize=500 sebserver.webservice.api.pagination.maxPageSize=500
@ -62,4 +61,8 @@ springdoc.swagger-ui.enabled=true
management.server.port=${server.port} management.server.port=${server.port}
management.endpoints.web.base-path=/management management.endpoints.web.base-path=/management
management.endpoints.web.exposure.include=logfile,loggers,jolokia management.endpoints.web.exposure.include=logfile,loggers,jolokia
management.endpoints.web.path-mapping.jolokia=jmx 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

View file

@ -37,7 +37,7 @@ spring.datasource.password=${sebserver.mariadb.password}
sebserver.webservice.api.admin.clientSecret=${sebserver.password} sebserver.webservice.api.admin.clientSecret=${sebserver.password}
sebserver.webservice.internalSecret=${sebserver.password} sebserver.webservice.internalSecret=${sebserver.password}
### webservice networking ### webservice setup configuration
sebserver.webservice.forceMaster=false sebserver.webservice.forceMaster=false
sebserver.webservice.distributed=false sebserver.webservice.distributed=false
sebserver.webservice.distributed.updateInterval=2000 sebserver.webservice.distributed.updateInterval=2000
@ -45,6 +45,8 @@ sebserver.webservice.http.external.scheme=https
sebserver.webservice.http.external.servername= sebserver.webservice.http.external.servername=
sebserver.webservice.http.external.port= sebserver.webservice.http.external.port=
sebserver.webservice.http.redirect.gui=/gui sebserver.webservice.http.redirect.gui=/gui
sebserver.webservice.ping.service.strategy=BLOCKING
### Open API Documentation ### Open API Documentation
springdoc.api-docs.enabled=false 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.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/* springdoc.paths-to-exclude=/exam-api,/exam-api/discovery,/sebserver/error,/sebserver/check,/oauth,/exam-api/v1/*
### webservice API ### webservice API
sebserver.webservice.api.admin.clientId=guiClient sebserver.webservice.api.admin.clientId=guiClient
sebserver.webservice.api.admin.endpoint=/admin-api/v1 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=/exam-api
sebserver.webservice.api.exam.endpoint.discovery=${sebserver.webservice.api.exam.endpoint}/discovery 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.endpoint.v1=${sebserver.webservice.api.exam.endpoint}/v1
sebserver.webservice.api.exam.accessTokenValiditySeconds=43200 sebserver.webservice.api.exam.accessTokenValiditySeconds=43200
sebserver.webservice.api.exam.event-handling-strategy=SINGLE_EVENT_STORE_STRATEGY
sebserver.webservice.api.exam.enable-indicator-cache=true sebserver.webservice.api.exam.enable-indicator-cache=true
sebserver.webservice.api.pagination.maxPageSize=500 sebserver.webservice.api.pagination.maxPageSize=500
# comma separated list of known possible OpenEdX API access token request endpoints # comma separated list of known possible OpenEdX API access token request endpoints

View file

@ -66,6 +66,10 @@ sebserver.ssl.redirect.html.port=8080
# features # features
sebserver.feature.seb.screenProctoring=false sebserver.feature.seb.screenProctoring=false
sebserver.feature.seb.screenProctoring.bundled=true 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.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 sebserver.feature.CollectingRoomStrategy.SEB-GROUP=false