added check endpoint and fixed gui endpoint

This commit is contained in:
anhefti 2021-03-25 20:47:58 +01:00
parent e73dd5105a
commit 9d5ed34ec6
6 changed files with 105 additions and 63 deletions

View file

@ -40,6 +40,9 @@ import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
@Order(7) @Order(7)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter implements ErrorController { 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}") @Value("${sebserver.webservice.http.redirect.gui}")
private String guiRedirect; private String guiRedirect;
@Value("${sebserver.webservice.api.exam.endpoint.discovery}") @Value("${sebserver.webservice.api.exam.endpoint.discovery}")
@ -78,22 +81,27 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter implements E
public void configure(final WebSecurity web) { public void configure(final WebSecurity web) {
web web
.ignoring() .ignoring()
.antMatchers("/error") .antMatchers(ERROR_PATH)
.antMatchers(CHECK_PATH)
.antMatchers(this.examAPIDiscoveryEndpoint) .antMatchers(this.examAPIDiscoveryEndpoint)
.antMatchers(this.adminAPIEndpoint + API.INFO_ENDPOINT + API.LOGO_PATH_SEGMENT + "/**") .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.INFO_ENDPOINT + API.INFO_INST_PATH_SEGMENT + "/**")
.antMatchers(this.adminAPIEndpoint + API.REGISTER_ENDPOINT); .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 { 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.setHeader(HttpHeaders.LOCATION, this.guiRedirect);
response.flushBuffer(); response.flushBuffer();
} }
@Override @Override
public String getErrorPath() { public String getErrorPath() {
return "/error"; return ERROR_PATH;
} }
} }

View file

