mirror of
https://github.com/DSpace/DSpace.git
synced 2025-10-10 03:23:13 +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,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;
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user