mirror of
https://github.com/DSpace/DSpace.git
synced 2025-10-17 15:03:18 +00:00
[DSC-196] Porting of OpenID authentication provider
This commit is contained in:

committed by
Hardy Pottinger

parent
79c7ab77b1
commit
6521564d71
@@ -0,0 +1,77 @@
|
|||||||
|
/**
|
||||||
|
* 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.authenticate;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.dspace.core.Context;
|
||||||
|
import org.dspace.eperson.EPerson;
|
||||||
|
import org.dspace.eperson.Group;
|
||||||
|
import org.dspace.kernel.ServiceManager;
|
||||||
|
import org.dspace.utils.DSpace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of {@link AuthenticationMethod} that delegate all the method
|
||||||
|
* invocations to the bean of class {@link OrcidAuthenticationBean}.
|
||||||
|
*
|
||||||
|
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class OidcAuthentication implements AuthenticationMethod {
|
||||||
|
|
||||||
|
private final ServiceManager serviceManager = new DSpace().getServiceManager();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canSelfRegister(Context context, HttpServletRequest request, String username) throws SQLException {
|
||||||
|
return getOidcAuthentication().canSelfRegister(context, request, username);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initEPerson(Context context, HttpServletRequest request, EPerson eperson) throws SQLException {
|
||||||
|
getOidcAuthentication().initEPerson(context, request, eperson);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean allowSetPassword(Context context, HttpServletRequest request, String username) throws SQLException {
|
||||||
|
return getOidcAuthentication().allowSetPassword(context, request, username);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isImplicit() {
|
||||||
|
return getOidcAuthentication().isImplicit();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Group> getSpecialGroups(Context context, HttpServletRequest request) throws SQLException {
|
||||||
|
return getOidcAuthentication().getSpecialGroups(context, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int authenticate(Context context, String username, String password, String realm, HttpServletRequest request)
|
||||||
|
throws SQLException {
|
||||||
|
return getOidcAuthentication().authenticate(context, username, password, realm, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String loginPageURL(Context context, HttpServletRequest request, HttpServletResponse response) {
|
||||||
|
return getOidcAuthentication().loginPageURL(context, request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return getOidcAuthentication().getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
private OidcAuthenticationBean getOidcAuthentication() {
|
||||||
|
return serviceManager.getServiceByName("oidcAuthentication", OidcAuthenticationBean.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,260 @@
|
|||||||
|
/**
|
||||||
|
* 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.authenticate;
|
||||||
|
|
||||||
|
|
||||||
|
import static java.lang.String.format;
|
||||||
|
import static java.net.URLEncoder.encode;
|
||||||
|
import static org.apache.commons.lang.BooleanUtils.toBoolean;
|
||||||
|
import static org.apache.commons.lang3.StringUtils.isAnyBlank;
|
||||||
|
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||||
|
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.dspace.authenticate.oidc.OidcClient;
|
||||||
|
import org.dspace.authenticate.oidc.model.OidcTokenResponseDTO;
|
||||||
|
import org.dspace.core.Context;
|
||||||
|
import org.dspace.eperson.EPerson;
|
||||||
|
import org.dspace.eperson.Group;
|
||||||
|
import org.dspace.eperson.service.EPersonService;
|
||||||
|
import org.dspace.services.ConfigurationService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OpenID Connect Authentication for DSpace.
|
||||||
|
*
|
||||||
|
* This implementation doesn't allow/needs to register user, which may be holder
|
||||||
|
* by the openID authentication server.
|
||||||
|
*
|
||||||
|
* @link https://openid.net/developers/specs/
|
||||||
|
*
|
||||||
|
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
|
||||||
|
*/
|
||||||
|
public class OidcAuthenticationBean implements AuthenticationMethod {
|
||||||
|
|
||||||
|
public static final String OIDC_AUTH_ATTRIBUTE = "oidc";
|
||||||
|
|
||||||
|
private final static String LOGIN_PAGE_URL_FORMAT = "%s?client_id=%s&response_type=code&scope=%s&redirect_uri=%s";
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(OidcAuthenticationBean.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ConfigurationService configurationService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private OidcClient oidcClient;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private EPersonService ePersonService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean allowSetPassword(Context context, HttpServletRequest request, String username) throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isImplicit() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canSelfRegister(Context context, HttpServletRequest request, String username) throws SQLException {
|
||||||
|
return canSelfRegister();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initEPerson(Context context, HttpServletRequest request, EPerson eperson) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Group> getSpecialGroups(Context context, HttpServletRequest request) throws SQLException {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return OIDC_AUTH_ATTRIBUTE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int authenticate(Context context, String username, String password, String realm, HttpServletRequest request)
|
||||||
|
throws SQLException {
|
||||||
|
|
||||||
|
if (request == null) {
|
||||||
|
LOGGER.warn("Unable to authenticate using OIDC because the request object is null.");
|
||||||
|
return BAD_ARGS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.getAttribute(OIDC_AUTH_ATTRIBUTE) == null) {
|
||||||
|
return NO_SUCH_USER;
|
||||||
|
}
|
||||||
|
|
||||||
|
String code = (String) request.getParameter("code");
|
||||||
|
if (StringUtils.isEmpty(code)) {
|
||||||
|
LOGGER.warn("The incoming request has not code parameter");
|
||||||
|
return NO_SUCH_USER;
|
||||||
|
}
|
||||||
|
|
||||||
|
return authenticateWithOidc(context, code, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int authenticateWithOidc(Context context, String code, HttpServletRequest request) throws SQLException {
|
||||||
|
|
||||||
|
OidcTokenResponseDTO accessToken = getOidcAccessToken(code);
|
||||||
|
if (accessToken == null) {
|
||||||
|
LOGGER.warn("No access token retrieved by code");
|
||||||
|
return NO_SUCH_USER;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> userInfo = getOidcUserInfo(accessToken.getAccessToken());
|
||||||
|
|
||||||
|
String email = getAttributeAsString(userInfo, getEmailAttribute());
|
||||||
|
if (StringUtils.isBlank(email)) {
|
||||||
|
LOGGER.warn("No email found in the user info attributes");
|
||||||
|
return NO_SUCH_USER;
|
||||||
|
}
|
||||||
|
|
||||||
|
EPerson ePerson = ePersonService.findByEmail(context, email);
|
||||||
|
if (ePerson != null) {
|
||||||
|
return ePerson.canLogIn() ? logInEPerson(context, ePerson) : BAD_ARGS;
|
||||||
|
}
|
||||||
|
|
||||||
|
return canSelfRegister() ? registerNewEPerson(context, userInfo, email) : NO_SUCH_USER;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String loginPageURL(Context context, HttpServletRequest request, HttpServletResponse response) {
|
||||||
|
|
||||||
|
String authorizeUrl = configurationService.getProperty("authentication-oidc.authorize-endpoint");
|
||||||
|
String clientId = configurationService.getProperty("authentication-oidc.client-id");
|
||||||
|
String clientSecret = configurationService.getProperty("authentication-oidc.client-secret");
|
||||||
|
String redirectUri = configurationService.getProperty("authentication-oidc.redirect-url");
|
||||||
|
String tokenUrl = configurationService.getProperty("authentication-oidc.token-endpoint");
|
||||||
|
String userInfoUrl = configurationService.getProperty("authentication-oidc.user-info-endpoint");
|
||||||
|
String scopes = String.join(" ", configurationService.getArrayProperty("authentication-oidc.scopes"));
|
||||||
|
String email = getEmailAttribute();
|
||||||
|
|
||||||
|
if (isAnyBlank(authorizeUrl, clientId, redirectUri, scopes, clientSecret, tokenUrl, userInfoUrl, email)) {
|
||||||
|
LOGGER.error("Missing mandatory configuration properties for OidcAuthenticationBean");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return format(LOGIN_PAGE_URL_FORMAT, authorizeUrl, clientId, scopes, encode(redirectUri, "UTF-8"));
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
LOGGER.error(e.getMessage(), e);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private int logInEPerson(Context context, EPerson ePerson) {
|
||||||
|
context.setCurrentUser(ePerson);
|
||||||
|
return SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int registerNewEPerson(Context context, Map<String, Object> userInfo, String email) throws SQLException {
|
||||||
|
try {
|
||||||
|
|
||||||
|
context.turnOffAuthorisationSystem();
|
||||||
|
|
||||||
|
EPerson eperson = ePersonService.create(context);
|
||||||
|
|
||||||
|
eperson.setNetid(email);
|
||||||
|
eperson.setEmail(email);
|
||||||
|
|
||||||
|
String firstName = getAttributeAsString(userInfo, getFirstNameAttribute());
|
||||||
|
if (firstName != null) {
|
||||||
|
eperson.setFirstName(context, firstName);
|
||||||
|
}
|
||||||
|
|
||||||
|
String lastName = getAttributeAsString(userInfo, getLastNameAttribute());
|
||||||
|
if (lastName != null) {
|
||||||
|
eperson.setLastName(context, lastName);
|
||||||
|
}
|
||||||
|
|
||||||
|
eperson.setCanLogIn(true);
|
||||||
|
eperson.setSelfRegistered(true);
|
||||||
|
|
||||||
|
ePersonService.update(context, eperson);
|
||||||
|
context.setCurrentUser(eperson);
|
||||||
|
context.dispatchEvents();
|
||||||
|
|
||||||
|
return SUCCESS;
|
||||||
|
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LOGGER.error("An error occurs registering a new EPerson from OIDC", ex);
|
||||||
|
context.rollback();
|
||||||
|
return NO_SUCH_USER;
|
||||||
|
} finally {
|
||||||
|
context.restoreAuthSystemState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private OidcTokenResponseDTO getOidcAccessToken(String code) {
|
||||||
|
try {
|
||||||
|
return oidcClient.getAccessToken(code);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LOGGER.error("An error occurs retriving the OIDC access_token", ex);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> getOidcUserInfo(String accessToken) {
|
||||||
|
try {
|
||||||
|
return oidcClient.getUserInfo(accessToken);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LOGGER.error("An error occurs retriving the OIDC user info", ex);
|
||||||
|
return Map.of();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getAttributeAsString(Map<String, Object> userInfo, String attribute) {
|
||||||
|
if (isBlank(attribute)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return userInfo.containsKey(attribute) ? String.valueOf(userInfo.get(attribute)) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getEmailAttribute() {
|
||||||
|
return configurationService.getProperty("authentication-oidc.user-info.email");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getFirstNameAttribute() {
|
||||||
|
return configurationService.getProperty("authentication-oidc.user-info.first-name");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getLastNameAttribute() {
|
||||||
|
return configurationService.getProperty("authentication-oidc.user-info.last-name");
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean canSelfRegister() {
|
||||||
|
String canSelfRegister = configurationService.getProperty("authentication-oidc.can-self-register", "true");
|
||||||
|
if (isBlank(canSelfRegister)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return toBoolean(canSelfRegister);
|
||||||
|
}
|
||||||
|
|
||||||
|
public OidcClient getOidcClient() {
|
||||||
|
return this.oidcClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOidcClient(OidcClient oidcClient) {
|
||||||
|
this.oidcClient = oidcClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,310 @@
|
|||||||
|
/**
|
||||||
|
* 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.authenticate;
|
||||||
|
|
||||||
|
import static java.lang.String.format;
|
||||||
|
import static java.net.URLEncoder.encode;
|
||||||
|
import static org.apache.commons.lang.BooleanUtils.toBoolean;
|
||||||
|
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||||
|
import static org.dspace.content.Item.ANY;
|
||||||
|
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.dspace.app.orcid.client.OrcidClient;
|
||||||
|
import org.dspace.app.orcid.client.OrcidConfiguration;
|
||||||
|
import org.dspace.app.orcid.model.OrcidTokenResponseDTO;
|
||||||
|
import org.dspace.app.orcid.service.OrcidSynchronizationService;
|
||||||
|
import org.dspace.app.profile.ResearcherProfile;
|
||||||
|
import org.dspace.app.profile.service.ResearcherProfileService;
|
||||||
|
import org.dspace.authorize.AuthorizeException;
|
||||||
|
import org.dspace.core.Context;
|
||||||
|
import org.dspace.eperson.EPerson;
|
||||||
|
import org.dspace.eperson.Group;
|
||||||
|
import org.dspace.eperson.service.EPersonService;
|
||||||
|
import org.dspace.services.ConfigurationService;
|
||||||
|
import org.orcid.jaxb.model.v3.release.record.Email;
|
||||||
|
import org.orcid.jaxb.model.v3.release.record.Person;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ORCID authentication for DSpace.
|
||||||
|
*
|
||||||
|
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class OrcidAuthenticationBean implements AuthenticationMethod {
|
||||||
|
|
||||||
|
public static final String ORCID_AUTH_ATTRIBUTE = "orcid-authentication";
|
||||||
|
|
||||||
|
private final static Logger LOGGER = LoggerFactory.getLogger(OrcidAuthenticationBean.class);
|
||||||
|
|
||||||
|
private final static String LOGIN_PAGE_URL_FORMAT = "%s?client_id=%s&response_type=code&scope=%s&redirect_uri=%s";
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private OrcidClient orcidClient;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private OrcidConfiguration orcidConfiguration;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ConfigurationService configurationService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private EPersonService ePersonService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ResearcherProfileService researcherProfileService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private OrcidSynchronizationService orcidSynchronizationService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int authenticate(Context context, String username, String password, String realm, HttpServletRequest request)
|
||||||
|
throws SQLException {
|
||||||
|
|
||||||
|
if (request == null) {
|
||||||
|
LOGGER.warn("Unable to authenticate using ORCID because the request object is null.");
|
||||||
|
return BAD_ARGS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.getAttribute(ORCID_AUTH_ATTRIBUTE) == null) {
|
||||||
|
return NO_SUCH_USER;
|
||||||
|
}
|
||||||
|
|
||||||
|
String code = (String) request.getParameter("code");
|
||||||
|
if (StringUtils.isEmpty(code)) {
|
||||||
|
LOGGER.warn("The incoming request has not code parameter");
|
||||||
|
return NO_SUCH_USER;
|
||||||
|
}
|
||||||
|
|
||||||
|
return authenticateWithOrcid(context, code, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String loginPageURL(Context context, HttpServletRequest request, HttpServletResponse response) {
|
||||||
|
|
||||||
|
String authorizeUrl = orcidConfiguration.getAuthorizeEndpointUrl();
|
||||||
|
String clientId = orcidConfiguration.getClientId();
|
||||||
|
String redirectUri = orcidConfiguration.getRedirectUrl();
|
||||||
|
String scopes = String.join("+", orcidConfiguration.getScopes());
|
||||||
|
|
||||||
|
if (StringUtils.isAnyBlank(authorizeUrl, clientId, redirectUri, scopes)) {
|
||||||
|
LOGGER.error("Missing mandatory configuration properties for OrcidAuthentication");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return format(LOGIN_PAGE_URL_FORMAT, authorizeUrl, clientId, scopes, encode(redirectUri, "UTF-8"));
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
LOGGER.error(e.getMessage(), e);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canSelfRegister(Context context, HttpServletRequest request, String username) throws SQLException {
|
||||||
|
return canSelfRegister();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initEPerson(Context context, HttpServletRequest request, EPerson eperson) throws SQLException {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean allowSetPassword(Context context, HttpServletRequest request, String username) throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isImplicit() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Group> getSpecialGroups(Context context, HttpServletRequest request) throws SQLException {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "orcid";
|
||||||
|
}
|
||||||
|
|
||||||
|
private int authenticateWithOrcid(Context context, String code, HttpServletRequest request) throws SQLException {
|
||||||
|
OrcidTokenResponseDTO token = getOrcidAccessToken(code);
|
||||||
|
if (token == null) {
|
||||||
|
return NO_SUCH_USER;
|
||||||
|
}
|
||||||
|
|
||||||
|
String orcid = token.getOrcid();
|
||||||
|
|
||||||
|
EPerson ePerson = ePersonService.findByNetid(context, orcid);
|
||||||
|
if (ePerson != null) {
|
||||||
|
return ePerson.canLogIn() ? logInEPerson(context, token, ePerson) : BAD_ARGS;
|
||||||
|
}
|
||||||
|
|
||||||
|
Person person = getPersonFromOrcid(token);
|
||||||
|
if (person == null) {
|
||||||
|
return NO_SUCH_USER;
|
||||||
|
}
|
||||||
|
|
||||||
|
String email = getEmail(person).orElse(null);
|
||||||
|
|
||||||
|
ePerson = ePersonService.findByEmail(context, email);
|
||||||
|
if (ePerson != null) {
|
||||||
|
return ePerson.canLogIn() ? logInEPerson(context, token, ePerson) : BAD_ARGS;
|
||||||
|
}
|
||||||
|
|
||||||
|
return canSelfRegister() ? registerNewEPerson(context, person, token) : NO_SUCH_USER;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private int logInEPerson(Context context, OrcidTokenResponseDTO token, EPerson ePerson)
|
||||||
|
throws SQLException {
|
||||||
|
|
||||||
|
context.setCurrentUser(ePerson);
|
||||||
|
|
||||||
|
setOrcidMetadataOnEPerson(context, ePerson, token);
|
||||||
|
|
||||||
|
ResearcherProfile profile = findProfile(context, ePerson);
|
||||||
|
if (profile != null) {
|
||||||
|
orcidSynchronizationService.linkProfile(context, profile.getItem(), token);
|
||||||
|
}
|
||||||
|
|
||||||
|
return SUCCESS;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResearcherProfile findProfile(Context context, EPerson ePerson) throws SQLException {
|
||||||
|
try {
|
||||||
|
return researcherProfileService.findById(context, ePerson.getID());
|
||||||
|
} catch (AuthorizeException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int registerNewEPerson(Context context, Person person, OrcidTokenResponseDTO token) throws SQLException {
|
||||||
|
|
||||||
|
try {
|
||||||
|
context.turnOffAuthorisationSystem();
|
||||||
|
|
||||||
|
String orcid = token.getOrcid();
|
||||||
|
|
||||||
|
EPerson eperson = ePersonService.create(context);
|
||||||
|
|
||||||
|
eperson.setNetid(orcid);
|
||||||
|
eperson.setEmail(getEmail(person).orElse(orcid));
|
||||||
|
eperson.setFirstName(context, getFirstName(person));
|
||||||
|
eperson.setLastName(context, getLastName(person));
|
||||||
|
eperson.setCanLogIn(true);
|
||||||
|
eperson.setSelfRegistered(true);
|
||||||
|
|
||||||
|
setOrcidMetadataOnEPerson(context, eperson, token);
|
||||||
|
|
||||||
|
ePersonService.update(context, eperson);
|
||||||
|
context.setCurrentUser(eperson);
|
||||||
|
context.dispatchEvents();
|
||||||
|
|
||||||
|
return SUCCESS;
|
||||||
|
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LOGGER.error("An error occurs registering a new EPerson from ORCID", ex);
|
||||||
|
context.rollback();
|
||||||
|
return NO_SUCH_USER;
|
||||||
|
} finally {
|
||||||
|
context.restoreAuthSystemState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setOrcidMetadataOnEPerson(Context context, EPerson person, OrcidTokenResponseDTO token)
|
||||||
|
throws SQLException {
|
||||||
|
|
||||||
|
String orcid = token.getOrcid();
|
||||||
|
String accessToken = token.getAccessToken();
|
||||||
|
String refreshToken = token.getRefreshToken();
|
||||||
|
String[] scopes = token.getScopeAsArray();
|
||||||
|
|
||||||
|
ePersonService.setMetadataSingleValue(context, person, "eperson", "orcid", null, null, orcid);
|
||||||
|
ePersonService.setMetadataSingleValue(context, person, "eperson", "orcid", "access-token", null, accessToken);
|
||||||
|
ePersonService.setMetadataSingleValue(context, person, "eperson", "orcid", "refresh-token", null, refreshToken);
|
||||||
|
ePersonService.clearMetadata(context, person, "eperson", "orcid", "scope", ANY);
|
||||||
|
for (String scope : scopes) {
|
||||||
|
ePersonService.addMetadata(context, person, "eperson", "orcid", "scope", null, scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private Person getPersonFromOrcid(OrcidTokenResponseDTO token) {
|
||||||
|
try {
|
||||||
|
return orcidClient.getPerson(token.getAccessToken(), token.getOrcid());
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LOGGER.error("An error occurs retriving the ORCID record with id " + token.getOrcid(), ex);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<String> getEmail(Person person) {
|
||||||
|
List<Email> emails = person.getEmails() != null ? person.getEmails().getEmails() : Collections.emptyList();
|
||||||
|
if (CollectionUtils.isEmpty(emails)) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
return Optional.ofNullable(emails.get(0).getEmail());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getFirstName(Person person) {
|
||||||
|
return Optional.ofNullable(person.getName())
|
||||||
|
.map(name -> name.getGivenNames())
|
||||||
|
.map(givenNames -> givenNames.getContent())
|
||||||
|
.orElseThrow(() -> new IllegalStateException("The found ORCID person has no first name"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getLastName(Person person) {
|
||||||
|
return Optional.ofNullable(person.getName())
|
||||||
|
.map(name -> name.getFamilyName())
|
||||||
|
.map(givenNames -> givenNames.getContent())
|
||||||
|
.orElseThrow(() -> new IllegalStateException("The found ORCID person has no last name"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean canSelfRegister() {
|
||||||
|
String canSelfRegister = configurationService.getProperty("authentication-oidc.can-self-register", "true");
|
||||||
|
if (isBlank(canSelfRegister)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return toBoolean(canSelfRegister);
|
||||||
|
}
|
||||||
|
|
||||||
|
private OrcidTokenResponseDTO getOrcidAccessToken(String code) {
|
||||||
|
try {
|
||||||
|
return orcidClient.getAccessToken(code);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LOGGER.error("An error occurs retriving the ORCID access_token", ex);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public OrcidClient getOrcidClient() {
|
||||||
|
return orcidClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOrcidClient(OrcidClient orcidClient) {
|
||||||
|
this.orcidClient = orcidClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -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.authenticate.oidc;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.dspace.authenticate.oidc.model.OidcTokenResponseDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client to interact with the configured OIDC provider.
|
||||||
|
*
|
||||||
|
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public interface OidcClient {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exchange the authorization code for a 3-legged access token. The
|
||||||
|
* authorization code expires upon use.
|
||||||
|
*
|
||||||
|
* @param code the authorization code
|
||||||
|
* @return the OIDC token
|
||||||
|
* @throws OidcClientException if some error occurs during the exchange
|
||||||
|
*/
|
||||||
|
OidcTokenResponseDTO getAccessToken(String code) throws OidcClientException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the info related to the user associated with the given accessToken
|
||||||
|
* from the user info endpoint.
|
||||||
|
*
|
||||||
|
* @param accessToken the access token
|
||||||
|
* @return a map with the user infos
|
||||||
|
* @throws OidcClientException if some error occurs during the exchange
|
||||||
|
*/
|
||||||
|
Map<String, Object> getUserInfo(String accessToken) throws OidcClientException;
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* 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.authenticate.oidc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception throwable from class that implements {@link OidcClient} in case of
|
||||||
|
* error response from the OIDC provider.
|
||||||
|
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class OidcClientException extends RuntimeException {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -7618061110212398216L;
|
||||||
|
|
||||||
|
private int status = 0;
|
||||||
|
|
||||||
|
public OidcClientException(int status, String content) {
|
||||||
|
super(content);
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OidcClientException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStatus() {
|
||||||
|
return this.status;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,148 @@
|
|||||||
|
/**
|
||||||
|
* 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.authenticate.oidc.impl;
|
||||||
|
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.apache.http.HttpEntity;
|
||||||
|
import org.apache.http.HttpResponse;
|
||||||
|
import org.apache.http.NameValuePair;
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||||
|
import org.apache.http.client.methods.HttpUriRequest;
|
||||||
|
import org.apache.http.client.methods.RequestBuilder;
|
||||||
|
import org.apache.http.impl.client.HttpClientBuilder;
|
||||||
|
import org.apache.http.message.BasicNameValuePair;
|
||||||
|
import org.dspace.authenticate.oidc.OidcClient;
|
||||||
|
import org.dspace.authenticate.oidc.OidcClientException;
|
||||||
|
import org.dspace.authenticate.oidc.model.OidcTokenResponseDTO;
|
||||||
|
import org.dspace.services.ConfigurationService;
|
||||||
|
import org.dspace.util.ThrowingSupplier;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of {@link OidcClient}.
|
||||||
|
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class OidcClientImpl implements OidcClient {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ConfigurationService configurationService;
|
||||||
|
|
||||||
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OidcTokenResponseDTO getAccessToken(String code) throws OidcClientException {
|
||||||
|
List<NameValuePair> params = new ArrayList<NameValuePair>();
|
||||||
|
params.add(new BasicNameValuePair("code", code));
|
||||||
|
params.add(new BasicNameValuePair("grant_type", "authorization_code"));
|
||||||
|
params.add(new BasicNameValuePair("client_id", getClientId()));
|
||||||
|
params.add(new BasicNameValuePair("client_secret", getClientSecret()));
|
||||||
|
params.add(new BasicNameValuePair("redirect_uri", getRedirectUrl()));
|
||||||
|
|
||||||
|
HttpUriRequest httpUriRequest = RequestBuilder.post(getTokenEndpointUrl())
|
||||||
|
.addHeader("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
.addHeader("Accept", "application/json")
|
||||||
|
.setEntity(new UrlEncodedFormEntity(params, Charset.defaultCharset()))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return executeAndParseJson(httpUriRequest, OidcTokenResponseDTO.class);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public Map<String, Object> getUserInfo(String accessToken) throws OidcClientException {
|
||||||
|
|
||||||
|
HttpUriRequest httpUriRequest = RequestBuilder.get(getUserInfoEndpointUrl())
|
||||||
|
.addHeader("Authorization", "Bearer " + accessToken)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return executeAndParseJson(httpUriRequest, Map.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> T executeAndParseJson(HttpUriRequest httpUriRequest, Class<T> clazz) {
|
||||||
|
|
||||||
|
HttpClient client = HttpClientBuilder.create().build();
|
||||||
|
|
||||||
|
return executeAndReturns(() -> {
|
||||||
|
|
||||||
|
HttpResponse response = client.execute(httpUriRequest);
|
||||||
|
|
||||||
|
if (isNotSuccessfull(response)) {
|
||||||
|
throw new OidcClientException(getStatusCode(response), formatErrorMessage(response));
|
||||||
|
}
|
||||||
|
|
||||||
|
return objectMapper.readValue(getContent(response), clazz);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> T executeAndReturns(ThrowingSupplier<T, Exception> supplier) {
|
||||||
|
try {
|
||||||
|
return supplier.get();
|
||||||
|
} catch (OidcClientException ex) {
|
||||||
|
throw ex;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new OidcClientException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatErrorMessage(HttpResponse response) {
|
||||||
|
try {
|
||||||
|
return IOUtils.toString(response.getEntity().getContent(), Charset.defaultCharset());
|
||||||
|
} catch (UnsupportedOperationException | IOException e) {
|
||||||
|
return "Generic error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isNotSuccessfull(HttpResponse response) {
|
||||||
|
int statusCode = getStatusCode(response);
|
||||||
|
return statusCode < 200 || statusCode > 299;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getStatusCode(HttpResponse response) {
|
||||||
|
return response.getStatusLine().getStatusCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getContent(HttpResponse response) throws UnsupportedOperationException, IOException {
|
||||||
|
HttpEntity entity = response.getEntity();
|
||||||
|
return entity != null ? IOUtils.toString(entity.getContent(), UTF_8.name()) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getClientId() {
|
||||||
|
return configurationService.getProperty("authentication-oidc.client-id");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getClientSecret() {
|
||||||
|
return configurationService.getProperty("authentication-oidc.client-secret");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getTokenEndpointUrl() {
|
||||||
|
return configurationService.getProperty("authentication-oidc.token-endpoint");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getUserInfoEndpointUrl() {
|
||||||
|
return configurationService.getProperty("authentication-oidc.user-info-endpoint");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getRedirectUrl() {
|
||||||
|
return configurationService.getProperty("authentication-oidc.redirect-url");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,115 @@
|
|||||||
|
/**
|
||||||
|
* 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.authenticate.oidc.model;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class map the response from and OpenID Connect token endpoint.
|
||||||
|
* {@link https://openid.net/specs/openid-connect-core-1_0.html}
|
||||||
|
*
|
||||||
|
* Response example:
|
||||||
|
*
|
||||||
|
* { "access_token": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCIsIm9yZ...", "id_token":
|
||||||
|
* "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGki...", "token_type": "bearer",
|
||||||
|
* "expires_in": 28800, "scope": "pgc-role email openid profile" }
|
||||||
|
*
|
||||||
|
* @author Pasquale Cavallo (pasquale.cavallo at 4science dot it)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class OidcTokenResponseDTO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The access token release by the authorization server this is the most
|
||||||
|
* relevant item, because it allow the server to access to the user resources as
|
||||||
|
* defined in the scopes {@link https://tools.ietf.org/html/rfc6749#section-1.4}
|
||||||
|
*/
|
||||||
|
@JsonProperty("access_token")
|
||||||
|
private String accessToken;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The id token as defined in the OpenID connect standard
|
||||||
|
* {@link https://openid.net/specs/openid-connect-core-1_0.html#IDToken}
|
||||||
|
*/
|
||||||
|
@JsonProperty("id_token")
|
||||||
|
private String idToken;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The refresh token as defined in the OAuth standard
|
||||||
|
* {@link https://tools.ietf.org/html/rfc6749#section-1.5}
|
||||||
|
*/
|
||||||
|
@JsonProperty("refresh_token")
|
||||||
|
private String refreshToken;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It will be "bearer"
|
||||||
|
*/
|
||||||
|
@JsonProperty("token_type")
|
||||||
|
private String tokenType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The expiration timestamp in millis
|
||||||
|
*/
|
||||||
|
@JsonProperty("expires_in")
|
||||||
|
private Long expiresIn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of scopes {@link https://tools.ietf.org/html/rfc6749#section-3.3}
|
||||||
|
*/
|
||||||
|
@JsonProperty("scope")
|
||||||
|
private String scope;
|
||||||
|
|
||||||
|
public String getAccessToken() {
|
||||||
|
return accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAccessToken(String accessToken) {
|
||||||
|
this.accessToken = accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIdToken() {
|
||||||
|
return idToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIdToken(String idToken) {
|
||||||
|
this.idToken = idToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRefreshToken() {
|
||||||
|
return refreshToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRefreshToken(String refreshToken) {
|
||||||
|
this.refreshToken = refreshToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTokenType() {
|
||||||
|
return tokenType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTokenType(String tokenType) {
|
||||||
|
this.tokenType = tokenType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getExpiresIn() {
|
||||||
|
return expiresIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExpiresIn(Long expiresIn) {
|
||||||
|
this.expiresIn = expiresIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getScope() {
|
||||||
|
return scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScope(String scope) {
|
||||||
|
this.scope = scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,58 @@
|
|||||||
|
/**
|
||||||
|
* 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.List;
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
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.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 OIDC authentication succeded.
|
||||||
|
*
|
||||||
|
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(value = "/api/" + AuthnRest.CATEGORY + "/oidc")
|
||||||
|
public class OidcRestController {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(OidcRestController.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ConfigurationService configurationService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private DiscoverableEndpointsService discoverableEndpointsService;
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void afterPropertiesSet() {
|
||||||
|
discoverableEndpointsService.register(this, List.of(new Link("/api/" + AuthnRest.CATEGORY, "oidc")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping(method = RequestMethod.GET)
|
||||||
|
public void oidc(HttpServletResponse response,
|
||||||
|
@RequestParam(name = "redirectUrl", required = false) String redirectUrl) throws IOException {
|
||||||
|
if (StringUtils.isBlank(redirectUrl)) {
|
||||||
|
redirectUrl = configurationService.getProperty("dspace.ui.url");
|
||||||
|
}
|
||||||
|
log.info("Redirecting to " + redirectUrl);
|
||||||
|
response.sendRedirect(redirectUrl);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* 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 static org.dspace.authenticate.OidcAuthenticationBean.OIDC_AUTH_ATTRIBUTE;
|
||||||
|
|
||||||
|
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 openID Connect requests and try and authenticate them.
|
||||||
|
*
|
||||||
|
* @author Pasquale Cavallo (pasquale.cavallo at 4science dot it)
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class OidcAuthenticationFilter extends StatelessLoginFilter {
|
||||||
|
|
||||||
|
public OidcAuthenticationFilter(String url, AuthenticationManager authenticationManager,
|
||||||
|
RestAuthenticationService restAuthenticationService) {
|
||||||
|
super(url, authenticationManager, restAuthenticationService);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
|
||||||
|
throws AuthenticationException {
|
||||||
|
req.setAttribute(OIDC_AUTH_ATTRIBUTE, OIDC_AUTH_ATTRIBUTE);
|
||||||
|
return authenticationManager.authenticate(new DSpaceAuthentication(null, null, new ArrayList<>()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain,
|
||||||
|
Authentication auth) throws IOException, ServletException {
|
||||||
|
restAuthenticationService.addAuthenticationDataForUser(req, res, (DSpaceAuthentication) auth, true);
|
||||||
|
chain.doFilter(req, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -137,6 +137,14 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
|
|||||||
.addFilterBefore(new ShibbolethLoginFilter("/api/authn/shibboleth", authenticationManager(),
|
.addFilterBefore(new ShibbolethLoginFilter("/api/authn/shibboleth", authenticationManager(),
|
||||||
restAuthenticationService),
|
restAuthenticationService),
|
||||||
LogoutFilter.class)
|
LogoutFilter.class)
|
||||||
|
//Add a filter before our ORCID endpoints to do the authentication based on the data in the
|
||||||
|
// HTTP request
|
||||||
|
.addFilterBefore(new OrcidAuthenticationFilter("/api/authn/orcid", authenticationManager(),
|
||||||
|
restAuthenticationService),
|
||||||
|
LogoutFilter.class)
|
||||||
|
.addFilterBefore(new OidcAuthenticationFilter("/api/authn/oidc", 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,
|
||||||
|
@@ -0,0 +1,328 @@
|
|||||||
|
/**
|
||||||
|
* 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 java.util.Arrays.asList;
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
import static org.hamcrest.Matchers.notNullValue;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.verifyNoInteractions;
|
||||||
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.cookie;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.util.Map;
|
||||||
|
import javax.servlet.http.Cookie;
|
||||||
|
|
||||||
|
import com.nimbusds.jose.JOSEException;
|
||||||
|
import com.nimbusds.jwt.SignedJWT;
|
||||||
|
import org.dspace.app.rest.model.AuthnRest;
|
||||||
|
import org.dspace.app.rest.security.jwt.EPersonClaimProvider;
|
||||||
|
import org.dspace.app.rest.test.AbstractControllerIntegrationTest;
|
||||||
|
import org.dspace.authenticate.OidcAuthenticationBean;
|
||||||
|
import org.dspace.authenticate.oidc.OidcClient;
|
||||||
|
import org.dspace.authenticate.oidc.OidcClientException;
|
||||||
|
import org.dspace.authenticate.oidc.model.OidcTokenResponseDTO;
|
||||||
|
import org.dspace.builder.EPersonBuilder;
|
||||||
|
import org.dspace.eperson.EPerson;
|
||||||
|
import org.dspace.eperson.service.EPersonService;
|
||||||
|
import org.dspace.services.ConfigurationService;
|
||||||
|
import org.dspace.util.UUIDUtils;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.test.web.servlet.MvcResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Integration tests for {@link OidcAuthenticationRestController}.
|
||||||
|
*
|
||||||
|
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class OidcAuthenticationRestControllerIT extends AbstractControllerIntegrationTest {
|
||||||
|
|
||||||
|
private final static String CODE = "123456";
|
||||||
|
private final static String EMAIL = "email";
|
||||||
|
private final static String FIRST_NAME = "first_name";
|
||||||
|
private final static String LAST_NAME = "last_name";
|
||||||
|
|
||||||
|
private final static String ACCESS_TOKEN = "c41e37e5-c2de-4177-91d6-ed9e9d1f31bf";
|
||||||
|
private final static String REFRESH_TOKEN = "0062a9eb-d4ec-4d94-9491-95dd75376d3e";
|
||||||
|
private final static String[] OIDC_SCOPES = { "FirstScope", "SecondScope" };
|
||||||
|
|
||||||
|
private OidcClient originalOidcClient;
|
||||||
|
|
||||||
|
private OidcClient oidcClientMock = mock(OidcClient.class);
|
||||||
|
|
||||||
|
private EPerson createdEperson;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private OidcAuthenticationBean oidcAuthentication;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ConfigurationService configurationService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private EPersonService ePersonService;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() {
|
||||||
|
originalOidcClient = oidcAuthentication.getOidcClient();
|
||||||
|
oidcAuthentication.setOidcClient(oidcClientMock);
|
||||||
|
|
||||||
|
configurationService.setProperty("authentication-oidc.user-info.email", EMAIL);
|
||||||
|
configurationService.setProperty("authentication-oidc.user-info.first-name", FIRST_NAME);
|
||||||
|
configurationService.setProperty("authentication-oidc.user-info.last-name", LAST_NAME);
|
||||||
|
|
||||||
|
configurationService.setProperty("plugin.sequence.org.dspace.authenticate.AuthenticationMethod",
|
||||||
|
asList("org.dspace.authenticate.OidcAuthentication", "org.dspace.authenticate.PasswordAuthentication"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void after() throws Exception {
|
||||||
|
oidcAuthentication.setOidcClient(originalOidcClient);
|
||||||
|
if (createdEperson != null) {
|
||||||
|
context.turnOffAuthorisationSystem();
|
||||||
|
ePersonService.delete(context, createdEperson);
|
||||||
|
context.restoreAuthSystemState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEPersonCreationViaOidcLogin() throws Exception {
|
||||||
|
|
||||||
|
when(oidcClientMock.getAccessToken(CODE)).thenReturn(buildOidcTokenResponse(ACCESS_TOKEN));
|
||||||
|
when(oidcClientMock.getUserInfo(ACCESS_TOKEN)).thenReturn(buildUserInfo("test@email.it", "Test", "User"));
|
||||||
|
|
||||||
|
MvcResult mvcResult = getClient().perform(get("/api/" + AuthnRest.CATEGORY + "/oidc")
|
||||||
|
.param("code", CODE))
|
||||||
|
.andExpect(status().is3xxRedirection())
|
||||||
|
.andExpect(redirectedUrl(configurationService.getProperty("dspace.ui.url")))
|
||||||
|
.andExpect(cookie().exists("Authorization-cookie"))
|
||||||
|
.andReturn();
|
||||||
|
|
||||||
|
verify(oidcClientMock).getAccessToken(CODE);
|
||||||
|
verify(oidcClientMock).getUserInfo(ACCESS_TOKEN);
|
||||||
|
verifyNoMoreInteractions(oidcClientMock);
|
||||||
|
|
||||||
|
String ePersonId = getEPersonIdFromAuthorizationCookie(mvcResult);
|
||||||
|
|
||||||
|
createdEperson = ePersonService.find(context, UUIDUtils.fromString(ePersonId));
|
||||||
|
assertThat(createdEperson, notNullValue());
|
||||||
|
assertThat(createdEperson.getEmail(), equalTo("test@email.it"));
|
||||||
|
assertThat(createdEperson.getFullName(), equalTo("Test User"));
|
||||||
|
assertThat(createdEperson.getNetid(), equalTo("test@email.it"));
|
||||||
|
assertThat(createdEperson.canLogIn(), equalTo(true));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEPersonCreationViaOidcLoginWithoutEmail() throws Exception {
|
||||||
|
|
||||||
|
when(oidcClientMock.getAccessToken(CODE)).thenReturn(buildOidcTokenResponse(ACCESS_TOKEN));
|
||||||
|
when(oidcClientMock.getUserInfo(ACCESS_TOKEN)).thenReturn(buildUserInfo("test@email.it"));
|
||||||
|
|
||||||
|
MvcResult mvcResult = getClient().perform(get("/api/" + AuthnRest.CATEGORY + "/oidc")
|
||||||
|
.param("code", CODE))
|
||||||
|
.andExpect(status().is3xxRedirection())
|
||||||
|
.andExpect(redirectedUrl(configurationService.getProperty("dspace.ui.url")))
|
||||||
|
.andExpect(cookie().exists("Authorization-cookie"))
|
||||||
|
.andReturn();
|
||||||
|
|
||||||
|
verify(oidcClientMock).getAccessToken(CODE);
|
||||||
|
verify(oidcClientMock).getUserInfo(ACCESS_TOKEN);
|
||||||
|
verifyNoMoreInteractions(oidcClientMock);
|
||||||
|
|
||||||
|
String ePersonId = getEPersonIdFromAuthorizationCookie(mvcResult);
|
||||||
|
|
||||||
|
createdEperson = ePersonService.find(context, UUIDUtils.fromString(ePersonId));
|
||||||
|
assertThat(createdEperson, notNullValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWithoutSelfRegistrationEnabled() throws Exception {
|
||||||
|
|
||||||
|
configurationService.setProperty("authentication-oidc.can-self-register", "false");
|
||||||
|
when(oidcClientMock.getAccessToken(CODE)).thenReturn(buildOidcTokenResponse(ACCESS_TOKEN));
|
||||||
|
when(oidcClientMock.getUserInfo(ACCESS_TOKEN)).thenReturn(buildUserInfo("test@email.it"));
|
||||||
|
|
||||||
|
MvcResult mvcResult = getClient().perform(get("/api/" + AuthnRest.CATEGORY + "/oidc")
|
||||||
|
.param("code", CODE))
|
||||||
|
.andExpect(status().isUnauthorized())
|
||||||
|
.andExpect(cookie().doesNotExist("Authorization-cookie"))
|
||||||
|
.andExpect(header().exists("WWW-Authenticate"))
|
||||||
|
.andReturn();
|
||||||
|
|
||||||
|
String authenticateHeader = mvcResult.getResponse().getHeader("WWW-Authenticate");
|
||||||
|
assertThat(authenticateHeader, containsString("oidc realm=\"DSpace REST API\""));
|
||||||
|
|
||||||
|
verify(oidcClientMock).getAccessToken(CODE);
|
||||||
|
verify(oidcClientMock).getUserInfo(ACCESS_TOKEN);
|
||||||
|
verifyNoMoreInteractions(oidcClientMock);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWithoutAuthorizationCode() throws Exception {
|
||||||
|
|
||||||
|
getClient().perform(get("/api/" + AuthnRest.CATEGORY + "/oidc"))
|
||||||
|
.andExpect(status().isUnauthorized())
|
||||||
|
.andExpect(cookie().doesNotExist("Authorization-cookie"))
|
||||||
|
.andExpect(header().exists("WWW-Authenticate"));
|
||||||
|
|
||||||
|
verifyNoInteractions(oidcClientMock);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEPersonLoggedInByEmail() throws Exception {
|
||||||
|
|
||||||
|
when(oidcClientMock.getAccessToken(CODE)).thenReturn(buildOidcTokenResponse(ACCESS_TOKEN));
|
||||||
|
when(oidcClientMock.getUserInfo(ACCESS_TOKEN)).thenReturn(buildUserInfo("test@email.it"));
|
||||||
|
|
||||||
|
context.turnOffAuthorisationSystem();
|
||||||
|
|
||||||
|
EPerson ePerson = EPersonBuilder.createEPerson(context)
|
||||||
|
.withEmail("test@email.it")
|
||||||
|
.withNameInMetadata("Test", "User")
|
||||||
|
.withCanLogin(true)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
context.restoreAuthSystemState();
|
||||||
|
|
||||||
|
MvcResult mvcResult = getClient().perform(get("/api/" + AuthnRest.CATEGORY + "/oidc")
|
||||||
|
.param("code", CODE))
|
||||||
|
.andExpect(status().is3xxRedirection())
|
||||||
|
.andExpect(redirectedUrl(configurationService.getProperty("dspace.ui.url")))
|
||||||
|
.andExpect(cookie().exists("Authorization-cookie"))
|
||||||
|
.andReturn();
|
||||||
|
|
||||||
|
verify(oidcClientMock).getAccessToken(CODE);
|
||||||
|
verify(oidcClientMock).getUserInfo(ACCESS_TOKEN);
|
||||||
|
verifyNoMoreInteractions(oidcClientMock);
|
||||||
|
|
||||||
|
String ePersonId = getEPersonIdFromAuthorizationCookie(mvcResult);
|
||||||
|
assertThat(ePersonId, notNullValue());
|
||||||
|
assertThat(ePersonId, equalTo(ePerson.getID().toString()));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEPersonCannotLogInByEmail() throws Exception {
|
||||||
|
|
||||||
|
when(oidcClientMock.getAccessToken(CODE)).thenReturn(buildOidcTokenResponse(ACCESS_TOKEN));
|
||||||
|
when(oidcClientMock.getUserInfo(ACCESS_TOKEN)).thenReturn(buildUserInfo("test@email.it"));
|
||||||
|
|
||||||
|
context.turnOffAuthorisationSystem();
|
||||||
|
|
||||||
|
EPersonBuilder.createEPerson(context)
|
||||||
|
.withEmail("test@email.it")
|
||||||
|
.withNameInMetadata("Test", "User")
|
||||||
|
.withCanLogin(false)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
context.restoreAuthSystemState();
|
||||||
|
|
||||||
|
getClient().perform(get("/api/" + AuthnRest.CATEGORY + "/oidc")
|
||||||
|
.param("code", CODE))
|
||||||
|
.andExpect(status().isUnauthorized())
|
||||||
|
.andExpect(cookie().doesNotExist("Authorization-cookie"))
|
||||||
|
.andExpect(header().exists("WWW-Authenticate"));
|
||||||
|
|
||||||
|
verify(oidcClientMock).getAccessToken(CODE);
|
||||||
|
verify(oidcClientMock).getUserInfo(ACCESS_TOKEN);
|
||||||
|
verifyNoMoreInteractions(oidcClientMock);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNoAuthenticationIfAnErrorOccursRetrivingOidcToken() throws Exception {
|
||||||
|
|
||||||
|
when(oidcClientMock.getAccessToken(CODE)).thenThrow(new OidcClientException(500, "internal error"));
|
||||||
|
|
||||||
|
context.turnOffAuthorisationSystem();
|
||||||
|
|
||||||
|
EPersonBuilder.createEPerson(context)
|
||||||
|
.withEmail("test@email.it")
|
||||||
|
.withNameInMetadata("Test", "User")
|
||||||
|
.withCanLogin(false)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
context.restoreAuthSystemState();
|
||||||
|
|
||||||
|
getClient().perform(get("/api/" + AuthnRest.CATEGORY + "/oidc")
|
||||||
|
.param("code", CODE))
|
||||||
|
.andExpect(status().isUnauthorized())
|
||||||
|
.andExpect(cookie().doesNotExist("Authorization-cookie"))
|
||||||
|
.andExpect(header().exists("WWW-Authenticate"));
|
||||||
|
|
||||||
|
verify(oidcClientMock).getAccessToken(CODE);
|
||||||
|
verifyNoMoreInteractions(oidcClientMock);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNoAuthenticationIfAnErrorOccursRetrivingOidcPerson() throws Exception {
|
||||||
|
|
||||||
|
when(oidcClientMock.getAccessToken(CODE)).thenReturn(buildOidcTokenResponse(ACCESS_TOKEN));
|
||||||
|
when(oidcClientMock.getUserInfo(ACCESS_TOKEN)).thenThrow(new OidcClientException(500, "Internal Error"));
|
||||||
|
|
||||||
|
context.turnOffAuthorisationSystem();
|
||||||
|
|
||||||
|
EPersonBuilder.createEPerson(context)
|
||||||
|
.withEmail("test@email.it")
|
||||||
|
.withNameInMetadata("Test", "User")
|
||||||
|
.withCanLogin(false)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
context.restoreAuthSystemState();
|
||||||
|
|
||||||
|
getClient().perform(get("/api/" + AuthnRest.CATEGORY + "/oidc")
|
||||||
|
.param("code", CODE))
|
||||||
|
.andExpect(status().isUnauthorized())
|
||||||
|
.andExpect(cookie().doesNotExist("Authorization-cookie"))
|
||||||
|
.andExpect(header().exists("WWW-Authenticate"));
|
||||||
|
|
||||||
|
verify(oidcClientMock).getAccessToken(CODE);
|
||||||
|
verify(oidcClientMock).getUserInfo(ACCESS_TOKEN);
|
||||||
|
verifyNoMoreInteractions(oidcClientMock);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private OidcTokenResponseDTO buildOidcTokenResponse(String accessToken) {
|
||||||
|
OidcTokenResponseDTO token = new OidcTokenResponseDTO();
|
||||||
|
token.setAccessToken(accessToken);
|
||||||
|
token.setTokenType("Bearer");
|
||||||
|
token.setRefreshToken(REFRESH_TOKEN);
|
||||||
|
token.setScope(String.join(" ", OIDC_SCOPES));
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> buildUserInfo(String email) {
|
||||||
|
return Map.of(EMAIL, email);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> buildUserInfo(String email, String firstName, String lastName) {
|
||||||
|
return Map.of(EMAIL, email, FIRST_NAME, firstName, LAST_NAME, lastName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getEPersonIdFromAuthorizationCookie(MvcResult mvcResult) throws ParseException, JOSEException {
|
||||||
|
Cookie authorizationCookie = mvcResult.getResponse().getCookie("Authorization-cookie");
|
||||||
|
SignedJWT jwt = SignedJWT.parse(authorizationCookie.getValue());
|
||||||
|
return (String) jwt.getJWTClaimsSet().getClaim(EPersonClaimProvider.EPERSON_ID);
|
||||||
|
}
|
||||||
|
}
|
42
dspace/config/modules/authentication-oidc.cfg
Normal file
42
dspace/config/modules/authentication-oidc.cfg
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#---------------------------------------------------------------#
|
||||||
|
#---------------OIDC AUTHENTICATION CONFIGURATIONS--------------#
|
||||||
|
#---------------------------------------------------------------#
|
||||||
|
# Configuration properties used by the CAS OIDC #
|
||||||
|
# Authentication plugin, when it is enabled. #
|
||||||
|
#---------------------------------------------------------------#
|
||||||
|
|
||||||
|
# The domain of the OpenID Connect server
|
||||||
|
authentication-oidc.auth-server-domain =
|
||||||
|
|
||||||
|
# The URL of the Token endpoint
|
||||||
|
authentication-oidc.token-endpoint =
|
||||||
|
|
||||||
|
# The URL of the Authorize endpoint
|
||||||
|
authentication-oidc.authorize-endpoint =
|
||||||
|
|
||||||
|
# The URL of the Introspect endpoint
|
||||||
|
authentication-oidc.user-info-endpoint =
|
||||||
|
|
||||||
|
# The registered client id
|
||||||
|
authentication-oidc.client-id =
|
||||||
|
|
||||||
|
# The registered client secret
|
||||||
|
authentication-oidc.client-secret =
|
||||||
|
|
||||||
|
# The redirect url
|
||||||
|
authentication-oidc.redirect-url = ${dspace.server.url}/api/authn/oidc
|
||||||
|
|
||||||
|
# The scopes to request
|
||||||
|
authentication-oidc.scopes =
|
||||||
|
|
||||||
|
#Specify if the user can self register using OIDC (true|false). If not specified, true is assumed
|
||||||
|
authentication-oidc.can-self-register =
|
||||||
|
|
||||||
|
#Specify the attribute present in the user info json related to the user's email
|
||||||
|
authentication-oidc.user-info.email =
|
||||||
|
|
||||||
|
#Specify the attribute present in the user info json related to the user's first name
|
||||||
|
authentication-oidc.user-info.first-name =
|
||||||
|
|
||||||
|
#Specify the attribute present in the user info json related to the user's last name
|
||||||
|
authentication-oidc.user-info.last-name =
|
@@ -24,6 +24,13 @@
|
|||||||
# * X.509 Certificate Authentication
|
# * X.509 Certificate Authentication
|
||||||
# Plugin class: org.dspace.authenticate.X509Authentication
|
# Plugin class: org.dspace.authenticate.X509Authentication
|
||||||
# Configuration file: authentication-x509.cfg
|
# Configuration file: authentication-x509.cfg
|
||||||
|
# * ORCID Authentication
|
||||||
|
# Plugin class: org.dspace.authenticate.OrcidAuthentication
|
||||||
|
# Configuration file: authentication-orcid.cfg
|
||||||
|
# * OIDC Authentication
|
||||||
|
# Plugin class: org.dspace.authenticate.OidcAuthentication
|
||||||
|
# Configuration file: authentication-oidc.cfg
|
||||||
|
|
||||||
#
|
#
|
||||||
# One or more of the above plugins can be enabled by listing its plugin class in
|
# One or more of the above plugins can be enabled by listing its plugin class in
|
||||||
# the below setting. To configure the enabled plugin(s) visit the configuration file(s)
|
# the below setting. To configure the enabled plugin(s) visit the configuration file(s)
|
||||||
@@ -45,6 +52,12 @@
|
|||||||
# X.509 certificate authentication. See authentication-x509.cfg for default configuration.
|
# X.509 certificate authentication. See authentication-x509.cfg for default configuration.
|
||||||
#plugin.sequence.org.dspace.authenticate.AuthenticationMethod = org.dspace.authenticate.X509Authentication
|
#plugin.sequence.org.dspace.authenticate.AuthenticationMethod = org.dspace.authenticate.X509Authentication
|
||||||
|
|
||||||
|
# ORCID authentication. See authentication-orcid.cfg for default configuration.
|
||||||
|
#plugin.sequence.org.dspace.authenticate.AuthenticationMethod = org.dspace.authenticate.OrcidAuthentication
|
||||||
|
|
||||||
|
# OIDC authentication. See authentication-oidc.cfg for default configuration.
|
||||||
|
#plugin.sequence.org.dspace.authenticate.AuthenticationMethod = org.dspace.authenticate.OidcAuthentication
|
||||||
|
|
||||||
# Authentication by Password (encrypted in DSpace's database). See authentication-password.cfg for default configuration.
|
# Authentication by Password (encrypted in DSpace's database). See authentication-password.cfg for default configuration.
|
||||||
# Enabled by default (to disable, either comment out, or define a new list of AuthenticationMethod plugins in your local.cfg)
|
# Enabled by default (to disable, either comment out, or define a new list of AuthenticationMethod plugins in your local.cfg)
|
||||||
plugin.sequence.org.dspace.authenticate.AuthenticationMethod = org.dspace.authenticate.PasswordAuthentication
|
plugin.sequence.org.dspace.authenticate.AuthenticationMethod = org.dspace.authenticate.PasswordAuthentication
|
||||||
|
@@ -134,5 +134,28 @@
|
|||||||
<bean class="org.dspace.discovery.indexobject.WorkspaceItemIndexFactoryImpl" autowire-candidate="true"/>
|
<bean class="org.dspace.discovery.indexobject.WorkspaceItemIndexFactoryImpl" autowire-candidate="true"/>
|
||||||
<bean class="org.dspace.discovery.indexobject.MetadataFieldIndexFactoryImpl" autowire-candidate="true"/>
|
<bean class="org.dspace.discovery.indexobject.MetadataFieldIndexFactoryImpl" autowire-candidate="true"/>
|
||||||
|
|
||||||
|
<!-- Imp beans -->
|
||||||
|
<bean class="org.dspace.batch.ImpBitstreamServiceImpl"/>
|
||||||
|
<bean class="org.dspace.batch.ImpMetadatavalueServiceImpl"/>
|
||||||
|
<bean class="org.dspace.batch.ImpRecordServiceImpl"/>
|
||||||
|
<bean class="org.dspace.batch.ImpBitstreamMetadatavalueServiceImpl"/>
|
||||||
|
<bean class="org.dspace.batch.ImpWorkflowNStateServiceImpl"/>
|
||||||
|
|
||||||
|
<!-- Configurable layout services -->
|
||||||
|
<bean class="org.dspace.layout.service.impl.CrisLayoutTabServiceImpl"/>
|
||||||
|
<bean class="org.dspace.layout.service.impl.CrisLayoutBoxServiceImpl"/>
|
||||||
|
<bean class="org.dspace.layout.service.impl.CrisLayoutFieldServiceImpl"/>
|
||||||
|
<bean class="org.dspace.layout.service.impl.CrisLayoutMetadataGroupServiceImpl"/>
|
||||||
|
<bean class="org.dspace.layout.service.impl.CrisLayoutMetric2BoxServiceImpl"/>
|
||||||
|
|
||||||
|
<bean class="org.dspace.layout.service.impl.LayoutSecurityServiceImpl"/>
|
||||||
|
<bean class="org.dspace.layout.service.impl.CrisLayoutBoxAccessServiceImpl"/>
|
||||||
|
<bean class="org.dspace.layout.service.impl.CrisLayoutTabAccessServiceImpl"/>
|
||||||
|
<bean class="org.dspace.content.security.CrisSecurityServiceImpl"/>
|
||||||
|
<bean class="org.dspace.app.metrics.service.CrisMetricsServiceImpl"/>
|
||||||
|
|
||||||
|
<bean class="org.dspace.authenticate.OidcAuthenticationBean" id="oidcAuthentication"/>
|
||||||
|
<bean class="org.dspace.authenticate.oidc.impl.OidcClientImpl" />
|
||||||
|
|
||||||
</beans>
|
</beans>
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user