mirror of
https://github.com/DSpace/DSpace.git
synced 2025-10-07 10:04:21 +00:00
Merge pull request #2651 from 4Science/dspace-7-shibboleth
Dspace 7 shibboleth (REST)
This commit is contained in:
@@ -34,6 +34,7 @@ import org.dspace.content.factory.ContentServiceFactory;
|
|||||||
import org.dspace.content.service.MetadataFieldService;
|
import org.dspace.content.service.MetadataFieldService;
|
||||||
import org.dspace.content.service.MetadataSchemaService;
|
import org.dspace.content.service.MetadataSchemaService;
|
||||||
import org.dspace.core.Context;
|
import org.dspace.core.Context;
|
||||||
|
import org.dspace.core.Utils;
|
||||||
import org.dspace.eperson.EPerson;
|
import org.dspace.eperson.EPerson;
|
||||||
import org.dspace.eperson.Group;
|
import org.dspace.eperson.Group;
|
||||||
import org.dspace.eperson.factory.EPersonServiceFactory;
|
import org.dspace.eperson.factory.EPersonServiceFactory;
|
||||||
@@ -492,35 +493,22 @@ public class ShibAuthentication implements AuthenticationMethod {
|
|||||||
boolean lazySession = configurationService.getBooleanProperty("authentication-shibboleth.lazysession", false);
|
boolean lazySession = configurationService.getBooleanProperty("authentication-shibboleth.lazysession", false);
|
||||||
|
|
||||||
if ( lazySession ) {
|
if ( lazySession ) {
|
||||||
String shibURL = configurationService.getProperty("authentication-shibboleth.lazysession.loginurl");
|
String shibURL = getShibURL(request);
|
||||||
boolean forceHTTPS =
|
|
||||||
configurationService.getBooleanProperty("authentication-shibboleth.lazysession.secure",true);
|
|
||||||
|
|
||||||
// Shibboleth authentication initiator
|
// Determine the client redirect URL, where to redirect after authenticating.
|
||||||
if (shibURL == null || shibURL.length() == 0) {
|
String redirectUrl = null;
|
||||||
shibURL = "/Shibboleth.sso/Login";
|
if (request.getHeader("Referer") != null && StringUtils.isNotBlank(request.getHeader("Referer"))) {
|
||||||
|
redirectUrl = request.getHeader("Referer");
|
||||||
|
} else if (request.getHeader("X-Requested-With") != null
|
||||||
|
&& StringUtils.isNotBlank(request.getHeader("X-Requested-With"))) {
|
||||||
|
redirectUrl = request.getHeader("X-Requested-With");
|
||||||
}
|
}
|
||||||
shibURL = shibURL.trim();
|
|
||||||
|
|
||||||
// Determine the return URL, where shib will send the user after authenticating. We need it to go back
|
// Determine the server return URL, where shib will send the user after authenticating.
|
||||||
// to DSpace's shibboleth-login url so the we will extract the user's information and locally
|
// We need it to go back to DSpace's shibboleth-login url so we will extract the user's information
|
||||||
// authenticate them.
|
// and locally authenticate them.
|
||||||
String host = request.getServerName();
|
String returnURL = configurationService.getProperty("dspace.server.url") + "/api/authn/shibboleth"
|
||||||
int port = request.getServerPort();
|
+ ((redirectUrl != null) ? "?redirectUrl=" + redirectUrl : "");
|
||||||
String contextPath = request.getContextPath();
|
|
||||||
|
|
||||||
String returnURL = request.getHeader("Referer");
|
|
||||||
if (returnURL == null) {
|
|
||||||
if (request.isSecure() || forceHTTPS) {
|
|
||||||
returnURL = "https://";
|
|
||||||
} else {
|
|
||||||
returnURL = "http://";
|
|
||||||
}
|
|
||||||
returnURL += host;
|
|
||||||
if (!(port == 443 || port == 80)) {
|
|
||||||
returnURL += ":" + port;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
shibURL += "?target=" + URLEncoder.encode(returnURL, "UTF-8");
|
shibURL += "?target=" + URLEncoder.encode(returnURL, "UTF-8");
|
||||||
@@ -1258,6 +1246,23 @@ public class ShibAuthentication implements AuthenticationMethod {
|
|||||||
return valueList;
|
return valueList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getShibURL(HttpServletRequest request) {
|
||||||
|
String shibURL = configurationService.getProperty("authentication-shibboleth.lazysession.loginurl",
|
||||||
|
"/Shibboleth.sso/Login");
|
||||||
|
boolean forceHTTPS =
|
||||||
|
configurationService.getBooleanProperty("authentication-shibboleth.lazysession.secure", true);
|
||||||
|
|
||||||
|
// Shibboleth url must be absolute
|
||||||
|
if (shibURL.startsWith("/")) {
|
||||||
|
String serverUrl = Utils.getBaseUrl(configurationService.getProperty("dspace.server.url"));
|
||||||
|
shibURL = serverUrl + shibURL;
|
||||||
|
if ((request.isSecure() || forceHTTPS) && shibURL.startsWith("http://")) {
|
||||||
|
shibURL = shibURL.replace("http://", "https://");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return shibURL;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -13,8 +13,10 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
|
import java.net.URL;
|
||||||
import java.rmi.dgc.VMID;
|
import java.rmi.dgc.VMID;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
@@ -414,6 +416,23 @@ public final class Utils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the baseurl from a given URL string
|
||||||
|
* @param urlString URL string
|
||||||
|
* @return baseurl (without any context path) or null (if URL was invalid)
|
||||||
|
*/
|
||||||
|
public static String getBaseUrl(String urlString) {
|
||||||
|
try {
|
||||||
|
URL url = new URL(urlString);
|
||||||
|
String baseUrl = url.getProtocol() + "://" + url.getHost();
|
||||||
|
if (url.getPort() != -1) {
|
||||||
|
baseUrl += (":" + url.getPort());
|
||||||
|
}
|
||||||
|
return baseUrl;
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the hostname from a given URI string
|
* Retrieve the hostname from a given URI string
|
||||||
|
@@ -22,6 +22,33 @@ import org.junit.Test;
|
|||||||
*/
|
*/
|
||||||
public class UtilsTest extends AbstractUnitTest {
|
public class UtilsTest extends AbstractUnitTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test of getBaseUrl method, of class Utils
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testGetBaseUrl() {
|
||||||
|
assertEquals("Test remove /server", "http://dspace.org",
|
||||||
|
Utils.getBaseUrl("http://dspace.org/server"));
|
||||||
|
|
||||||
|
assertEquals("Test remove /server/api/core/items", "https://dspace.org",
|
||||||
|
Utils.getBaseUrl("https://dspace.org/server/api/core/items"));
|
||||||
|
|
||||||
|
assertEquals("Test remove trailing slash", "https://dspace.org",
|
||||||
|
Utils.getBaseUrl("https://dspace.org/"));
|
||||||
|
|
||||||
|
assertEquals("Test keep url", "https://demo.dspace.org",
|
||||||
|
Utils.getBaseUrl("https://demo.dspace.org"));
|
||||||
|
|
||||||
|
assertEquals("Test keep url", "http://localhost:8080",
|
||||||
|
Utils.getBaseUrl("http://localhost:8080"));
|
||||||
|
|
||||||
|
assertEquals("Test keep url", "http://localhost:8080",
|
||||||
|
Utils.getBaseUrl("http://localhost:8080/server"));
|
||||||
|
|
||||||
|
// This uses a bunch of reserved URI characters
|
||||||
|
assertNull("Test invalid URI returns null", Utils.getBaseUrl("&+,?/@="));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test of getHostName method, of class Utils
|
* Test of getHostName method, of class Utils
|
||||||
*/
|
*/
|
||||||
|
@@ -10,6 +10,7 @@ package org.dspace.app.rest;
|
|||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.dspace.app.rest.converter.ConverterService;
|
import org.dspace.app.rest.converter.ConverterService;
|
||||||
import org.dspace.app.rest.converter.EPersonConverter;
|
import org.dspace.app.rest.converter.EPersonConverter;
|
||||||
@@ -20,6 +21,7 @@ import org.dspace.app.rest.model.EPersonRest;
|
|||||||
import org.dspace.app.rest.model.hateoas.AuthenticationStatusResource;
|
import org.dspace.app.rest.model.hateoas.AuthenticationStatusResource;
|
||||||
import org.dspace.app.rest.model.hateoas.AuthnResource;
|
import org.dspace.app.rest.model.hateoas.AuthnResource;
|
||||||
import org.dspace.app.rest.projection.Projection;
|
import org.dspace.app.rest.projection.Projection;
|
||||||
|
import org.dspace.app.rest.security.RestAuthenticationService;
|
||||||
import org.dspace.app.rest.utils.ContextUtil;
|
import org.dspace.app.rest.utils.ContextUtil;
|
||||||
import org.dspace.app.rest.utils.Utils;
|
import org.dspace.app.rest.utils.Utils;
|
||||||
import org.dspace.core.Context;
|
import org.dspace.core.Context;
|
||||||
@@ -60,6 +62,9 @@ public class AuthenticationRestController implements InitializingBean {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private HalLinkService halLinkService;
|
private HalLinkService halLinkService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RestAuthenticationService restAuthenticationService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private Utils utils;
|
private Utils utils;
|
||||||
|
|
||||||
@@ -77,7 +82,8 @@ public class AuthenticationRestController implements InitializingBean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@RequestMapping(value = "/status", method = RequestMethod.GET)
|
@RequestMapping(value = "/status", method = RequestMethod.GET)
|
||||||
public AuthenticationStatusResource status(HttpServletRequest request) throws SQLException {
|
public AuthenticationStatusResource status(HttpServletRequest request, HttpServletResponse response)
|
||||||
|
throws SQLException {
|
||||||
Context context = ContextUtil.obtainContext(request);
|
Context context = ContextUtil.obtainContext(request);
|
||||||
EPersonRest ePersonRest = null;
|
EPersonRest ePersonRest = null;
|
||||||
Projection projection = utils.obtainProjection();
|
Projection projection = utils.obtainProjection();
|
||||||
@@ -86,6 +92,14 @@ public class AuthenticationRestController implements InitializingBean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AuthenticationStatusRest authenticationStatusRest = new AuthenticationStatusRest(ePersonRest);
|
AuthenticationStatusRest authenticationStatusRest = new AuthenticationStatusRest(ePersonRest);
|
||||||
|
// Whether authentication status is false add WWW-Authenticate so client can retrieve the available
|
||||||
|
// authentication methods
|
||||||
|
if (!authenticationStatusRest.isAuthenticated()) {
|
||||||
|
String authenticateHeaderValue = restAuthenticationService
|
||||||
|
.getWwwAuthenticateHeaderValue(request, response);
|
||||||
|
|
||||||
|
response.setHeader("WWW-Authenticate", authenticateHeaderValue);
|
||||||
|
}
|
||||||
authenticationStatusRest.setProjection(projection);
|
authenticationStatusRest.setProjection(projection);
|
||||||
AuthenticationStatusResource authenticationStatusResource = converter.toResource(authenticationStatusRest);
|
AuthenticationStatusResource authenticationStatusResource = converter.toResource(authenticationStatusRest);
|
||||||
|
|
||||||
|
@@ -0,0 +1,60 @@
|
|||||||
|
/**
|
||||||
|
* The contents of this file are subject to the license and copyright
|
||||||
|
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||||
|
* tree and available online at
|
||||||
|
*
|
||||||
|
* http://www.dspace.org/license/
|
||||||
|
*/
|
||||||
|
package org.dspace.app.rest;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.dspace.app.rest.model.AuthnRest;
|
||||||
|
import org.dspace.services.ConfigurationService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.hateoas.Link;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rest controller that handles redirect after shibboleth authentication succeded
|
||||||
|
*
|
||||||
|
* @author Andrea Bollini (andrea dot bollini at 4science dot it)
|
||||||
|
* @author Giuseppe Digilio (giuseppe dot digilio at 4science dot it)
|
||||||
|
*/
|
||||||
|
@RequestMapping(value = "/api/" + AuthnRest.CATEGORY + "/shibboleth")
|
||||||
|
@RestController
|
||||||
|
public class ShibbolethRestController implements InitializingBean {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(ShibbolethRestController.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
ConfigurationService configurationService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
DiscoverableEndpointsService discoverableEndpointsService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterPropertiesSet() {
|
||||||
|
discoverableEndpointsService
|
||||||
|
.register(this, Arrays.asList(new Link("/api/" + AuthnRest.CATEGORY, "shibboleth")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping(method = RequestMethod.GET)
|
||||||
|
public void shibboleth(HttpServletResponse response,
|
||||||
|
@RequestParam(name = "redirectUrl", required = false) String redirectUrl) throws IOException {
|
||||||
|
if (redirectUrl == null) {
|
||||||
|
redirectUrl = configurationService.getProperty("dspace.ui.url");
|
||||||
|
}
|
||||||
|
log.info("Redirecting to " + redirectUrl);
|
||||||
|
response.sendRedirect(redirectUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -35,7 +35,7 @@ public class AuthnHalLinkFactory extends HalLinkFactory<AuthnResource, Authentic
|
|||||||
.logout()));
|
.logout()));
|
||||||
|
|
||||||
list.add(buildLink("status", methodOn
|
list.add(buildLink("status", methodOn
|
||||||
.status(null)));
|
.status(null, null)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@@ -45,7 +45,7 @@ public class CustomLogoutHandler implements LogoutHandler {
|
|||||||
Authentication authentication) {
|
Authentication authentication) {
|
||||||
try {
|
try {
|
||||||
Context context = ContextUtil.obtainContext(httpServletRequest);
|
Context context = ContextUtil.obtainContext(httpServletRequest);
|
||||||
restAuthenticationService.invalidateAuthenticationData(httpServletRequest, context);
|
restAuthenticationService.invalidateAuthenticationData(httpServletRequest, httpServletResponse, context);
|
||||||
context.commit();
|
context.commit();
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@@ -26,13 +26,14 @@ import org.springframework.stereotype.Service;
|
|||||||
public interface RestAuthenticationService {
|
public interface RestAuthenticationService {
|
||||||
|
|
||||||
void addAuthenticationDataForUser(HttpServletRequest request, HttpServletResponse response,
|
void addAuthenticationDataForUser(HttpServletRequest request, HttpServletResponse response,
|
||||||
DSpaceAuthentication authentication) throws IOException;
|
DSpaceAuthentication authentication, boolean addCookie) throws IOException;
|
||||||
|
|
||||||
EPerson getAuthenticatedEPerson(HttpServletRequest request, Context context);
|
EPerson getAuthenticatedEPerson(HttpServletRequest request, Context context);
|
||||||
|
|
||||||
boolean hasAuthenticationData(HttpServletRequest request);
|
boolean hasAuthenticationData(HttpServletRequest request);
|
||||||
|
|
||||||
void invalidateAuthenticationData(HttpServletRequest request, Context context) throws Exception;
|
void invalidateAuthenticationData(HttpServletRequest request, HttpServletResponse response, Context context)
|
||||||
|
throws Exception;
|
||||||
|
|
||||||
AuthenticationService getAuthenticationService();
|
AuthenticationService getAuthenticationService();
|
||||||
|
|
||||||
@@ -44,4 +45,6 @@ public interface RestAuthenticationService {
|
|||||||
*/
|
*/
|
||||||
String getWwwAuthenticateHeaderValue(HttpServletRequest request, HttpServletResponse response);
|
String getWwwAuthenticateHeaderValue(HttpServletRequest request, HttpServletResponse response);
|
||||||
|
|
||||||
|
void invalidateAuthenticationCookie(HttpServletResponse res);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,53 @@
|
|||||||
|
/**
|
||||||
|
* The contents of this file are subject to the license and copyright
|
||||||
|
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||||
|
* tree and available online at
|
||||||
|
*
|
||||||
|
* http://www.dspace.org/license/
|
||||||
|
*/
|
||||||
|
package org.dspace.app.rest.security;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class will filter shibboleth requests to try and authenticate them
|
||||||
|
*
|
||||||
|
* @author Giuseppe Digilio (giuseppe dot digilio at 4science dot it)
|
||||||
|
*/
|
||||||
|
public class ShibbolethAuthenticationFilter extends StatelessLoginFilter {
|
||||||
|
|
||||||
|
public ShibbolethAuthenticationFilter(String url, AuthenticationManager authenticationManager,
|
||||||
|
RestAuthenticationService restAuthenticationService) {
|
||||||
|
super(url, authenticationManager, restAuthenticationService);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Authentication attemptAuthentication(HttpServletRequest req,
|
||||||
|
HttpServletResponse res) throws AuthenticationException {
|
||||||
|
|
||||||
|
return authenticationManager.authenticate(
|
||||||
|
new DSpaceAuthentication(null, null, new ArrayList<>())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void successfulAuthentication(HttpServletRequest req,
|
||||||
|
HttpServletResponse res,
|
||||||
|
FilterChain chain,
|
||||||
|
Authentication auth) throws IOException, ServletException {
|
||||||
|
|
||||||
|
DSpaceAuthentication dSpaceAuthentication = (DSpaceAuthentication) auth;
|
||||||
|
restAuthenticationService.addAuthenticationDataForUser(req, res, dSpaceAuthentication, true);
|
||||||
|
chain.doFilter(req, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -61,6 +61,7 @@ public class StatelessAuthenticationFilter extends BasicAuthenticationFilter {
|
|||||||
Authentication authentication = getAuthentication(req);
|
Authentication authentication = getAuthentication(req);
|
||||||
if (authentication != null) {
|
if (authentication != null) {
|
||||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||||
|
restAuthenticationService.invalidateAuthenticationCookie(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
chain.doFilter(req, res);
|
chain.doFilter(req, res);
|
||||||
|
@@ -28,9 +28,9 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
|||||||
*/
|
*/
|
||||||
public class StatelessLoginFilter extends AbstractAuthenticationProcessingFilter {
|
public class StatelessLoginFilter extends AbstractAuthenticationProcessingFilter {
|
||||||
|
|
||||||
private AuthenticationManager authenticationManager;
|
protected AuthenticationManager authenticationManager;
|
||||||
|
|
||||||
private RestAuthenticationService restAuthenticationService;
|
protected RestAuthenticationService restAuthenticationService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterPropertiesSet() {
|
public void afterPropertiesSet() {
|
||||||
@@ -63,7 +63,7 @@ public class StatelessLoginFilter extends AbstractAuthenticationProcessingFilter
|
|||||||
Authentication auth) throws IOException, ServletException {
|
Authentication auth) throws IOException, ServletException {
|
||||||
|
|
||||||
DSpaceAuthentication dSpaceAuthentication = (DSpaceAuthentication) auth;
|
DSpaceAuthentication dSpaceAuthentication = (DSpaceAuthentication) auth;
|
||||||
restAuthenticationService.addAuthenticationDataForUser(req, res, dSpaceAuthentication);
|
restAuthenticationService.addAuthenticationDataForUser(req, res, dSpaceAuthentication, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@@ -109,6 +109,12 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
|
|||||||
restAuthenticationService),
|
restAuthenticationService),
|
||||||
LogoutFilter.class)
|
LogoutFilter.class)
|
||||||
|
|
||||||
|
//Add a filter before our shibboleth endpoints to do the authentication based on the data in the
|
||||||
|
// HTTP request
|
||||||
|
.addFilterBefore(new ShibbolethAuthenticationFilter("/api/authn/shibboleth", authenticationManager(),
|
||||||
|
restAuthenticationService),
|
||||||
|
LogoutFilter.class)
|
||||||
|
|
||||||
// Add a custom Token based authentication filter based on the token previously given to the client
|
// Add a custom Token based authentication filter based on the token previously given to the client
|
||||||
// before each URL
|
// before each URL
|
||||||
.addFilterBefore(new StatelessAuthenticationFilter(authenticationManager(), restAuthenticationService,
|
.addFilterBefore(new StatelessAuthenticationFilter(authenticationManager(), restAuthenticationService,
|
||||||
|
@@ -12,6 +12,7 @@ import java.sql.SQLException;
|
|||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import javax.servlet.http.Cookie;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
@@ -37,11 +38,13 @@ import org.springframework.stereotype.Component;
|
|||||||
*
|
*
|
||||||
* @author Frederic Van Reet (frederic dot vanreet at atmire dot com)
|
* @author Frederic Van Reet (frederic dot vanreet at atmire dot com)
|
||||||
* @author Tom Desair (tom dot desair at atmire dot com)
|
* @author Tom Desair (tom dot desair at atmire dot com)
|
||||||
|
* @author Giuseppe Digilio (giuseppe dot digilio at 4science dot it)
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
public class JWTTokenRestAuthenticationServiceImpl implements RestAuthenticationService, InitializingBean {
|
public class JWTTokenRestAuthenticationServiceImpl implements RestAuthenticationService, InitializingBean {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(RestAuthenticationService.class);
|
private static final Logger log = LoggerFactory.getLogger(RestAuthenticationService.class);
|
||||||
|
private static final String AUTHORIZATION_COOKIE = "Authorization-cookie";
|
||||||
private static final String AUTHORIZATION_HEADER = "Authorization";
|
private static final String AUTHORIZATION_HEADER = "Authorization";
|
||||||
private static final String AUTHORIZATION_TYPE = "Bearer";
|
private static final String AUTHORIZATION_TYPE = "Bearer";
|
||||||
|
|
||||||
@@ -61,7 +64,7 @@ public class JWTTokenRestAuthenticationServiceImpl implements RestAuthentication
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addAuthenticationDataForUser(HttpServletRequest request, HttpServletResponse response,
|
public void addAuthenticationDataForUser(HttpServletRequest request, HttpServletResponse response,
|
||||||
DSpaceAuthentication authentication) throws IOException {
|
DSpaceAuthentication authentication, boolean addCookie) throws IOException {
|
||||||
try {
|
try {
|
||||||
Context context = ContextUtil.obtainContext(request);
|
Context context = ContextUtil.obtainContext(request);
|
||||||
context.setCurrentUser(ePersonService.findByEmail(context, authentication.getName()));
|
context.setCurrentUser(ePersonService.findByEmail(context, authentication.getName()));
|
||||||
@@ -71,7 +74,7 @@ public class JWTTokenRestAuthenticationServiceImpl implements RestAuthentication
|
|||||||
String token = jwtTokenHandler.createTokenForEPerson(context, request,
|
String token = jwtTokenHandler.createTokenForEPerson(context, request,
|
||||||
authentication.getPreviousLoginDate(), groups);
|
authentication.getPreviousLoginDate(), groups);
|
||||||
|
|
||||||
addTokenToResponse(response, token);
|
addTokenToResponse(response, token, addCookie);
|
||||||
context.commit();
|
context.commit();
|
||||||
|
|
||||||
} catch (JOSEException e) {
|
} catch (JOSEException e) {
|
||||||
@@ -99,15 +102,26 @@ public class JWTTokenRestAuthenticationServiceImpl implements RestAuthentication
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasAuthenticationData(HttpServletRequest request) {
|
public boolean hasAuthenticationData(HttpServletRequest request) {
|
||||||
return StringUtils.isNotBlank(request.getHeader(AUTHORIZATION_HEADER));
|
return StringUtils.isNotBlank(request.getHeader(AUTHORIZATION_HEADER))
|
||||||
|
|| StringUtils.isNotBlank(getAuthorizationCookie(request));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void invalidateAuthenticationData(HttpServletRequest request, Context context) throws Exception {
|
public void invalidateAuthenticationData(HttpServletRequest request, HttpServletResponse response,
|
||||||
|
Context context) throws Exception {
|
||||||
String token = getToken(request);
|
String token = getToken(request);
|
||||||
|
invalidateAuthenticationCookie(response);
|
||||||
jwtTokenHandler.invalidateToken(token, request, context);
|
jwtTokenHandler.invalidateToken(token, request, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invalidateAuthenticationCookie(HttpServletResponse response) {
|
||||||
|
Cookie cookie = new Cookie(AUTHORIZATION_COOKIE, "");
|
||||||
|
cookie.setHttpOnly(true);
|
||||||
|
cookie.setMaxAge(0);
|
||||||
|
response.addCookie(cookie);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AuthenticationService getAuthenticationService() {
|
public AuthenticationService getAuthenticationService() {
|
||||||
return authenticationService;
|
return authenticationService;
|
||||||
@@ -140,18 +154,42 @@ public class JWTTokenRestAuthenticationServiceImpl implements RestAuthentication
|
|||||||
return wwwAuthenticate.toString();
|
return wwwAuthenticate.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addTokenToResponse(final HttpServletResponse response, final String token) throws IOException {
|
private void addTokenToResponse(final HttpServletResponse response, final String token, final Boolean addCookie)
|
||||||
|
throws IOException {
|
||||||
|
// we need authentication cookies because Shibboleth can't use the authentication headers due to the redirects
|
||||||
|
if (addCookie) {
|
||||||
|
Cookie cookie = new Cookie(AUTHORIZATION_COOKIE, token);
|
||||||
|
cookie.setHttpOnly(true);
|
||||||
|
cookie.setSecure(true);
|
||||||
|
response.addCookie(cookie);
|
||||||
|
}
|
||||||
response.setHeader(AUTHORIZATION_HEADER, String.format("%s %s", AUTHORIZATION_TYPE, token));
|
response.setHeader(AUTHORIZATION_HEADER, String.format("%s %s", AUTHORIZATION_TYPE, token));
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getToken(HttpServletRequest request) {
|
private String getToken(HttpServletRequest request) {
|
||||||
|
String tokenValue = null;
|
||||||
String authHeader = request.getHeader(AUTHORIZATION_HEADER);
|
String authHeader = request.getHeader(AUTHORIZATION_HEADER);
|
||||||
|
String authCookie = getAuthorizationCookie(request);
|
||||||
if (StringUtils.isNotBlank(authHeader)) {
|
if (StringUtils.isNotBlank(authHeader)) {
|
||||||
String tokenValue = authHeader.replace(AUTHORIZATION_TYPE, "").trim();
|
tokenValue = authHeader.replace(AUTHORIZATION_TYPE, "").trim();
|
||||||
return tokenValue;
|
} else if (StringUtils.isNotBlank(authCookie)) {
|
||||||
} else {
|
tokenValue = authCookie;
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return tokenValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getAuthorizationCookie(HttpServletRequest request) {
|
||||||
|
String authCookie = "";
|
||||||
|
Cookie[] cookies = request.getCookies();
|
||||||
|
if (cookies != null) {
|
||||||
|
for (Cookie cookie : cookies) {
|
||||||
|
if (cookie.getName().equals(AUTHORIZATION_COOKIE) && StringUtils.isNotEmpty(cookie.getValue())) {
|
||||||
|
authCookie = cookie.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return authCookie;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -21,6 +21,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
|||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
|
import javax.servlet.http.Cookie;
|
||||||
|
|
||||||
import org.dspace.app.rest.builder.GroupBuilder;
|
import org.dspace.app.rest.builder.GroupBuilder;
|
||||||
import org.dspace.app.rest.matcher.AuthenticationStatusMatcher;
|
import org.dspace.app.rest.matcher.AuthenticationStatusMatcher;
|
||||||
@@ -40,6 +41,7 @@ import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
|
|||||||
*
|
*
|
||||||
* @author Frederic Van Reet (frederic dot vanreet at atmire dot com)
|
* @author Frederic Van Reet (frederic dot vanreet at atmire dot com)
|
||||||
* @author Tom Desair (tom dot desair at atmire dot com)
|
* @author Tom Desair (tom dot desair at atmire dot com)
|
||||||
|
* @author Giuseppe Digilio (giuseppe dot digilio at 4science dot it)
|
||||||
*/
|
*/
|
||||||
public class AuthenticationRestControllerIT extends AbstractControllerIntegrationTest {
|
public class AuthenticationRestControllerIT extends AbstractControllerIntegrationTest {
|
||||||
|
|
||||||
@@ -48,6 +50,9 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio
|
|||||||
|
|
||||||
public static final String[] PASS_ONLY = {"org.dspace.authenticate.PasswordAuthentication"};
|
public static final String[] PASS_ONLY = {"org.dspace.authenticate.PasswordAuthentication"};
|
||||||
public static final String[] SHIB_ONLY = {"org.dspace.authenticate.ShibAuthentication"};
|
public static final String[] SHIB_ONLY = {"org.dspace.authenticate.ShibAuthentication"};
|
||||||
|
public static final String[] SHIB_AND_PASS =
|
||||||
|
{"org.dspace.authenticate.ShibAuthentication",
|
||||||
|
"org.dspace.authenticate.PasswordAuthentication"};
|
||||||
public static final String[] SHIB_AND_IP =
|
public static final String[] SHIB_AND_IP =
|
||||||
{"org.dspace.authenticate.IPAuthentication",
|
{"org.dspace.authenticate.IPAuthentication",
|
||||||
"org.dspace.authenticate.ShibAuthentication"};
|
"org.dspace.authenticate.ShibAuthentication"};
|
||||||
@@ -97,7 +102,43 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio
|
|||||||
.andExpect(content().contentType(contentType))
|
.andExpect(content().contentType(contentType))
|
||||||
.andExpect(jsonPath("$.okay", is(true)))
|
.andExpect(jsonPath("$.okay", is(true)))
|
||||||
.andExpect(jsonPath("$.authenticated", is(false)))
|
.andExpect(jsonPath("$.authenticated", is(false)))
|
||||||
|
.andExpect(jsonPath("$.type", is("status")))
|
||||||
|
.andExpect(header().string("WWW-Authenticate",
|
||||||
|
"password realm=\"DSpace REST API\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStatusAuthenticatedWithCookie() throws Exception {
|
||||||
|
context.turnOffAuthorisationSystem();
|
||||||
|
//Enable Shibboleth login
|
||||||
|
configurationService.setProperty("plugin.sequence.org.dspace.authenticate.AuthenticationMethod", SHIB_ONLY);
|
||||||
|
|
||||||
|
context.restoreAuthSystemState();
|
||||||
|
|
||||||
|
//Simulate that a shibboleth authentication has happened
|
||||||
|
String token = getClient().perform(post("/api/authn/login")
|
||||||
|
.requestAttr("SHIB-MAIL", eperson.getEmail())
|
||||||
|
.requestAttr("SHIB-SCOPED-AFFILIATION", "faculty;staff"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andReturn().getResponse().getHeader(AUTHORIZATION_HEADER).replace("Bearer ", "");
|
||||||
|
|
||||||
|
Cookie[] cookies = new Cookie[1];
|
||||||
|
cookies[0] = new Cookie(AUTHORIZATION_COOKIE, token);
|
||||||
|
|
||||||
|
//Check if we are authenticated with a status request with authorization cookie
|
||||||
|
getClient().perform(get("/api/authn/status")
|
||||||
|
.secure(true)
|
||||||
|
.cookie(cookies))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
//We expect the content type to be "application/hal+json;charset=UTF-8"
|
||||||
|
.andExpect(content().contentType(contentType))
|
||||||
|
.andExpect(jsonPath("$.okay", is(true)))
|
||||||
|
.andExpect(jsonPath("$.authenticated", is(true)))
|
||||||
.andExpect(jsonPath("$.type", is("status")));
|
.andExpect(jsonPath("$.type", is("status")));
|
||||||
|
|
||||||
|
//Logout
|
||||||
|
getClient(token).perform(get("/api/authn/logout"))
|
||||||
|
.andExpect(status().isNoContent());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -354,6 +395,108 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio
|
|||||||
.andExpect(status().isMethodNotAllowed());
|
.andExpect(status().isMethodNotAllowed());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testShibbolethLoginURLWithDefaultLazyURL() throws Exception {
|
||||||
|
context.turnOffAuthorisationSystem();
|
||||||
|
//Enable Shibboleth login
|
||||||
|
configurationService.setProperty("plugin.sequence.org.dspace.authenticate.AuthenticationMethod", SHIB_ONLY);
|
||||||
|
|
||||||
|
//Create a reviewers group
|
||||||
|
Group reviewersGroup = GroupBuilder.createGroup(context)
|
||||||
|
.withName("Reviewers")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
//Faculty members are assigned to the Reviewers group
|
||||||
|
configurationService.setProperty("authentication-shibboleth.role.faculty", "Reviewers");
|
||||||
|
context.restoreAuthSystemState();
|
||||||
|
|
||||||
|
getClient().perform(post("/api/authn/login").header("Referer", "http://my.uni.edu"))
|
||||||
|
.andExpect(status().isUnauthorized())
|
||||||
|
.andExpect(header().string("WWW-Authenticate",
|
||||||
|
"shibboleth realm=\"DSpace REST API\", " +
|
||||||
|
"location=\"https://localhost/Shibboleth.sso/Login?" +
|
||||||
|
"target=http%3A%2F%2Flocalhost%2Fapi%2Fauthn%2Fshibboleth%3F" +
|
||||||
|
"redirectUrl%3Dhttp%3A%2F%2Fmy.uni.edu\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testShibbolethLoginURLWithServerlURLConteiningPort() throws Exception {
|
||||||
|
context.turnOffAuthorisationSystem();
|
||||||
|
//Enable Shibboleth login
|
||||||
|
configurationService.setProperty("plugin.sequence.org.dspace.authenticate.AuthenticationMethod", SHIB_ONLY);
|
||||||
|
configurationService.setProperty("dspace.server.url", "http://localhost:8080/server");
|
||||||
|
configurationService.setProperty("authentication-shibboleth.lazysession.secure", false);
|
||||||
|
|
||||||
|
//Create a reviewers group
|
||||||
|
Group reviewersGroup = GroupBuilder.createGroup(context)
|
||||||
|
.withName("Reviewers")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
//Faculty members are assigned to the Reviewers group
|
||||||
|
configurationService.setProperty("authentication-shibboleth.role.faculty", "Reviewers");
|
||||||
|
context.restoreAuthSystemState();
|
||||||
|
|
||||||
|
getClient().perform(post("/api/authn/login").header("Referer", "http://my.uni.edu"))
|
||||||
|
.andExpect(status().isUnauthorized())
|
||||||
|
.andExpect(header().string("WWW-Authenticate",
|
||||||
|
"shibboleth realm=\"DSpace REST API\", " +
|
||||||
|
"location=\"http://localhost:8080/Shibboleth.sso/Login?" +
|
||||||
|
"target=http%3A%2F%2Flocalhost%3A8080%2Fserver%2Fapi%2Fauthn%2Fshibboleth%3F" +
|
||||||
|
"redirectUrl%3Dhttp%3A%2F%2Fmy.uni.edu\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testShibbolethLoginURLWithConfiguredLazyURL() throws Exception {
|
||||||
|
context.turnOffAuthorisationSystem();
|
||||||
|
//Enable Shibboleth login
|
||||||
|
configurationService.setProperty("plugin.sequence.org.dspace.authenticate.AuthenticationMethod", SHIB_ONLY);
|
||||||
|
configurationService.setProperty("authentication-shibboleth.lazysession.loginurl",
|
||||||
|
"http://shibboleth.org/Shibboleth.sso/Login");
|
||||||
|
|
||||||
|
//Create a reviewers group
|
||||||
|
Group reviewersGroup = GroupBuilder.createGroup(context)
|
||||||
|
.withName("Reviewers")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
//Faculty members are assigned to the Reviewers group
|
||||||
|
configurationService.setProperty("authentication-shibboleth.role.faculty", "Reviewers");
|
||||||
|
context.restoreAuthSystemState();
|
||||||
|
|
||||||
|
getClient().perform(post("/api/authn/login").header("Referer", "http://my.uni.edu"))
|
||||||
|
.andExpect(status().isUnauthorized())
|
||||||
|
.andExpect(header().string("WWW-Authenticate",
|
||||||
|
"shibboleth realm=\"DSpace REST API\", " +
|
||||||
|
"location=\"http://shibboleth.org/Shibboleth.sso/Login?" +
|
||||||
|
"target=http%3A%2F%2Flocalhost%2Fapi%2Fauthn%2Fshibboleth%3F" +
|
||||||
|
"redirectUrl%3Dhttp%3A%2F%2Fmy.uni.edu\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testShibbolethLoginURLWithConfiguredLazyURLWithPort() throws Exception {
|
||||||
|
context.turnOffAuthorisationSystem();
|
||||||
|
//Enable Shibboleth login
|
||||||
|
configurationService.setProperty("plugin.sequence.org.dspace.authenticate.AuthenticationMethod", SHIB_ONLY);
|
||||||
|
configurationService.setProperty("authentication-shibboleth.lazysession.loginurl",
|
||||||
|
"http://shibboleth.org:8080/Shibboleth.sso/Login");
|
||||||
|
|
||||||
|
//Create a reviewers group
|
||||||
|
Group reviewersGroup = GroupBuilder.createGroup(context)
|
||||||
|
.withName("Reviewers")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
//Faculty members are assigned to the Reviewers group
|
||||||
|
configurationService.setProperty("authentication-shibboleth.role.faculty", "Reviewers");
|
||||||
|
context.restoreAuthSystemState();
|
||||||
|
|
||||||
|
getClient().perform(post("/api/authn/login").header("Referer", "http://my.uni.edu"))
|
||||||
|
.andExpect(status().isUnauthorized())
|
||||||
|
.andExpect(header().string("WWW-Authenticate",
|
||||||
|
"shibboleth realm=\"DSpace REST API\", " +
|
||||||
|
"location=\"http://shibboleth.org:8080/Shibboleth.sso/Login?" +
|
||||||
|
"target=http%3A%2F%2Flocalhost%2Fapi%2Fauthn%2Fshibboleth%3F" +
|
||||||
|
"redirectUrl%3Dhttp%3A%2F%2Fmy.uni.edu\""));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore
|
@Ignore
|
||||||
// Ignored until an endpoint is added to return all groups
|
// Ignored until an endpoint is added to return all groups
|
||||||
@@ -375,7 +518,9 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio
|
|||||||
.andExpect(status().isUnauthorized())
|
.andExpect(status().isUnauthorized())
|
||||||
.andExpect(header().string("WWW-Authenticate",
|
.andExpect(header().string("WWW-Authenticate",
|
||||||
"shibboleth realm=\"DSpace REST API\", " +
|
"shibboleth realm=\"DSpace REST API\", " +
|
||||||
"location=\"/Shibboleth.sso/Login?target=http%3A%2F%2Fmy.uni.edu\""));
|
"location=\"https://localhost/Shibboleth.sso/Login?" +
|
||||||
|
"target=http%3A%2F%2Flocalhost%2Fapi%2Fauthn%2Fshibboleth%3F" +
|
||||||
|
"redirectUrl%3Dhttp%3A%2F%2Fmy.uni.edu\""));
|
||||||
|
|
||||||
//Simulate that a shibboleth authentication has happened
|
//Simulate that a shibboleth authentication has happened
|
||||||
|
|
||||||
@@ -411,7 +556,9 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio
|
|||||||
.andExpect(status().isUnauthorized())
|
.andExpect(status().isUnauthorized())
|
||||||
.andExpect(header().string("WWW-Authenticate",
|
.andExpect(header().string("WWW-Authenticate",
|
||||||
"ip realm=\"DSpace REST API\", shibboleth realm=\"DSpace REST API\", " +
|
"ip realm=\"DSpace REST API\", shibboleth realm=\"DSpace REST API\", " +
|
||||||
"location=\"/Shibboleth.sso/Login?target=http%3A%2F%2Fmy.uni.edu\""));
|
"location=\"https://localhost/Shibboleth.sso/Login?" +
|
||||||
|
"target=http%3A%2F%2Flocalhost%2Fapi%2Fauthn%2Fshibboleth%3F" +
|
||||||
|
"redirectUrl%3Dhttp%3A%2F%2Fmy.uni.edu\""));
|
||||||
|
|
||||||
//Simulate that a shibboleth authentication has happened
|
//Simulate that a shibboleth authentication has happened
|
||||||
String token = getClient().perform(post("/api/authn/login")
|
String token = getClient().perform(post("/api/authn/login")
|
||||||
@@ -454,4 +601,164 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio
|
|||||||
EPersonMatcher.matchEPersonWithGroups(eperson.getEmail(), "Anonymous")));
|
EPersonMatcher.matchEPersonWithGroups(eperson.getEmail(), "Anonymous")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testShibbolethAndPasswordAuthentication() throws Exception {
|
||||||
|
context.turnOffAuthorisationSystem();
|
||||||
|
//Enable Shibboleth and password login
|
||||||
|
configurationService.setProperty("plugin.sequence.org.dspace.authenticate.AuthenticationMethod", SHIB_AND_PASS);
|
||||||
|
|
||||||
|
context.restoreAuthSystemState();
|
||||||
|
|
||||||
|
//Check if WWW-Authenticate header contains shibboleth and password
|
||||||
|
getClient().perform(get("/api/authn/status").header("Referer", "http://my.uni.edu"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(header().string("WWW-Authenticate",
|
||||||
|
"shibboleth realm=\"DSpace REST API\", " +
|
||||||
|
"location=\"https://localhost/Shibboleth.sso/Login?" +
|
||||||
|
"target=http%3A%2F%2Flocalhost%2Fapi%2Fauthn%2Fshibboleth%3F" +
|
||||||
|
"redirectUrl%3Dhttp%3A%2F%2Fmy.uni.edu\"" +
|
||||||
|
", password realm=\"DSpace REST API\""));
|
||||||
|
|
||||||
|
//Simulate a password authentication
|
||||||
|
String token = getAuthToken(eperson.getEmail(), password);
|
||||||
|
|
||||||
|
//Check if we have a valid token
|
||||||
|
getClient(token).perform(get("/api/authn/status"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.okay", is(true)))
|
||||||
|
.andExpect(jsonPath("$.authenticated", is(true)))
|
||||||
|
.andExpect(jsonPath("$.type", is("status")));
|
||||||
|
|
||||||
|
//Logout
|
||||||
|
getClient(token).perform(get("/api/authn/logout"))
|
||||||
|
.andExpect(status().isNoContent());
|
||||||
|
|
||||||
|
//Check if we are actually logged out
|
||||||
|
getClient(token).perform(get("/api/authn/status"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.okay", is(true)))
|
||||||
|
.andExpect(jsonPath("$.authenticated", is(false)))
|
||||||
|
.andExpect(jsonPath("$.type", is("status")));
|
||||||
|
|
||||||
|
//Simulate that a shibboleth authentication has happened
|
||||||
|
token = getClient().perform(post("/api/authn/login")
|
||||||
|
.requestAttr("SHIB-MAIL", eperson.getEmail())
|
||||||
|
.requestAttr("SHIB-SCOPED-AFFILIATION", "faculty;staff"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andReturn().getResponse().getHeader(AUTHORIZATION_HEADER).replace("Bearer ", "");
|
||||||
|
|
||||||
|
//Check if we have a valid token
|
||||||
|
getClient(token).perform(get("/api/authn/status"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.okay", is(true)))
|
||||||
|
.andExpect(jsonPath("$.authenticated", is(true)))
|
||||||
|
.andExpect(jsonPath("$.type", is("status")));
|
||||||
|
|
||||||
|
//Logout
|
||||||
|
getClient(token).perform(get("/api/authn/logout"))
|
||||||
|
.andExpect(status().isNoContent());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnlyPasswordAuthenticationWorks() throws Exception {
|
||||||
|
context.turnOffAuthorisationSystem();
|
||||||
|
//Enable only password login
|
||||||
|
configurationService.setProperty("plugin.sequence.org.dspace.authenticate.AuthenticationMethod", PASS_ONLY);
|
||||||
|
|
||||||
|
context.restoreAuthSystemState();
|
||||||
|
|
||||||
|
//Check if WWW-Authenticate header contains only
|
||||||
|
getClient().perform(get("/api/authn/status").header("Referer", "http://my.uni.edu"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(header().string("WWW-Authenticate",
|
||||||
|
"password realm=\"DSpace REST API\""));
|
||||||
|
|
||||||
|
//Simulate a password authentication
|
||||||
|
String token = getAuthToken(eperson.getEmail(), password);
|
||||||
|
|
||||||
|
//Check if we have a valid token
|
||||||
|
getClient(token).perform(get("/api/authn/status"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.okay", is(true)))
|
||||||
|
.andExpect(jsonPath("$.authenticated", is(true)))
|
||||||
|
.andExpect(jsonPath("$.type", is("status")));
|
||||||
|
|
||||||
|
//Logout
|
||||||
|
getClient(token).perform(get("/api/authn/logout"))
|
||||||
|
.andExpect(status().isNoContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testShibbolethAuthenticationDoesNotWorkWithPassOnly() throws Exception {
|
||||||
|
context.turnOffAuthorisationSystem();
|
||||||
|
//Enable only password login
|
||||||
|
configurationService.setProperty("plugin.sequence.org.dspace.authenticate.AuthenticationMethod", PASS_ONLY);
|
||||||
|
|
||||||
|
context.restoreAuthSystemState();
|
||||||
|
|
||||||
|
//Check if WWW-Authenticate header contains only password
|
||||||
|
getClient().perform(get("/api/authn/status").header("Referer", "http://my.uni.edu"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(header().string("WWW-Authenticate",
|
||||||
|
"password realm=\"DSpace REST API\""));
|
||||||
|
|
||||||
|
//Check if a shibboleth authentication fails
|
||||||
|
getClient().perform(post("/api/authn/login")
|
||||||
|
.requestAttr("SHIB-MAIL", eperson.getEmail())
|
||||||
|
.requestAttr("SHIB-SCOPED-AFFILIATION", "faculty;staff"))
|
||||||
|
.andExpect(status().isUnauthorized());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnlyShibbolethAuthenticationWorks() throws Exception {
|
||||||
|
context.turnOffAuthorisationSystem();
|
||||||
|
//Enable only Shibboleth login
|
||||||
|
configurationService.setProperty("plugin.sequence.org.dspace.authenticate.AuthenticationMethod", SHIB_ONLY);
|
||||||
|
|
||||||
|
context.restoreAuthSystemState();
|
||||||
|
|
||||||
|
//Check if WWW-Authenticate header contains only shibboleth
|
||||||
|
getClient().perform(get("/api/authn/status").header("Referer", "http://my.uni.edu"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(header().string("WWW-Authenticate",
|
||||||
|
"shibboleth realm=\"DSpace REST API\", " +
|
||||||
|
"location=\"https://localhost/Shibboleth.sso/Login?" +
|
||||||
|
"target=http%3A%2F%2Flocalhost%2Fapi%2Fauthn%2Fshibboleth%3F" +
|
||||||
|
"redirectUrl%3Dhttp%3A%2F%2Fmy.uni.edu\""));
|
||||||
|
|
||||||
|
//Simulate that a shibboleth authentication has happened
|
||||||
|
String token = getClient().perform(post("/api/authn/login")
|
||||||
|
.requestAttr("SHIB-MAIL", eperson.getEmail())
|
||||||
|
.requestAttr("SHIB-SCOPED-AFFILIATION", "faculty;staff"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andReturn().getResponse().getHeader(AUTHORIZATION_HEADER);
|
||||||
|
|
||||||
|
//Logout
|
||||||
|
getClient(token).perform(get("/api/authn/logout"))
|
||||||
|
.andExpect(status().isNoContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPasswordAuthenticationDoesNotWorkWithShibOnly() throws Exception {
|
||||||
|
context.turnOffAuthorisationSystem();
|
||||||
|
//Enable only Shibboleth login
|
||||||
|
configurationService.setProperty("plugin.sequence.org.dspace.authenticate.AuthenticationMethod", SHIB_ONLY);
|
||||||
|
|
||||||
|
//Create a reviewers group
|
||||||
|
Group reviewersGroup = GroupBuilder.createGroup(context)
|
||||||
|
.withName("Reviewers")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
//Faculty members are assigned to the Reviewers group
|
||||||
|
configurationService.setProperty("authentication-shibboleth.role.faculty", "Reviewers");
|
||||||
|
context.restoreAuthSystemState();
|
||||||
|
|
||||||
|
getClient().perform(post("/api/authn/login")
|
||||||
|
.param("user", eperson.getEmail())
|
||||||
|
.param("password", password))
|
||||||
|
.andExpect(status().isUnauthorized());
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* The contents of this file are subject to the license and copyright
|
||||||
|
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||||
|
* tree and available online at
|
||||||
|
*
|
||||||
|
* http://www.dspace.org/license/
|
||||||
|
*/
|
||||||
|
package org.dspace.app.rest;
|
||||||
|
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
|
import org.dspace.app.rest.test.AbstractControllerIntegrationTest;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Integration test that cover ShibbolethRestController
|
||||||
|
*
|
||||||
|
* @author Giuseppe Digilio (giuseppe dot digilio at 4science dot it)
|
||||||
|
*/
|
||||||
|
public class ShibbolethRestControllerIT extends AbstractControllerIntegrationTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRedirectToDefaultDspaceUrl() throws Exception {
|
||||||
|
String token = getAuthToken(eperson.getEmail(), password);
|
||||||
|
|
||||||
|
getClient(token).perform(get("/api/authn/shibboleth"))
|
||||||
|
.andExpect(status().is3xxRedirection())
|
||||||
|
.andExpect(redirectedUrl("http://localhost:3000"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRedirectToGivenUrl() throws Exception {
|
||||||
|
String token = getAuthToken(eperson.getEmail(), password);
|
||||||
|
|
||||||
|
getClient(token).perform(get("/api/authn/shibboleth")
|
||||||
|
.param("redirectUrl", "http://dspace.org"))
|
||||||
|
.andExpect(status().is3xxRedirection())
|
||||||
|
.andExpect(redirectedUrl("http://dspace.org"));
|
||||||
|
}
|
||||||
|
}
|
@@ -71,6 +71,7 @@ import org.springframework.web.context.WebApplicationContext;
|
|||||||
public class AbstractControllerIntegrationTest extends AbstractIntegrationTestWithDatabase {
|
public class AbstractControllerIntegrationTest extends AbstractIntegrationTestWithDatabase {
|
||||||
|
|
||||||
protected static final String AUTHORIZATION_HEADER = "Authorization";
|
protected static final String AUTHORIZATION_HEADER = "Authorization";
|
||||||
|
protected static final String AUTHORIZATION_COOKIE = "Authorization-cookie";
|
||||||
|
|
||||||
//The Authorization header contains a value like "Bearer TOKENVALUE". This constant string represents the part that
|
//The Authorization header contains a value like "Bearer TOKENVALUE". This constant string represents the part that
|
||||||
//sits before the actual authentication token and can be used to easily compose or parse the Authorization header.
|
//sits before the actual authentication token and can be used to easily compose or parse the Authorization header.
|
||||||
|
@@ -38,6 +38,8 @@
|
|||||||
authentication-shibboleth.lazysession = true
|
authentication-shibboleth.lazysession = true
|
||||||
|
|
||||||
# The url to start a shibboleth session (only for lazy sessions)
|
# The url to start a shibboleth session (only for lazy sessions)
|
||||||
|
# This must contain an absolute URL (e.g. http://dsapce-org/Shibboleth.sso/Login) or
|
||||||
|
# a relative path that start with slash (e.g. /Shibboleth.sso/Login)
|
||||||
authentication-shibboleth.lazysession.loginurl = /Shibboleth.sso/Login
|
authentication-shibboleth.lazysession.loginurl = /Shibboleth.sso/Login
|
||||||
|
|
||||||
# Force HTTPS when authenticating (only for lazy sessions)
|
# Force HTTPS when authenticating (only for lazy sessions)
|
||||||
|
Reference in New Issue
Block a user