@ -116,60 +116,56 @@ public final class InstitutionalAuthenticationEntryPoint implements Authenticati
final String institutionalEndpoint = extractInstitutionalEndpoint(request); final String institutionalEndpoint = extractInstitutionalEndpoint(request);
if (StringUtils.isNoneBlank(institutionalEndpoint) && log.isDebugEnabled()) { if (StringUtils.isNotBlank(institutionalEndpoint)) {
log.debug("No default gui entrypoint requested: {}", institutionalEndpoint); if (log.isDebugEnabled()) {
} else { log.debug("No default gui entrypoint requested: {}", institutionalEndpoint);
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<EntityName> institutions = restTemplate
.exchange(
this.webserviceURIService.getURIBuilder()
.path(API.INFO_ENDPOINT + API.INFO_INST_ENDPOINT)
.toUriString(),
HttpMethod.GET,
HttpEntity.EMPTY,
new ParameterizedTypeReference<List<EntityName>>() {
},
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);
try {
final RestTemplate restTemplate = new RestTemplate();
final List<EntityName> institutions = restTemplate
.exchange(
this.webserviceURIService.getURIBuilder()
.path(API.INFO_ENDPOINT + API.INFO_INST_ENDPOINT)
.toUriString(),
HttpMethod.GET,
HttpEntity.EMPTY,
new ParameterizedTypeReference<List<EntityName>>() {
},
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().setAttribute(INST_SUFFIX_ATTRIBUTE, null);
request.getSession().removeAttribute(API.PARAM_LOGO_IMAGE); request.getSession().removeAttribute(API.PARAM_LOGO_IMAGE);
response.setStatus(HttpStatus.UNAUTHORIZED.value()); 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) { public static String extractInstitutionalEndpoint(final HttpServletRequest request) {
final String requestURI = request.getRequestURI(); final String requestURI = request.getRequestURI();
if (StringUtils.isBlank(requestURI) || requestURI.equals(Constants.SLASH.toString())) { if (StringUtils.isBlank(requestURI)) {
return null; return null;
} }
if (log.isDebugEnabled()) { if (requestURI.equals(Constants.SLASH.toString())) {
log.debug("Trying to verify institution from requested entrypoint url: {}", requestURI); return StringUtils.EMPTY;
} }
try { 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) { } catch (final Exception e) {
log.error("Failed to extract institutional URL suffix: {}", e.getMessage()); log.error("Failed to extract institutional URL suffix: {}", e.getMessage());
return null; return null;

View file

@ -102,7 +102,7 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO {
.execute(); .execute();
if (active > 0) { 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(); final String newCollectingRoomName = UUID.randomUUID().toString();

View file

@ -21,6 +21,7 @@ import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
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.beans.factory.annotation.Qualifier; 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.ResponseStatus;
import org.springframework.web.bind.annotation.RestController; 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.API;
import ch.ethz.seb.sebserver.gbl.api.APIMessage; import ch.ethz.seb.sebserver.gbl.api.APIMessage;
import ch.ethz.seb.sebserver.gbl.api.JSONMapper; 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 POSTMapper mapper = new POSTMapper(formParams, request.getQueryString());
final String remoteAddr = request.getRemoteAddr(); final String remoteAddr = this.getClientAddress(request);
final Long institutionId = (instIdRequestParam != null) final Long institutionId = (instIdRequestParam != null)
? instIdRequestParam ? instIdRequestParam
: mapper.getLong(API.PARAM_INSTITUTION_ID); : mapper.getLong(API.PARAM_INSTITUTION_ID);
@ -158,7 +160,7 @@ public class ExamAPI_V1_Controller {
return CompletableFuture.runAsync( return CompletableFuture.runAsync(
() -> { () -> {
final String remoteAddr = request.getRemoteAddr(); final String remoteAddr = this.getClientAddress(request);
final Long institutionId = getInstitutionId(principal); final Long institutionId = getInstitutionId(principal);
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
@ -198,7 +200,7 @@ public class ExamAPI_V1_Controller {
return CompletableFuture.runAsync( return CompletableFuture.runAsync(
() -> { () -> {
final String remoteAddr = request.getRemoteAddr(); final String remoteAddr = this.getClientAddress(request);
final Long institutionId = getInstitutionId(principal); final Long institutionId = getInstitutionId(principal);
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
@ -234,7 +236,7 @@ public class ExamAPI_V1_Controller {
return CompletableFuture.runAsync( return CompletableFuture.runAsync(
() -> { () -> {
final String remoteAddr = request.getRemoteAddr(); final String remoteAddr = this.getClientAddress(request);
final Long institutionId = getInstitutionId(principal); final Long institutionId = getInstitutionId(principal);
if (log.isDebugEnabled()) { 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");
}
}
} }

View file

@ -316,7 +316,7 @@ public class ExamProctoringController {
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId, defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@PathVariable(name = API.PARAM_MODEL_ID) final Long examId) { @PathVariable(name = API.PARAM_MODEL_ID) final Long examId) {
checkAccess(institutionId, examId); checkExamReadAccess(institutionId);
return this.examProcotringRoomService.getTownhallRoomData(examId) return this.examProcotringRoomService.getTownhallRoomData(examId)
.getOrElse(() -> RemoteProctoringRoom.NULL_ROOM); .getOrElse(() -> RemoteProctoringRoom.NULL_ROOM);
@ -351,7 +351,8 @@ public class ExamProctoringController {
// first create and register a room to collect all connection of the exam // 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 // As long as the room exists new connections will join this room immediately
// after have been applied to the default collecting room // 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)) .onError(error -> this.examProcotringRoomService.disposeTownhallRoom(examId))
.getOrThrow(); .getOrThrow();
@ -590,6 +591,13 @@ public class ExamProctoringController {
true); true);
} }
private void checkExamReadAccess(final Long institutionId) {
this.authorization.check(
PrivilegeType.READ,
EntityType.EXAM,
institutionId);
}
private void checkAccess(final Long institutionId, final Long examId) { private void checkAccess(final Long institutionId, final Long examId) {
this.authorization.check( this.authorization.check(
PrivilegeType.READ, PrivilegeType.READ,

View file

@ -36,7 +36,7 @@ logging.level.ch=INFO
### spring actuator configuration ### spring actuator configuration
management.endpoints.web.base-path=/mprofile 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 ### Overall Security Settings