diff --git a/src/main/java/ch/ethz/seb/sebserver/WebSecurityConfig.java b/src/main/java/ch/ethz/seb/sebserver/WebSecurityConfig.java index f62042b7..c2b5d8f4 100644 --- a/src/main/java/ch/ethz/seb/sebserver/WebSecurityConfig.java +++ b/src/main/java/ch/ethz/seb/sebserver/WebSecurityConfig.java @@ -40,6 +40,9 @@ import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; @Order(7) public class WebSecurityConfig extends WebSecurityConfigurerAdapter implements ErrorController { + private static final String ERROR_PATH = "/sebserver/error"; + private static final String CHECK_PATH = "/sebserver/check"; + @Value("${sebserver.webservice.http.redirect.gui}") private String guiRedirect; @Value("${sebserver.webservice.api.exam.endpoint.discovery}") @@ -78,22 +81,27 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter implements E public void configure(final WebSecurity web) { web .ignoring() - .antMatchers("/error") + .antMatchers(ERROR_PATH) + .antMatchers(CHECK_PATH) .antMatchers(this.examAPIDiscoveryEndpoint) .antMatchers(this.adminAPIEndpoint + API.INFO_ENDPOINT + API.LOGO_PATH_SEGMENT + "/**") .antMatchers(this.adminAPIEndpoint + API.INFO_ENDPOINT + API.INFO_INST_PATH_SEGMENT + "/**") .antMatchers(this.adminAPIEndpoint + API.REGISTER_ENDPOINT); } - @RequestMapping("/error") + @RequestMapping(CHECK_PATH) + public void check() throws IOException { + } + + @RequestMapping(ERROR_PATH) public void handleError(final HttpServletResponse response) throws IOException { - //response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY); + response.getOutputStream().print(response.getStatus()); response.setHeader(HttpHeaders.LOCATION, this.guiRedirect); response.flushBuffer(); } @Override public String getErrorPath() { - return "/error"; + return ERROR_PATH; } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/InstitutionalAuthenticationEntryPoint.java b/src/main/java/ch/ethz/seb/sebserver/gui/InstitutionalAuthenticationEntryPoint.java index 39586d96..14d264bd 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/InstitutionalAuthenticationEntryPoint.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/InstitutionalAuthenticationEntryPoint.java @@ -116,60 +116,56 @@ public final class InstitutionalAuthenticationEntryPoint implements Authenticati final String institutionalEndpoint = extractInstitutionalEndpoint(request); - if (StringUtils.isNoneBlank(institutionalEndpoint) && log.isDebugEnabled()) { - log.debug("No default gui entrypoint requested: {}", institutionalEndpoint); - } else { - request.getSession().setAttribute(INST_SUFFIX_ATTRIBUTE, null); - request.getSession().removeAttribute(API.PARAM_LOGO_IMAGE); - forwardToEntryPoint(request, response, this.guiEntryPoint, false); - return; - } - - try { - - final RestTemplate restTemplate = new RestTemplate(); - final List institutions = restTemplate - .exchange( - this.webserviceURIService.getURIBuilder() - .path(API.INFO_ENDPOINT + API.INFO_INST_ENDPOINT) - .toUriString(), - HttpMethod.GET, - HttpEntity.EMPTY, - new ParameterizedTypeReference>() { - }, - institutionalEndpoint, - API.INFO_PARAM_INST_SUFFIX, - institutionalEndpoint) - .getBody(); - - if (institutions != null && !institutions.isEmpty()) { - request.getSession().setAttribute( - INST_SUFFIX_ATTRIBUTE, - StringUtils.isNotBlank(institutionalEndpoint) - ? institutionalEndpoint - : null); - - if (log.isDebugEnabled()) { - log.debug("Known and active gui entrypoint requested: {}", institutions); - } - - final String logoImageBase64 = requestLogoImage(institutionalEndpoint); - if (StringUtils.isNotBlank(logoImageBase64)) { - request.getSession().setAttribute(API.PARAM_LOGO_IMAGE, logoImageBase64); - - } - forwardToEntryPoint(request, response, this.guiEntryPoint, false); - return; + if (StringUtils.isNotBlank(institutionalEndpoint)) { + if (log.isDebugEnabled()) { + log.debug("No default gui entrypoint requested: {}", institutionalEndpoint); } - } catch (final Exception e) { - log.error("Failed to extract and set institutional endpoint request: ", e); + try { + + final RestTemplate restTemplate = new RestTemplate(); + final List institutions = restTemplate + .exchange( + this.webserviceURIService.getURIBuilder() + .path(API.INFO_ENDPOINT + API.INFO_INST_ENDPOINT) + .toUriString(), + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + institutionalEndpoint, + API.INFO_PARAM_INST_SUFFIX, + institutionalEndpoint) + .getBody(); + + if (institutions != null && !institutions.isEmpty()) { + request.getSession().setAttribute( + INST_SUFFIX_ATTRIBUTE, + StringUtils.isNotBlank(institutionalEndpoint) + ? institutionalEndpoint + : null); + + if (log.isDebugEnabled()) { + log.debug("Known and active gui entrypoint requested: {}", institutions); + } + + final String logoImageBase64 = requestLogoImage(institutionalEndpoint); + if (StringUtils.isNotBlank(logoImageBase64)) { + request.getSession().setAttribute(API.PARAM_LOGO_IMAGE, logoImageBase64); + + } + forwardToEntryPoint(request, response, this.guiEntryPoint, false); + return; + } + } catch (final Exception e) { + log.error("Failed to extract and set institutional endpoint request: ", e); + } } request.getSession().setAttribute(INST_SUFFIX_ATTRIBUTE, null); request.getSession().removeAttribute(API.PARAM_LOGO_IMAGE); response.setStatus(HttpStatus.UNAUTHORIZED.value()); - forwardToEntryPoint(request, response, this.guiEntryPoint, true); + forwardToEntryPoint(request, response, this.guiEntryPoint, institutionalEndpoint == null); } @@ -203,16 +199,25 @@ public final class InstitutionalAuthenticationEntryPoint implements Authenticati public static String extractInstitutionalEndpoint(final HttpServletRequest request) { final String requestURI = request.getRequestURI(); - if (StringUtils.isBlank(requestURI) || requestURI.equals(Constants.SLASH.toString())) { + if (StringUtils.isBlank(requestURI)) { return null; } - if (log.isDebugEnabled()) { - log.debug("Trying to verify institution from requested entrypoint url: {}", requestURI); + if (requestURI.equals(Constants.SLASH.toString())) { + return StringUtils.EMPTY; } try { - return requestURI.substring(requestURI.lastIndexOf(Constants.SLASH) + 1); + if (log.isDebugEnabled()) { + log.debug("Trying to verify institution from requested entrypoint url: {}", requestURI); + } + + final String[] split = StringUtils.split(requestURI, Constants.SLASH); + if (split.length > 1) { + return null; + } + + return split[0]; } catch (final Exception e) { log.error("Failed to extract institutional URL suffix: {}", e.getMessage()); return null; diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/RemoteProctoringRoomDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/RemoteProctoringRoomDAOImpl.java index c0ebb6a5..76274c48 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/RemoteProctoringRoomDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/RemoteProctoringRoomDAOImpl.java @@ -102,7 +102,7 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO { .execute(); if (active > 0) { - throw new IllegalStateException("Townhall, for exam: " + examId + " already existis"); + throw new IllegalStateException("Townhall, for exam: " + examId + " already exists"); } final String newCollectingRoomName = UUID.randomUUID().toString(); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java index e0367431..b55e74b6 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java @@ -21,6 +21,7 @@ import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier; @@ -35,6 +36,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; +import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.APIMessage; import ch.ethz.seb.sebserver.gbl.api.JSONMapper; @@ -100,7 +102,7 @@ public class ExamAPI_V1_Controller { final POSTMapper mapper = new POSTMapper(formParams, request.getQueryString()); - final String remoteAddr = request.getRemoteAddr(); + final String remoteAddr = this.getClientAddress(request); final Long institutionId = (instIdRequestParam != null) ? instIdRequestParam : mapper.getLong(API.PARAM_INSTITUTION_ID); @@ -158,7 +160,7 @@ public class ExamAPI_V1_Controller { return CompletableFuture.runAsync( () -> { - final String remoteAddr = request.getRemoteAddr(); + final String remoteAddr = this.getClientAddress(request); final Long institutionId = getInstitutionId(principal); if (log.isDebugEnabled()) { @@ -198,7 +200,7 @@ public class ExamAPI_V1_Controller { return CompletableFuture.runAsync( () -> { - final String remoteAddr = request.getRemoteAddr(); + final String remoteAddr = this.getClientAddress(request); final Long institutionId = getInstitutionId(principal); if (log.isDebugEnabled()) { @@ -234,7 +236,7 @@ public class ExamAPI_V1_Controller { return CompletableFuture.runAsync( () -> { - final String remoteAddr = request.getRemoteAddr(); + final String remoteAddr = this.getClientAddress(request); final Long institutionId = getInstitutionId(principal); if (log.isDebugEnabled()) { @@ -419,4 +421,23 @@ public class ExamAPI_V1_Controller { } } + private String getClientAddress(final HttpServletRequest request) { + try { + final String ipAddress = request.getHeader("X-FORWARDED-FOR"); + + if (ipAddress == null) { + return request.getRemoteAddr(); + } + + if (ipAddress.contains(",")) { + return StringUtils.split(ipAddress, Constants.COMMA)[0]; + } + + return ipAddress; + } catch (final Exception e) { + log.warn("Failed to verify client IP address: {}", e.getMessage()); + return request.getHeader("X-FORWARDED-FOR"); + } + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamProctoringController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamProctoringController.java index 5e9e2381..698b2e9c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamProctoringController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamProctoringController.java @@ -316,7 +316,7 @@ public class ExamProctoringController { defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId, @PathVariable(name = API.PARAM_MODEL_ID) final Long examId) { - checkAccess(institutionId, examId); + checkExamReadAccess(institutionId); return this.examProcotringRoomService.getTownhallRoomData(examId) .getOrElse(() -> RemoteProctoringRoom.NULL_ROOM); @@ -351,7 +351,8 @@ public class ExamProctoringController { // first create and register a room to collect all connection of the exam // As long as the room exists new connections will join this room immediately // after have been applied to the default collecting room - final RemoteProctoringRoom townhallRoom = this.examProcotringRoomService.createTownhallRoom(examId, subject) + final RemoteProctoringRoom townhallRoom = this.examProcotringRoomService + .createTownhallRoom(examId, subject) .onError(error -> this.examProcotringRoomService.disposeTownhallRoom(examId)) .getOrThrow(); @@ -590,6 +591,13 @@ public class ExamProctoringController { true); } + private void checkExamReadAccess(final Long institutionId) { + this.authorization.check( + PrivilegeType.READ, + EntityType.EXAM, + institutionId); + } + private void checkAccess(final Long institutionId, final Long examId) { this.authorization.check( PrivilegeType.READ, diff --git a/src/main/resources/config/application.properties b/src/main/resources/config/application.properties index 023e512e..aa543423 100644 --- a/src/main/resources/config/application.properties +++ b/src/main/resources/config/application.properties @@ -36,7 +36,7 @@ logging.level.ch=INFO ### spring actuator configuration management.endpoints.web.base-path=/mprofile -management.endpoints.web.exposure.include=metrics,logfile,loggers,heapdump +management.endpoints.web.exposure.include=metrics,logfile,loggers,heapdump,health ########################################################## ### Overall Security Settings