mirror of
https://github.com/DSpace/DSpace.git
synced 2025-10-17 06:53:09 +00:00
85276: Store and retrieve the Authentication method in the JWT
This commit is contained in:
@@ -216,4 +216,12 @@ public interface AuthenticationMethod {
|
||||
* @return The authentication method name
|
||||
*/
|
||||
public String getName();
|
||||
|
||||
/**
|
||||
* Get whether the authentication method is being used.
|
||||
* @param context The DSpace context
|
||||
* @param request The current request
|
||||
* @return whether the authentication method is being used.
|
||||
*/
|
||||
public boolean isUsed(Context context, HttpServletRequest request);
|
||||
}
|
||||
|
@@ -193,4 +193,17 @@ public class AuthenticationServiceImpl implements AuthenticationService {
|
||||
public Iterator<AuthenticationMethod> authenticationMethodIterator() {
|
||||
return getAuthenticationMethodStack().iterator();
|
||||
}
|
||||
|
||||
public String getAuthenticationMethod(final Context context, final HttpServletRequest request) {
|
||||
final Iterator<AuthenticationMethod> authenticationMethodIterator = authenticationMethodIterator();
|
||||
|
||||
while (authenticationMethodIterator.hasNext()) {
|
||||
final AuthenticationMethod authenticationMethod = authenticationMethodIterator.next();
|
||||
if (authenticationMethod.isUsed(context, request)) {
|
||||
return authenticationMethod.getName();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@@ -273,4 +273,9 @@ public class IPAuthentication implements AuthenticationMethod {
|
||||
public String getName() {
|
||||
return "ip";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUsed(final Context context, final HttpServletRequest request) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@@ -83,6 +83,9 @@ public class LDAPAuthentication
|
||||
protected ConfigurationService configurationService
|
||||
= DSpaceServicesFactory.getInstance().getConfigurationService();
|
||||
|
||||
private static final String LDAP_AUTHENTICATED = "ldap.authenticated";
|
||||
|
||||
|
||||
/**
|
||||
* Let a real auth method return true if it wants.
|
||||
*
|
||||
@@ -261,6 +264,7 @@ public class LDAPAuthentication
|
||||
|
||||
if (ldap.ldapAuthenticate(dn, password, context)) {
|
||||
context.setCurrentUser(eperson);
|
||||
request.getSession().setAttribute(LDAP_AUTHENTICATED, true);
|
||||
|
||||
// assign user to groups based on ldap dn
|
||||
assignGroups(dn, ldap.ldapGroup, context);
|
||||
@@ -311,6 +315,8 @@ public class LDAPAuthentication
|
||||
context.dispatchEvents();
|
||||
context.restoreAuthSystemState();
|
||||
context.setCurrentUser(eperson);
|
||||
request.getSession().setAttribute(LDAP_AUTHENTICATED, true);
|
||||
|
||||
|
||||
// assign user to groups based on ldap dn
|
||||
assignGroups(dn, ldap.ldapGroup, context);
|
||||
@@ -341,6 +347,8 @@ public class LDAPAuthentication
|
||||
ePersonService.update(context, eperson);
|
||||
context.dispatchEvents();
|
||||
context.setCurrentUser(eperson);
|
||||
request.getSession().setAttribute(LDAP_AUTHENTICATED, true);
|
||||
|
||||
|
||||
// assign user to groups based on ldap dn
|
||||
assignGroups(dn, ldap.ldapGroup, context);
|
||||
@@ -734,4 +742,14 @@ public class LDAPAuthentication
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUsed(final Context context, final HttpServletRequest request) {
|
||||
if (request != null &&
|
||||
context.getCurrentUser() != null &&
|
||||
request.getSession().getAttribute(LDAP_AUTHENTICATED) != null) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@@ -51,6 +51,9 @@ public class PasswordAuthentication
|
||||
*/
|
||||
private static final Logger log = LogManager.getLogger();
|
||||
|
||||
private static final String PASSWORD_AUTHENTICATED = "password.authenticated";
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Look to see if this email address is allowed to register.
|
||||
@@ -216,6 +219,7 @@ public class PasswordAuthentication
|
||||
.checkPassword(context, eperson, password)) {
|
||||
// login is ok if password matches:
|
||||
context.setCurrentUser(eperson);
|
||||
request.getSession().setAttribute(PASSWORD_AUTHENTICATED, true);
|
||||
log.info(LogHelper.getHeader(context, "authenticate", "type=PasswordAuthentication"));
|
||||
return SUCCESS;
|
||||
} else {
|
||||
@@ -247,4 +251,15 @@ public class PasswordAuthentication
|
||||
public String getName() {
|
||||
return "password";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isUsed(final Context context, final HttpServletRequest request) {
|
||||
if (request != null &&
|
||||
context.getCurrentUser() != null &&
|
||||
request.getSession().getAttribute(PASSWORD_AUTHENTICATED) != null) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@@ -1283,5 +1283,14 @@ public class ShibAuthentication implements AuthenticationMethod {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUsed(final Context context, final HttpServletRequest request) {
|
||||
if (request != null &&
|
||||
context.getCurrentUser() != null &&
|
||||
request.getSession().getAttribute("shib.authenticated") != null) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -128,6 +128,8 @@ public class X509Authentication implements AuthenticationMethod {
|
||||
protected ConfigurationService configurationService =
|
||||
DSpaceServicesFactory.getInstance().getConfigurationService();
|
||||
|
||||
private static final String X509_AUTHENTICATED = "x509.authenticated";
|
||||
|
||||
|
||||
/**
|
||||
* Initialization: Set caPublicKey and/or keystore. This loads the
|
||||
@@ -544,6 +546,7 @@ public class X509Authentication implements AuthenticationMethod {
|
||||
context.dispatchEvents();
|
||||
context.restoreAuthSystemState();
|
||||
context.setCurrentUser(eperson);
|
||||
request.getSession().setAttribute(X509_AUTHENTICATED, true);
|
||||
setSpecialGroupsFlag(request, email);
|
||||
return SUCCESS;
|
||||
} else {
|
||||
@@ -563,6 +566,7 @@ public class X509Authentication implements AuthenticationMethod {
|
||||
log.info(LogHelper.getHeader(context, "login",
|
||||
"type=x509certificate"));
|
||||
context.setCurrentUser(eperson);
|
||||
request.getSession().setAttribute(X509_AUTHENTICATED, true);
|
||||
setSpecialGroupsFlag(request, email);
|
||||
return SUCCESS;
|
||||
}
|
||||
@@ -594,4 +598,14 @@ public class X509Authentication implements AuthenticationMethod {
|
||||
public String getName() {
|
||||
return "x509";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUsed(final Context context, final HttpServletRequest request) {
|
||||
if (request != null &&
|
||||
context.getCurrentUser() != null &&
|
||||
request.getSession().getAttribute(X509_AUTHENTICATED) != null) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@@ -168,4 +168,13 @@ public interface AuthenticationService {
|
||||
*/
|
||||
public Iterator<AuthenticationMethod> authenticationMethodIterator();
|
||||
|
||||
/**
|
||||
* Retrieves the currently used authentication method name based on the context and the request
|
||||
*
|
||||
* @param context A valid DSpace context.
|
||||
* @param request The request that started this operation, or null if not applicable.
|
||||
* @return the currently used authentication method name
|
||||
*/
|
||||
public String getAuthenticationMethod(Context context, HttpServletRequest request);
|
||||
|
||||
}
|
||||
|
@@ -98,6 +98,11 @@ public class Context implements AutoCloseable {
|
||||
*/
|
||||
private List<UUID> specialGroupsPreviousState;
|
||||
|
||||
/**
|
||||
* The currently used authentication method
|
||||
*/
|
||||
private String authenticationMethod;
|
||||
|
||||
/**
|
||||
* Content events
|
||||
*/
|
||||
@@ -890,4 +895,11 @@ public class Context implements AutoCloseable {
|
||||
currentUser = reloadEntity(currentUser);
|
||||
}
|
||||
|
||||
public String getAuthenticationMethod() {
|
||||
return authenticationMethod;
|
||||
}
|
||||
|
||||
public void setAuthenticationMethod(final String authenticationMethod) {
|
||||
this.authenticationMethod = authenticationMethod;
|
||||
}
|
||||
}
|
||||
|
@@ -118,6 +118,7 @@ public class AuthenticationRestController implements InitializingBean {
|
||||
|
||||
response.setHeader("WWW-Authenticate", authenticateHeaderValue);
|
||||
}
|
||||
authenticationStatusRest.setAuthenticationMethod(context.getAuthenticationMethod());
|
||||
authenticationStatusRest.setProjection(projection);
|
||||
AuthenticationStatusResource authenticationStatusResource = converter.toResource(authenticationStatusRest);
|
||||
|
||||
|
@@ -16,6 +16,7 @@ import org.dspace.app.rest.RestResourceController;
|
||||
public class AuthenticationStatusRest extends BaseObjectRest<Integer> {
|
||||
private boolean okay;
|
||||
private boolean authenticated;
|
||||
private String authenticationMethod;
|
||||
|
||||
public static final String NAME = "status";
|
||||
public static final String CATEGORY = RestAddressableModel.AUTHENTICATION;
|
||||
@@ -81,4 +82,12 @@ public class AuthenticationStatusRest extends BaseObjectRest<Integer> {
|
||||
public void setOkay(boolean okay) {
|
||||
this.okay = okay;
|
||||
}
|
||||
|
||||
public String getAuthenticationMethod() {
|
||||
return authenticationMethod;
|
||||
}
|
||||
|
||||
public void setAuthenticationMethod(final String authenticationMethod) {
|
||||
this.authenticationMethod = authenticationMethod;
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* 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.jwt;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.text.ParseException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import com.nimbusds.jwt.JWTClaimsSet;
|
||||
import org.dspace.authenticate.service.AuthenticationService;
|
||||
import org.dspace.core.Context;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Provides a claim for a JSON Web Token, this claim is responsible for adding the authentication method to it
|
||||
*/
|
||||
@Component
|
||||
public class AuthenticationMethodClaimProvider implements JWTClaimProvider {
|
||||
|
||||
public static final String AUTHENTICATION_METHOD = "authenticationMethod";
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(AuthenticationMethodClaimProvider.class);
|
||||
|
||||
@Autowired
|
||||
private AuthenticationService authenticationService;
|
||||
|
||||
public String getKey() {
|
||||
return AUTHENTICATION_METHOD;
|
||||
}
|
||||
|
||||
public Object getValue(final Context context, final HttpServletRequest request) {
|
||||
if (context.getAuthenticationMethod() != null) {
|
||||
return context.getAuthenticationMethod();
|
||||
}
|
||||
return authenticationService.getAuthenticationMethod(context, request);
|
||||
}
|
||||
|
||||
public void parseClaim(final Context context, final HttpServletRequest request, final JWTClaimsSet jwtClaimsSet)
|
||||
throws SQLException {
|
||||
try {
|
||||
context.setAuthenticationMethod(jwtClaimsSet.getStringClaim(AUTHENTICATION_METHOD));
|
||||
} catch (ParseException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
@@ -107,6 +107,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio
|
||||
.andExpect(content().contentType(contentType))
|
||||
.andExpect(jsonPath("$.okay", is(true)))
|
||||
.andExpect(jsonPath("$.authenticated", is(true)))
|
||||
.andExpect(jsonPath("$.authenticationMethod", is("password")))
|
||||
.andExpect(jsonPath("$.type", is("status")))
|
||||
|
||||
.andExpect(jsonPath("$._links.eperson.href", startsWith(REST_SERVER_URL)))
|
||||
@@ -136,6 +137,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio
|
||||
.andExpect(content().contentType(contentType))
|
||||
.andExpect(jsonPath("$.okay", is(true)))
|
||||
.andExpect(jsonPath("$.authenticated", is(true)))
|
||||
.andExpect(jsonPath("$.authenticationMethod", is("password")))
|
||||
.andExpect(jsonPath("$.type", is("status")))
|
||||
|
||||
.andExpect(jsonPath("$._links.eperson.href", startsWith(REST_SERVER_URL)))
|
||||
@@ -159,6 +161,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio
|
||||
.andExpect(content().contentType(contentType))
|
||||
.andExpect(jsonPath("$.okay", is(true)))
|
||||
.andExpect(jsonPath("$.authenticated", is(false)))
|
||||
.andExpect(jsonPath("$.authenticationMethod").doesNotExist())
|
||||
.andExpect(jsonPath("$.type", is("status")))
|
||||
.andExpect(header().string("WWW-Authenticate",
|
||||
"password realm=\"DSpace REST API\""));
|
||||
@@ -213,6 +216,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio
|
||||
.andExpect(content().contentType(contentType))
|
||||
.andExpect(jsonPath("$.okay", is(true)))
|
||||
.andExpect(jsonPath("$.authenticated", is(true)))
|
||||
.andExpect(jsonPath("$.authenticationMethod", is("shibboleth")))
|
||||
.andExpect(jsonPath("$.type", is("status")))
|
||||
// Verify that the CSRF token has NOT been changed... status checks won't change the token
|
||||
// (only login/logout will)
|
||||
@@ -244,6 +248,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio
|
||||
.andExpect(content().contentType(contentType))
|
||||
.andExpect(jsonPath("$.okay", is(true)))
|
||||
.andExpect(jsonPath("$.authenticated", is(true)))
|
||||
.andExpect(jsonPath("$.authenticationMethod", is("shibboleth")))
|
||||
.andExpect(jsonPath("$.type", is("status")));
|
||||
|
||||
//Logout, invalidating the token
|
||||
@@ -260,12 +265,18 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio
|
||||
|
||||
// Verify /api/authn/shibboleth endpoint does not work
|
||||
// NOTE: this is the same call as in testStatusShibAuthenticatedWithCookie())
|
||||
getClient().perform(get("/api/authn/shibboleth")
|
||||
String token = getClient().perform(get("/api/authn/shibboleth")
|
||||
.header("Referer", "https://myshib.example.com")
|
||||
.param("redirectUrl", uiURL)
|
||||
.requestAttr("SHIB-MAIL", eperson.getEmail())
|
||||
.requestAttr("SHIB-SCOPED-AFFILIATION", "faculty;staff"))
|
||||
.andExpect(status().isUnauthorized());
|
||||
.andExpect(status().isUnauthorized())
|
||||
.andReturn().getResponse().getHeader("Authorization");
|
||||
|
||||
getClient(token).perform(get("/api/authn/status"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.authenticated", is(false)))
|
||||
.andExpect(jsonPath("$.authenticationMethod").doesNotExist());
|
||||
}
|
||||
|
||||
// NOTE: This test is similar to testStatusShibAuthenticatedWithCookie(), but proves the same process works
|
||||
@@ -453,6 +464,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.okay", is(true)))
|
||||
.andExpect(jsonPath("$.authenticated", is(false)))
|
||||
.andExpect(jsonPath("$.authenticationMethod").doesNotExist())
|
||||
.andExpect(jsonPath("$.type", is("status")));
|
||||
}
|
||||
|
||||
@@ -515,6 +527,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio
|
||||
|
||||
.andExpect(jsonPath("$.okay", is(true)))
|
||||
.andExpect(jsonPath("$.authenticated", is(true)))
|
||||
.andExpect(jsonPath("$.authenticationMethod", is("password")))
|
||||
.andExpect(jsonPath("$.type", is("status")));
|
||||
|
||||
// Logout, invalidating token
|
||||
@@ -858,6 +871,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio
|
||||
.andExpect(content().contentType(contentType))
|
||||
.andExpect(jsonPath("$.okay", is(true)))
|
||||
.andExpect(jsonPath("$.authenticated", is(true)))
|
||||
.andExpect(jsonPath("$.authenticationMethod", is("shibboleth")))
|
||||
.andExpect(jsonPath("$.type", is("status")))
|
||||
.andExpect(jsonPath("$._links.eperson.href", startsWith(REST_SERVER_URL)))
|
||||
.andExpect(jsonPath("$._embedded.eperson",
|
||||
@@ -901,6 +915,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio
|
||||
.andExpect(content().contentType(contentType))
|
||||
.andExpect(jsonPath("$.okay", is(true)))
|
||||
.andExpect(jsonPath("$.authenticated", is(true)))
|
||||
.andExpect(jsonPath("$.authenticationMethod", is("shibboleth")))
|
||||
.andExpect(jsonPath("$.type", is("status")))
|
||||
.andExpect(jsonPath("$._links.eperson.href", startsWith(REST_SERVER_URL)))
|
||||
.andExpect(jsonPath("$._embedded.eperson",
|
||||
@@ -921,6 +936,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio
|
||||
.andExpect(content().contentType(contentType))
|
||||
.andExpect(jsonPath("$.okay", is(true)))
|
||||
.andExpect(jsonPath("$.authenticated", is(true)))
|
||||
.andExpect(jsonPath("$.authenticationMethod", is("shibboleth")))
|
||||
.andExpect(jsonPath("$.type", is("status")))
|
||||
.andExpect(jsonPath("$._links.eperson.href", startsWith(REST_SERVER_URL)))
|
||||
.andExpect(jsonPath("$._embedded.eperson",
|
||||
@@ -954,6 +970,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.okay", is(true)))
|
||||
.andExpect(jsonPath("$.authenticated", is(true)))
|
||||
.andExpect(jsonPath("$.authenticationMethod", is("password")))
|
||||
.andExpect(jsonPath("$.type", is("status")));
|
||||
|
||||
//Logout
|
||||
@@ -965,6 +982,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.okay", is(true)))
|
||||
.andExpect(jsonPath("$.authenticated", is(false)))
|
||||
.andExpect(jsonPath("$.authenticationMethod").doesNotExist())
|
||||
.andExpect(jsonPath("$.type", is("status")));
|
||||
|
||||
//Simulate that a shibboleth authentication has happened
|
||||
@@ -979,6 +997,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.okay", is(true)))
|
||||
.andExpect(jsonPath("$.authenticated", is(true)))
|
||||
.andExpect(jsonPath("$.authenticationMethod", is("shibboleth")))
|
||||
.andExpect(jsonPath("$.type", is("status")));
|
||||
|
||||
//Logout
|
||||
@@ -990,6 +1009,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.okay", is(true)))
|
||||
.andExpect(jsonPath("$.authenticated", is(false)))
|
||||
.andExpect(jsonPath("$.authenticationMethod").doesNotExist())
|
||||
.andExpect(jsonPath("$.type", is("status")));
|
||||
|
||||
}
|
||||
|
@@ -7,7 +7,9 @@
|
||||
*/
|
||||
package org.dspace.app.rest;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
@@ -47,9 +49,16 @@ public class ShibbolethRestControllerIT extends AbstractControllerIntegrationTes
|
||||
// unauthenticated, but it must include some expected SHIB attributes.
|
||||
// SHIB-MAIL attribute is the default email header sent from Shibboleth after a successful login.
|
||||
// In this test we are simply mocking that behavior by setting it to an existing EPerson.
|
||||
getClient().perform(get("/api/authn/shibboleth").requestAttr("SHIB-MAIL", eperson.getEmail()))
|
||||
String token = getClient().perform(get("/api/authn/shibboleth").requestAttr("SHIB-MAIL", eperson.getEmail()))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andExpect(redirectedUrl("http://localhost:4000"));
|
||||
.andExpect(redirectedUrl("http://localhost:4000"))
|
||||
.andReturn().getResponse().getHeader("Authorization");
|
||||
|
||||
|
||||
getClient(token).perform(get("/api/authn/status"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.authenticated", is(true)))
|
||||
.andExpect(jsonPath("$.authenticationMethod", is("shibboleth")));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
Reference in New Issue
Block a user