2020-02-17 16:43:08 +01:00
|
|
|
/*
|
|
|
|
* 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.gui;
|
|
|
|
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.io.InputStreamReader;
|
|
|
|
import java.io.Reader;
|
2021-02-02 17:58:40 +01:00
|
|
|
import java.nio.charset.StandardCharsets;
|
2020-02-24 14:46:37 +01:00
|
|
|
import java.util.List;
|
2020-02-17 16:43:08 +01:00
|
|
|
|
|
|
|
import javax.servlet.RequestDispatcher;
|
|
|
|
import javax.servlet.ServletException;
|
|
|
|
import javax.servlet.http.HttpServletRequest;
|
|
|
|
import javax.servlet.http.HttpServletResponse;
|
|
|
|
|
|
|
|
import org.apache.commons.codec.binary.Base64InputStream;
|
|
|
|
import org.apache.commons.lang3.StringUtils;
|
|
|
|
import org.eclipse.rap.rwt.RWT;
|
|
|
|
import org.slf4j.Logger;
|
|
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
|
|
import org.springframework.context.annotation.Lazy;
|
2020-02-24 14:46:37 +01:00
|
|
|
import org.springframework.core.ParameterizedTypeReference;
|
2020-02-17 16:43:08 +01:00
|
|
|
import org.springframework.core.io.Resource;
|
|
|
|
import org.springframework.core.io.ResourceLoader;
|
|
|
|
import org.springframework.http.HttpEntity;
|
|
|
|
import org.springframework.http.HttpMethod;
|
|
|
|
import org.springframework.http.HttpStatus;
|
|
|
|
import org.springframework.http.ResponseEntity;
|
|
|
|
import org.springframework.http.client.ClientHttpRequestFactory;
|
|
|
|
import org.springframework.security.core.AuthenticationException;
|
|
|
|
import org.springframework.security.web.AuthenticationEntryPoint;
|
|
|
|
import org.springframework.stereotype.Component;
|
|
|
|
import org.springframework.util.FileCopyUtils;
|
|
|
|
import org.springframework.web.client.RestTemplate;
|
|
|
|
|
|
|
|
import ch.ethz.seb.sebserver.ClientHttpRequestFactoryService;
|
|
|
|
import ch.ethz.seb.sebserver.gbl.Constants;
|
|
|
|
import ch.ethz.seb.sebserver.gbl.api.API;
|
2020-02-24 14:46:37 +01:00
|
|
|
import ch.ethz.seb.sebserver.gbl.model.EntityName;
|
2020-02-17 16:43:08 +01:00
|
|
|
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
|
|
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.WebserviceURIService;
|
|
|
|
import ch.ethz.seb.sebserver.gui.widget.ImageUploadSelection;
|
|
|
|
|
|
|
|
@Lazy
|
|
|
|
@Component
|
|
|
|
@GuiProfile
|
|
|
|
public final class InstitutionalAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
|
|
|
|
2020-02-24 14:46:37 +01:00
|
|
|
private static final String INST_SUFFIX_ATTRIBUTE = "endpointInstId";
|
2020-02-17 16:43:08 +01:00
|
|
|
|
|
|
|
private static final Logger log = LoggerFactory.getLogger(InstitutionalAuthenticationEntryPoint.class);
|
|
|
|
|
|
|
|
private final String guiEntryPoint;
|
2020-10-06 11:30:37 +02:00
|
|
|
|
|
|
|
private final String remoteProctoringEndpoint;
|
2020-02-17 16:43:08 +01:00
|
|
|
private final String defaultLogo;
|
|
|
|
private final WebserviceURIService webserviceURIService;
|
|
|
|
private final ClientHttpRequestFactoryService clientHttpRequestFactoryService;
|
|
|
|
|
|
|
|
protected InstitutionalAuthenticationEntryPoint(
|
|
|
|
@Value("${sebserver.gui.entrypoint}") final String guiEntryPoint,
|
2020-10-06 11:30:37 +02:00
|
|
|
@Value("${sebserver.gui.remote.proctoring.entrypoint:/remote-proctoring}") final String remoteProctoringEndpoint,
|
2020-02-17 16:43:08 +01:00
|
|
|
@Value("${sebserver.gui.defaultLogo:" + Constants.NO_NAME + "}") final String defaultLogoFileName,
|
|
|
|
final WebserviceURIService webserviceURIService,
|
|
|
|
final ClientHttpRequestFactoryService clientHttpRequestFactoryService,
|
|
|
|
final ResourceLoader resourceLoader) {
|
|
|
|
|
|
|
|
this.guiEntryPoint = guiEntryPoint;
|
2020-10-06 11:30:37 +02:00
|
|
|
this.remoteProctoringEndpoint = remoteProctoringEndpoint;
|
2020-02-17 16:43:08 +01:00
|
|
|
this.webserviceURIService = webserviceURIService;
|
|
|
|
this.clientHttpRequestFactoryService = clientHttpRequestFactoryService;
|
|
|
|
|
2020-03-03 09:36:43 +01:00
|
|
|
String _defaultLogo;
|
2020-02-17 16:43:08 +01:00
|
|
|
if (!Constants.NO_NAME.equals(defaultLogoFileName)) {
|
|
|
|
try {
|
|
|
|
|
|
|
|
final String extension = ImageUploadSelection.SUPPORTED_IMAGE_FILES.stream()
|
2020-03-03 09:36:43 +01:00
|
|
|
.filter(defaultLogoFileName::endsWith)
|
2020-02-17 16:43:08 +01:00
|
|
|
.findFirst()
|
|
|
|
.orElse(null);
|
|
|
|
|
|
|
|
if (extension == null) {
|
|
|
|
throw new IllegalArgumentException("Image of type: " + defaultLogoFileName + " not supported");
|
|
|
|
}
|
|
|
|
|
|
|
|
final Resource resource = resourceLoader.getResource(defaultLogoFileName);
|
|
|
|
final Reader reader = new InputStreamReader(
|
|
|
|
new Base64InputStream(resource.getInputStream(), true),
|
2021-02-02 17:58:40 +01:00
|
|
|
StandardCharsets.UTF_8);
|
2020-02-17 16:43:08 +01:00
|
|
|
|
|
|
|
_defaultLogo = FileCopyUtils.copyToString(reader);
|
|
|
|
|
|
|
|
} catch (final Exception e) {
|
|
|
|
log.warn("Failed to load default logo image from filesystem: {}", defaultLogoFileName);
|
|
|
|
_defaultLogo = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.defaultLogo = _defaultLogo;
|
|
|
|
} else {
|
|
|
|
this.defaultLogo = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void commence(
|
|
|
|
final HttpServletRequest request,
|
|
|
|
final HttpServletResponse response,
|
|
|
|
final AuthenticationException authException) throws IOException, ServletException {
|
|
|
|
|
|
|
|
final String institutionalEndpoint = extractInstitutionalEndpoint(request);
|
|
|
|
|
2021-03-25 20:47:58 +01:00
|
|
|
if (StringUtils.isNotBlank(institutionalEndpoint)) {
|
|
|
|
if (log.isDebugEnabled()) {
|
|
|
|
log.debug("No default gui entrypoint requested: {}", institutionalEndpoint);
|
|
|
|
}
|
2020-02-24 14:46:37 +01:00
|
|
|
|
2021-03-25 20:47:58 +01:00
|
|
|
try {
|
2020-02-24 14:46:37 +01:00
|
|
|
|
2021-03-25 20:47:58 +01:00
|
|
|
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;
|
2020-02-24 14:46:37 +01:00
|
|
|
}
|
2021-03-25 20:47:58 +01:00
|
|
|
} catch (final Exception e) {
|
|
|
|
log.error("Failed to extract and set institutional endpoint request: ", e);
|
2020-02-24 14:46:37 +01:00
|
|
|
}
|
2020-02-17 16:43:08 +01:00
|
|
|
}
|
2020-02-24 14:46:37 +01:00
|
|
|
|
|
|
|
request.getSession().setAttribute(INST_SUFFIX_ATTRIBUTE, null);
|
|
|
|
request.getSession().removeAttribute(API.PARAM_LOGO_IMAGE);
|
|
|
|
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
2021-03-25 20:47:58 +01:00
|
|
|
forwardToEntryPoint(request, response, this.guiEntryPoint, institutionalEndpoint == null);
|
2020-02-24 14:46:37 +01:00
|
|
|
|
2020-02-17 16:43:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
private void forwardToEntryPoint(
|
|
|
|
final HttpServletRequest request,
|
|
|
|
final HttpServletResponse response,
|
2020-10-06 11:30:37 +02:00
|
|
|
final String entryPoint,
|
|
|
|
final boolean redirect) throws ServletException, IOException {
|
|
|
|
|
|
|
|
final String requestURI = request.getRequestURI();
|
|
|
|
if (requestURI.startsWith(this.remoteProctoringEndpoint)) {
|
2021-03-04 14:50:53 +01:00
|
|
|
|
2020-10-06 11:30:37 +02:00
|
|
|
final RequestDispatcher dispatcher = request
|
|
|
|
.getServletContext()
|
2020-10-07 15:07:08 +02:00
|
|
|
.getRequestDispatcher(this.remoteProctoringEndpoint);
|
2020-02-17 16:43:08 +01:00
|
|
|
|
2020-10-06 11:30:37 +02:00
|
|
|
dispatcher.forward(request, response);
|
|
|
|
return;
|
|
|
|
}
|
2020-02-17 16:43:08 +01:00
|
|
|
|
2020-10-06 11:30:37 +02:00
|
|
|
if (redirect) {
|
|
|
|
response.sendRedirect(entryPoint);
|
|
|
|
} else {
|
|
|
|
final RequestDispatcher dispatcher = request
|
|
|
|
.getServletContext()
|
|
|
|
.getRequestDispatcher(entryPoint);
|
|
|
|
|
|
|
|
dispatcher.forward(request, response);
|
|
|
|
}
|
2020-02-17 16:43:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public static String extractInstitutionalEndpoint(final HttpServletRequest request) {
|
|
|
|
final String requestURI = request.getRequestURI();
|
2021-03-25 20:47:58 +01:00
|
|
|
if (StringUtils.isBlank(requestURI)) {
|
2021-03-16 20:39:39 +01:00
|
|
|
return null;
|
|
|
|
}
|
2020-02-17 16:43:08 +01:00
|
|
|
|
2021-03-25 20:47:58 +01:00
|
|
|
if (requestURI.equals(Constants.SLASH.toString())) {
|
|
|
|
return StringUtils.EMPTY;
|
2020-02-17 16:43:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
2021-03-25 20:47:58 +01:00
|
|
|
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];
|
2020-02-17 16:43:08 +01:00
|
|
|
} catch (final Exception e) {
|
|
|
|
log.error("Failed to extract institutional URL suffix: {}", e.getMessage());
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static String extractInstitutionalEndpoint() {
|
|
|
|
try {
|
|
|
|
final Object attribute = RWT.getUISession().getHttpSession().getAttribute(INST_SUFFIX_ATTRIBUTE);
|
|
|
|
return (attribute != null) ? String.valueOf(attribute) : null;
|
|
|
|
} catch (final Exception e) {
|
|
|
|
log.warn("Failed to extract institutional endpoint form user session: {}", e.getMessage());
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private String requestLogoImage(final String institutionalEndpoint) {
|
|
|
|
if (StringUtils.isBlank(institutionalEndpoint)) {
|
|
|
|
return this.defaultLogo;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
final RestTemplate restTemplate = new RestTemplate();
|
|
|
|
|
|
|
|
final ClientHttpRequestFactory clientHttpRequestFactory = this.clientHttpRequestFactoryService
|
|
|
|
.getClientHttpRequestFactory()
|
|
|
|
.getOrThrow();
|
|
|
|
|
|
|
|
restTemplate.setRequestFactory(clientHttpRequestFactory);
|
|
|
|
|
|
|
|
final ResponseEntity<String> exchange = restTemplate
|
|
|
|
.exchange(
|
|
|
|
this.webserviceURIService.getURIBuilder()
|
|
|
|
.path(API.INFO_ENDPOINT + API.INSTITUTIONAL_LOGO_PATH)
|
|
|
|
.toUriString(),
|
|
|
|
HttpMethod.GET,
|
|
|
|
HttpEntity.EMPTY,
|
|
|
|
String.class,
|
|
|
|
institutionalEndpoint);
|
|
|
|
|
|
|
|
if (exchange.getStatusCodeValue() == HttpStatus.OK.value()) {
|
|
|
|
return exchange.getBody();
|
|
|
|
} else {
|
2020-03-03 09:36:43 +01:00
|
|
|
log.warn("Failed to verify institution from requested entrypoint url: {}, response: {}",
|
2020-02-17 16:43:08 +01:00
|
|
|
institutionalEndpoint,
|
|
|
|
exchange);
|
|
|
|
}
|
|
|
|
} catch (final Exception e) {
|
2020-03-03 09:36:43 +01:00
|
|
|
log.warn("Failed to verify institution from requested entrypoint url: {}",
|
2020-02-17 16:43:08 +01:00
|
|
|
institutionalEndpoint,
|
|
|
|
e);
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2019-02-14 16:54:48 +01:00
|
|
|
}
|