[DSC-196] Porting of OpenID authentication provider

This commit is contained in:
Luca Giamminonni
2021-10-13 16:31:56 +02:00
committed by Hardy Pottinger
parent 79c7ab77b1
commit 6521564d71
14 changed files with 1508 additions and 0 deletions

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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");
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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,

View File

@@ -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);
}
}

View 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 =

View File

@@ -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

View File

@@ -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>