mirror of
https://github.com/DSpace/DSpace.git
synced 2025-10-07 01:54:22 +00:00
Merge pull request #9849 from 4Science/task/main/CST-15074
ORCID Login flow for private emails
This commit is contained in:
@@ -19,6 +19,7 @@ import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -457,7 +458,7 @@ public class DSpaceCSV implements Serializable {
|
||||
List<Collection> collections = i.getCollections();
|
||||
for (Collection c : collections) {
|
||||
// Only add if it is not the owning collection
|
||||
if (!c.getHandle().equals(owningCollectionHandle)) {
|
||||
if (!Objects.equals(c.getHandle(), owningCollectionHandle)) {
|
||||
line.add("collection", c.getHandle());
|
||||
}
|
||||
}
|
||||
|
@@ -29,7 +29,10 @@ import org.dspace.authorize.AuthorizeException;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.eperson.EPerson;
|
||||
import org.dspace.eperson.Group;
|
||||
import org.dspace.eperson.RegistrationData;
|
||||
import org.dspace.eperson.RegistrationTypeEnum;
|
||||
import org.dspace.eperson.service.EPersonService;
|
||||
import org.dspace.eperson.service.RegistrationDataService;
|
||||
import org.dspace.orcid.OrcidToken;
|
||||
import org.dspace.orcid.client.OrcidClient;
|
||||
import org.dspace.orcid.client.OrcidConfiguration;
|
||||
@@ -47,11 +50,15 @@ 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_DEFAULT_FIRSTNAME = "Unnamed";
|
||||
public static final String ORCID_DEFAULT_LASTNAME = ORCID_DEFAULT_FIRSTNAME;
|
||||
public static final String ORCID_AUTH_ATTRIBUTE = "orcid-authentication";
|
||||
public static final String ORCID_REGISTRATION_TOKEN = "orcid-registration-token";
|
||||
public static final String ORCID_DEFAULT_REGISTRATION_URL = "/external-login/{0}";
|
||||
|
||||
private final static Logger LOGGER = LogManager.getLogger();
|
||||
|
||||
@@ -78,6 +85,9 @@ public class OrcidAuthenticationBean implements AuthenticationMethod {
|
||||
@Autowired
|
||||
private OrcidTokenService orcidTokenService;
|
||||
|
||||
@Autowired
|
||||
private RegistrationDataService registrationDataService;
|
||||
|
||||
@Override
|
||||
public int authenticate(Context context, String username, String password, String realm, HttpServletRequest request)
|
||||
throws SQLException {
|
||||
@@ -183,7 +193,7 @@ public class OrcidAuthenticationBean implements AuthenticationMethod {
|
||||
return ePerson.canLogIn() ? logInEPerson(context, token, ePerson) : BAD_ARGS;
|
||||
}
|
||||
|
||||
return canSelfRegister() ? registerNewEPerson(context, person, token) : NO_SUCH_USER;
|
||||
return canSelfRegister() ? createRegistrationData(context, request, person, token) : NO_SUCH_USER;
|
||||
|
||||
}
|
||||
|
||||
@@ -211,48 +221,59 @@ public class OrcidAuthenticationBean implements AuthenticationMethod {
|
||||
}
|
||||
}
|
||||
|
||||
private int registerNewEPerson(Context context, Person person, OrcidTokenResponseDTO token) throws SQLException {
|
||||
private int createRegistrationData(
|
||||
Context context, HttpServletRequest request, Person person, OrcidTokenResponseDTO token
|
||||
) throws SQLException {
|
||||
|
||||
try {
|
||||
context.turnOffAuthorisationSystem();
|
||||
|
||||
String email = getEmail(person)
|
||||
.orElseThrow(() -> new IllegalStateException("The email is configured private on orcid"));
|
||||
RegistrationData registrationData =
|
||||
this.registrationDataService.create(context, token.getOrcid(), RegistrationTypeEnum.ORCID);
|
||||
|
||||
String orcid = token.getOrcid();
|
||||
registrationData.setEmail(getEmail(person).orElse(null));
|
||||
setOrcidMetadataOnRegistration(context, registrationData, person, token);
|
||||
|
||||
EPerson eperson = ePersonService.create(context);
|
||||
registrationDataService.update(context, registrationData);
|
||||
|
||||
eperson.setNetid(orcid);
|
||||
|
||||
eperson.setEmail(email);
|
||||
|
||||
Optional<String> firstName = getFirstName(person);
|
||||
if (firstName.isPresent()) {
|
||||
eperson.setFirstName(context, firstName.get());
|
||||
}
|
||||
|
||||
Optional<String> lastName = getLastName(person);
|
||||
if (lastName.isPresent()) {
|
||||
eperson.setLastName(context, lastName.get());
|
||||
}
|
||||
eperson.setCanLogIn(true);
|
||||
eperson.setSelfRegistered(true);
|
||||
|
||||
setOrcidMetadataOnEPerson(context, eperson, token);
|
||||
|
||||
ePersonService.update(context, eperson);
|
||||
context.setCurrentUser(eperson);
|
||||
request.setAttribute(ORCID_REGISTRATION_TOKEN, registrationData.getToken());
|
||||
context.commit();
|
||||
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();
|
||||
return NO_SUCH_USER;
|
||||
}
|
||||
}
|
||||
|
||||
private void setOrcidMetadataOnRegistration(
|
||||
Context context, RegistrationData registration, Person person, OrcidTokenResponseDTO token
|
||||
) throws SQLException, AuthorizeException {
|
||||
String orcid = token.getOrcid();
|
||||
|
||||
setRegistrationMetadata(context, registration, "eperson.firstname", getFirstName(person));
|
||||
setRegistrationMetadata(context, registration, "eperson.lastname", getLastName(person));
|
||||
registrationDataService.setRegistrationMetadataValue(context, registration, "eperson", "orcid", null, orcid);
|
||||
|
||||
for (String scope : token.getScopeAsArray()) {
|
||||
registrationDataService.addMetadata(context, registration, "eperson", "orcid", "scope", scope);
|
||||
}
|
||||
}
|
||||
|
||||
private void setRegistrationMetadata(
|
||||
Context context, RegistrationData registration, String metadataString, String value) {
|
||||
String[] split = metadataString.split("\\.");
|
||||
String qualifier = split.length > 2 ? split[2] : null;
|
||||
try {
|
||||
registrationDataService.setRegistrationMetadataValue(
|
||||
context, registration, split[0], split[1], qualifier, value
|
||||
);
|
||||
} catch (SQLException | AuthorizeException ex) {
|
||||
LOGGER.error("An error occurs setting metadata", ex);
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,16 +317,20 @@ public class OrcidAuthenticationBean implements AuthenticationMethod {
|
||||
return Optional.ofNullable(emails.get(0).getEmail());
|
||||
}
|
||||
|
||||
private Optional<String> getFirstName(Person person) {
|
||||
private String getFirstName(Person person) {
|
||||
return Optional.ofNullable(person.getName())
|
||||
.map(name -> name.getGivenNames())
|
||||
.map(givenNames -> givenNames.getContent());
|
||||
.map(name -> name.getGivenNames())
|
||||
.map(givenNames -> givenNames.getContent())
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.orElse(ORCID_DEFAULT_FIRSTNAME);
|
||||
}
|
||||
|
||||
private Optional<String> getLastName(Person person) {
|
||||
private String getLastName(Person person) {
|
||||
return Optional.ofNullable(person.getName())
|
||||
.map(name -> name.getFamilyName())
|
||||
.map(givenNames -> givenNames.getContent());
|
||||
.map(name -> name.getFamilyName())
|
||||
.map(givenNames -> givenNames.getContent())
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.orElse(ORCID_DEFAULT_LASTNAME);
|
||||
}
|
||||
|
||||
private boolean canSelfRegister() {
|
||||
|
@@ -9,22 +9,36 @@ package org.dspace.eperson;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jakarta.mail.MessagingException;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.dspace.authenticate.service.AuthenticationService;
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.content.MetadataValue;
|
||||
import org.dspace.content.service.MetadataValueService;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.core.Email;
|
||||
import org.dspace.core.I18nUtil;
|
||||
import org.dspace.core.Utils;
|
||||
import org.dspace.eperson.dto.RegistrationDataPatch;
|
||||
import org.dspace.eperson.service.AccountService;
|
||||
import org.dspace.eperson.service.EPersonService;
|
||||
import org.dspace.eperson.service.GroupService;
|
||||
import org.dspace.eperson.service.RegistrationDataService;
|
||||
import org.dspace.services.ConfigurationService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.log.LogMessage;
|
||||
|
||||
/**
|
||||
* Methods for handling registration by email and forgotten passwords. When
|
||||
@@ -45,16 +59,30 @@ public class AccountServiceImpl implements AccountService {
|
||||
* log4j log
|
||||
*/
|
||||
private static final Logger log = LogManager.getLogger(AccountServiceImpl.class);
|
||||
|
||||
private static final Map<String, BiConsumer<RegistrationData, EPerson>> allowedMergeArguments =
|
||||
Map.of(
|
||||
"email",
|
||||
(RegistrationData registrationData, EPerson eperson) -> eperson.setEmail(registrationData.getEmail())
|
||||
);
|
||||
|
||||
@Autowired(required = true)
|
||||
protected EPersonService ePersonService;
|
||||
|
||||
@Autowired(required = true)
|
||||
protected RegistrationDataService registrationDataService;
|
||||
@Autowired
|
||||
private ConfigurationService configurationService;
|
||||
|
||||
@Autowired
|
||||
private GroupService groupService;
|
||||
|
||||
@Autowired
|
||||
private AuthenticationService authenticationService;
|
||||
|
||||
@Autowired
|
||||
private MetadataValueService metadataValueService;
|
||||
|
||||
protected AccountServiceImpl() {
|
||||
|
||||
}
|
||||
@@ -86,7 +114,7 @@ public class AccountServiceImpl implements AccountService {
|
||||
if (!authenticationService.canSelfRegister(context, null, email)) {
|
||||
throw new IllegalStateException("self registration is not allowed with this email address");
|
||||
}
|
||||
sendInfo(context, email, true, true);
|
||||
sendInfo(context, email, RegistrationTypeEnum.REGISTER, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,9 +138,27 @@ public class AccountServiceImpl implements AccountService {
|
||||
*/
|
||||
@Override
|
||||
public void sendForgotPasswordInfo(Context context, String email)
|
||||
throws SQLException, IOException, MessagingException,
|
||||
AuthorizeException {
|
||||
sendInfo(context, email, false, true);
|
||||
throws SQLException, IOException, MessagingException, AuthorizeException {
|
||||
sendInfo(context, email, RegistrationTypeEnum.FORGOT, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if exists an account related to the token provided
|
||||
*
|
||||
* @param context DSpace context
|
||||
* @param token Account token
|
||||
* @return true if exists, false otherwise
|
||||
* @throws SQLException
|
||||
* @throws AuthorizeException
|
||||
*/
|
||||
@Override
|
||||
public boolean existsAccountFor(Context context, String token) throws SQLException, AuthorizeException {
|
||||
return getEPerson(context, token) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existsAccountWithEmail(Context context, String email) throws SQLException {
|
||||
return ePersonService.findByEmail(context, email) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -179,6 +225,271 @@ public class AccountServiceImpl implements AccountService {
|
||||
registrationDataService.deleteByToken(context, token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EPerson mergeRegistration(Context context, UUID personId, String token, List<String> overrides)
|
||||
throws AuthorizeException, SQLException {
|
||||
|
||||
RegistrationData registrationData = getRegistrationData(context, token);
|
||||
EPerson eperson = null;
|
||||
if (personId != null) {
|
||||
eperson = ePersonService.findByIdOrLegacyId(context, personId.toString());
|
||||
}
|
||||
|
||||
if (!canCreateUserBy(context, registrationData.getRegistrationType())) {
|
||||
throw new AuthorizeException("Token type invalid for the current user.");
|
||||
}
|
||||
|
||||
if (hasLoggedEPerson(context) && !isSameContextEPerson(context, eperson)) {
|
||||
throw new AuthorizeException("Only the user with id: " + personId + " can make this action.");
|
||||
}
|
||||
|
||||
context.turnOffAuthorisationSystem();
|
||||
|
||||
eperson = Optional.ofNullable(eperson).orElseGet(() -> createEPerson(context, registrationData));
|
||||
updateValuesFromRegistration(context, eperson, registrationData, overrides);
|
||||
deleteToken(context, token);
|
||||
ePersonService.update(context, eperson);
|
||||
|
||||
context.commit();
|
||||
context.restoreAuthSystemState();
|
||||
|
||||
return eperson;
|
||||
}
|
||||
|
||||
private EPerson createEPerson(Context context, RegistrationData registrationData) {
|
||||
EPerson eperson;
|
||||
try {
|
||||
eperson = ePersonService.create(context);
|
||||
|
||||
eperson.setNetid(registrationData.getNetId());
|
||||
eperson.setEmail(registrationData.getEmail());
|
||||
|
||||
RegistrationDataMetadata firstName =
|
||||
registrationDataService.getMetadataByMetadataString(
|
||||
registrationData,
|
||||
"eperson.firstname"
|
||||
);
|
||||
if (firstName != null) {
|
||||
eperson.setFirstName(context, firstName.getValue());
|
||||
}
|
||||
|
||||
RegistrationDataMetadata lastName =
|
||||
registrationDataService.getMetadataByMetadataString(
|
||||
registrationData,
|
||||
"eperson.lastname"
|
||||
);
|
||||
if (lastName != null) {
|
||||
eperson.setLastName(context, lastName.getValue());
|
||||
}
|
||||
eperson.setCanLogIn(true);
|
||||
eperson.setSelfRegistered(true);
|
||||
} catch (SQLException | AuthorizeException e) {
|
||||
throw new RuntimeException(
|
||||
"Cannote create the eperson linked to the token: " + registrationData.getToken(),
|
||||
e
|
||||
);
|
||||
}
|
||||
return eperson;
|
||||
}
|
||||
|
||||
private boolean hasLoggedEPerson(Context context) {
|
||||
return context.getCurrentUser() != null;
|
||||
}
|
||||
|
||||
private boolean isSameContextEPerson(Context context, EPerson eperson) {
|
||||
return context.getCurrentUser().equals(eperson);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegistrationData renewRegistrationForEmail(
|
||||
Context context, RegistrationDataPatch registrationDataPatch
|
||||
) throws AuthorizeException {
|
||||
try {
|
||||
RegistrationData newRegistration = registrationDataService.clone(context, registrationDataPatch);
|
||||
registrationDataService.delete(context, registrationDataPatch.getOldRegistration());
|
||||
sendRegistationLinkByEmail(context, newRegistration);
|
||||
return newRegistration;
|
||||
} catch (SQLException | MessagingException | IOException e) {
|
||||
log.error(e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isEmailConfirmed(RegistrationData oldRegistration, String email) {
|
||||
return email.equals(oldRegistration.getEmail());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTokenValidForCreation(RegistrationData registrationData) {
|
||||
return (
|
||||
isExternalRegistrationToken(registrationData.getRegistrationType()) ||
|
||||
isValidationToken(registrationData.getRegistrationType())
|
||||
) &&
|
||||
StringUtils.isNotBlank(registrationData.getNetId());
|
||||
}
|
||||
|
||||
private boolean canCreateUserBy(Context context, RegistrationTypeEnum registrationTypeEnum) {
|
||||
return isValidationToken(registrationTypeEnum) ||
|
||||
canCreateUserFromExternalRegistrationToken(context, registrationTypeEnum);
|
||||
}
|
||||
|
||||
private static boolean canCreateUserFromExternalRegistrationToken(
|
||||
Context context, RegistrationTypeEnum registrationTypeEnum
|
||||
) {
|
||||
return context.getCurrentUser() != null && isExternalRegistrationToken(registrationTypeEnum);
|
||||
}
|
||||
|
||||
private static boolean isExternalRegistrationToken(RegistrationTypeEnum registrationTypeEnum) {
|
||||
return RegistrationTypeEnum.ORCID.equals(registrationTypeEnum);
|
||||
}
|
||||
|
||||
private static boolean isValidationToken(RegistrationTypeEnum registrationTypeEnum) {
|
||||
return RegistrationTypeEnum.VALIDATION_ORCID.equals(registrationTypeEnum);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates Eperson using the provided {@link RegistrationData}.<br/>
|
||||
* Tries to replace {@code metadata} already set inside the {@link EPerson} with the ones
|
||||
* listed inside the {@code overrides} field by taking the value from the {@link RegistrationData}. <br/>
|
||||
* Updates the empty values inside the {@link EPerson} by taking them directly from the {@link RegistrationData},
|
||||
* according to the method {@link AccountServiceImpl#getUpdateActions(Context, EPerson, RegistrationData)}
|
||||
*
|
||||
* @param context The DSpace context
|
||||
* @param eperson The EPerson that should be updated
|
||||
* @param registrationData The RegistrationData related to that EPerson
|
||||
* @param overrides List of metadata that will be overwritten inside the EPerson
|
||||
*/
|
||||
protected void updateValuesFromRegistration(
|
||||
Context context, EPerson eperson, RegistrationData registrationData, List<String> overrides
|
||||
) {
|
||||
Stream.concat(
|
||||
getMergeActions(registrationData, overrides),
|
||||
getUpdateActions(context, eperson, registrationData)
|
||||
).forEach(c -> c.accept(eperson));
|
||||
}
|
||||
|
||||
private Stream<Consumer<EPerson>> getMergeActions(RegistrationData registrationData, List<String> overrides) {
|
||||
if (overrides == null || overrides.isEmpty()) {
|
||||
return Stream.empty();
|
||||
}
|
||||
return overrides.stream().map(f -> mergeField(f, registrationData));
|
||||
}
|
||||
|
||||
/**
|
||||
* This methods tries to fullfill missing values inside the {@link EPerson} by taking them directly from the
|
||||
* {@link RegistrationData}. <br/>
|
||||
* Returns a {@link Stream} of consumers that will be evaluated on an {@link EPerson}, this stream contains
|
||||
* the following actions:
|
||||
* <ul>
|
||||
* <li>Copies {@code netId} and {@code email} to the {@link EPerson} <br/></li>
|
||||
* <li>Copies any {@link RegistrationData#metadata} inside {@link EPerson#metadata} if isn't already set.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param context DSpace context
|
||||
* @param eperson EPerson that will be evaluated
|
||||
* @param registrationData RegistrationData used as a base to copy value from.
|
||||
* @return a stream of consumers to be evaluated on EPerson.
|
||||
*/
|
||||
protected Stream<Consumer<EPerson>> getUpdateActions(
|
||||
Context context, EPerson eperson, RegistrationData registrationData
|
||||
) {
|
||||
Stream.Builder<Consumer<EPerson>> actions = Stream.builder();
|
||||
if (eperson.getNetid() == null) {
|
||||
actions.add(p -> p.setNetid(registrationData.getNetId()));
|
||||
}
|
||||
if (eperson.getEmail() == null) {
|
||||
actions.add(p -> p.setEmail(registrationData.getEmail()));
|
||||
}
|
||||
for (RegistrationDataMetadata metadatum : registrationData.getMetadata()) {
|
||||
Optional<List<MetadataValue>> epersonMetadata =
|
||||
Optional.ofNullable(
|
||||
ePersonService.getMetadataByMetadataString(
|
||||
eperson, metadatum.getMetadataField().toString('.')
|
||||
)
|
||||
).filter(l -> !l.isEmpty());
|
||||
if (epersonMetadata.isEmpty()) {
|
||||
actions.add(p -> addMetadataValue(context, metadatum, p));
|
||||
}
|
||||
}
|
||||
return actions.build();
|
||||
}
|
||||
|
||||
private List<MetadataValue> addMetadataValue(Context context, RegistrationDataMetadata metadatum, EPerson p) {
|
||||
try {
|
||||
return ePersonService.addMetadata(
|
||||
context, p, metadatum.getMetadataField(), Item.ANY, List.of(metadatum.getValue())
|
||||
);
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(
|
||||
"Could not add metadata" + metadatum.getMetadataField() + " to eperson with uuid: " + p.getID(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns a Consumer that will override a given {@link MetadataValue} of the {@link EPerson} by taking
|
||||
* that directly from the {@link RegistrationData}.
|
||||
*
|
||||
* @param field The metadatafield
|
||||
* @param registrationData The RegistrationData where the metadata wil be taken
|
||||
* @return a Consumer of the person that will replace that field
|
||||
*/
|
||||
protected Consumer<EPerson> mergeField(String field, RegistrationData registrationData) {
|
||||
return person ->
|
||||
allowedMergeArguments.getOrDefault(
|
||||
field,
|
||||
mergeRegistrationMetadata(field)
|
||||
).accept(registrationData, person);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns a {@link BiConsumer} that can be evaluated on any {@link RegistrationData} and
|
||||
* {@link EPerson} in order to replace the value of the metadata {@code field} placed on the {@link EPerson}
|
||||
* by taking the value directly from the {@link RegistrationData}.
|
||||
*
|
||||
* @param field The metadata that will be overwritten inside the {@link EPerson}
|
||||
* @return a BiConsumer
|
||||
*/
|
||||
protected BiConsumer<RegistrationData, EPerson> mergeRegistrationMetadata(String field) {
|
||||
return (registrationData, person) -> {
|
||||
RegistrationDataMetadata registrationMetadata = getMetadataOrThrow(registrationData, field);
|
||||
MetadataValue metadata = getMetadataOrThrow(person, field);
|
||||
metadata.setValue(registrationMetadata.getValue());
|
||||
ePersonService.setMetadataModified(person);
|
||||
};
|
||||
}
|
||||
|
||||
private RegistrationDataMetadata getMetadataOrThrow(RegistrationData registrationData, String field) {
|
||||
return registrationDataService.getMetadataByMetadataString(registrationData, field);
|
||||
}
|
||||
|
||||
private MetadataValue getMetadataOrThrow(EPerson eperson, String field) {
|
||||
return ePersonService.getMetadataByMetadataString(eperson, field).stream().findFirst()
|
||||
.orElseThrow(
|
||||
() -> new IllegalArgumentException(
|
||||
"Could not find the metadata field: " + field + " for eperson: " + eperson.getID())
|
||||
);
|
||||
}
|
||||
|
||||
private RegistrationData getRegistrationData(Context context, String token)
|
||||
throws SQLException, AuthorizeException {
|
||||
return Optional.ofNullable(registrationDataService.findByToken(context, token))
|
||||
.filter(rd ->
|
||||
isValid(rd) ||
|
||||
!isValidationToken(rd.getRegistrationType())
|
||||
)
|
||||
.orElseThrow(
|
||||
() -> new AuthorizeException(
|
||||
"The registration token: " + token + " is not valid!"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private boolean isValid(RegistrationData rd) {
|
||||
return registrationDataService.isValid(rd);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* THIS IS AN INTERNAL METHOD. THE SEND PARAMETER ALLOWS IT TO BE USED FOR
|
||||
* TESTING PURPOSES.
|
||||
@@ -191,8 +502,7 @@ public class AccountServiceImpl implements AccountService {
|
||||
*
|
||||
* @param context DSpace context
|
||||
* @param email Email address to send the forgot-password email to
|
||||
* @param isRegister If true, this is for registration; otherwise, it is
|
||||
* for forgot-password
|
||||
* @param type Type of registration {@link RegistrationTypeEnum}
|
||||
* @param send If true, send email; otherwise do not send any email
|
||||
* @return null if no EPerson with that email found
|
||||
* @throws SQLException Cannot create registration data in database
|
||||
@@ -200,16 +510,17 @@ public class AccountServiceImpl implements AccountService {
|
||||
* @throws IOException Error reading email template
|
||||
* @throws AuthorizeException Authorization error
|
||||
*/
|
||||
protected RegistrationData sendInfo(Context context, String email,
|
||||
boolean isRegister, boolean send) throws SQLException, IOException,
|
||||
MessagingException, AuthorizeException {
|
||||
protected RegistrationData sendInfo(
|
||||
Context context, String email, RegistrationTypeEnum type, boolean send
|
||||
) throws SQLException, IOException, MessagingException, AuthorizeException {
|
||||
// See if a registration token already exists for this user
|
||||
RegistrationData rd = registrationDataService.findByEmail(context, email);
|
||||
|
||||
RegistrationData rd = registrationDataService.findBy(context, email, type);
|
||||
boolean isRegister = RegistrationTypeEnum.REGISTER.equals(type);
|
||||
|
||||
// If it already exists, just re-issue it
|
||||
if (rd == null) {
|
||||
rd = registrationDataService.create(context);
|
||||
rd.setRegistrationType(type);
|
||||
rd.setToken(Utils.generateHexKey());
|
||||
|
||||
// don't set expiration date any more
|
||||
@@ -229,7 +540,7 @@ public class AccountServiceImpl implements AccountService {
|
||||
}
|
||||
|
||||
if (send) {
|
||||
sendEmail(context, email, isRegister, rd);
|
||||
fillAndSendEmail(context, email, isRegister, rd);
|
||||
}
|
||||
|
||||
return rd;
|
||||
@@ -250,7 +561,7 @@ public class AccountServiceImpl implements AccountService {
|
||||
* @throws IOException A general class of exceptions produced by failed or interrupted I/O operations.
|
||||
* @throws SQLException An exception that provides information on a database access error or other errors.
|
||||
*/
|
||||
protected void sendEmail(Context context, String email, boolean isRegister, RegistrationData rd)
|
||||
protected void fillAndSendEmail(Context context, String email, boolean isRegister, RegistrationData rd)
|
||||
throws MessagingException, IOException, SQLException {
|
||||
String base = configurationService.getProperty("dspace.ui.url");
|
||||
|
||||
@@ -261,11 +572,9 @@ public class AccountServiceImpl implements AccountService {
|
||||
.append(rd.getToken())
|
||||
.toString();
|
||||
Locale locale = context.getCurrentLocale();
|
||||
Email bean = Email.getEmail(I18nUtil.getEmailFilename(locale, isRegister ? "register"
|
||||
: "change_password"));
|
||||
bean.addRecipient(email);
|
||||
bean.addArgument(specialLink);
|
||||
bean.send();
|
||||
String emailFilename = I18nUtil.getEmailFilename(locale, isRegister ? "register" : "change_password");
|
||||
|
||||
fillAndSendEmail(email, emailFilename, specialLink);
|
||||
|
||||
// Breadcrumbs
|
||||
if (log.isInfoEnabled()) {
|
||||
@@ -273,4 +582,64 @@ public class AccountServiceImpl implements AccountService {
|
||||
+ " information to " + email);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns a link that will point to the Angular UI that will be used by the user to complete the
|
||||
* registration process.
|
||||
*
|
||||
* @param base is the UI url of DSpace
|
||||
* @param rd is the RegistrationData related to the user
|
||||
* @param subPath is the specific page that will be loaded on the FE
|
||||
* @return String that represents that link
|
||||
*/
|
||||
private static String getSpecialLink(String base, RegistrationData rd, String subPath) {
|
||||
return new StringBuffer(base)
|
||||
.append(base.endsWith("/") ? "" : "/")
|
||||
.append(subPath)
|
||||
.append("/")
|
||||
.append(rd.getToken())
|
||||
.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills out a given email template obtained starting from the {@link RegistrationTypeEnum}.
|
||||
*
|
||||
* @param context The DSpace Context
|
||||
* @param rd The RegistrationData that will be used as a registration.
|
||||
* @throws MessagingException
|
||||
* @throws IOException
|
||||
*/
|
||||
protected void sendRegistationLinkByEmail(
|
||||
Context context, RegistrationData rd
|
||||
) throws MessagingException, IOException {
|
||||
String base = configurationService.getProperty("dspace.ui.url");
|
||||
|
||||
// Note change from "key=" to "token="
|
||||
String specialLink = getSpecialLink(base, rd, rd.getRegistrationType().getLink());
|
||||
|
||||
String emailFilename = I18nUtil.getEmailFilename(
|
||||
context.getCurrentLocale(), rd.getRegistrationType().toString().toLowerCase()
|
||||
);
|
||||
|
||||
fillAndSendEmail(rd.getEmail(), emailFilename, specialLink);
|
||||
|
||||
log.info(LogMessage.of(() -> "Sent " + rd.getRegistrationType().getLink() + " link to " + rd.getEmail()));
|
||||
}
|
||||
|
||||
/**
|
||||
* This method fills out the given email with all the fields and sends out the email.
|
||||
*
|
||||
* @param email - The recipient
|
||||
* @param emailFilename The name of the email
|
||||
* @param specialLink - The link that will be set inside the email
|
||||
* @throws IOException
|
||||
* @throws MessagingException
|
||||
*/
|
||||
protected void fillAndSendEmail(String email, String emailFilename, String specialLink)
|
||||
throws IOException, MessagingException {
|
||||
Email bean = Email.getEmail(emailFilename);
|
||||
bean.addRecipient(email);
|
||||
bean.addArgument(specialLink);
|
||||
bean.send();
|
||||
}
|
||||
}
|
||||
|
@@ -8,16 +8,24 @@
|
||||
package org.dspace.eperson;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.SequenceGenerator;
|
||||
import jakarta.persistence.Table;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.core.ReloadableEntity;
|
||||
import org.hibernate.annotations.SortNatural;
|
||||
|
||||
/**
|
||||
* Database entity representation of the registrationdata table
|
||||
@@ -34,21 +42,65 @@ public class RegistrationData implements ReloadableEntity<Integer> {
|
||||
@SequenceGenerator(name = "registrationdata_seq", sequenceName = "registrationdata_seq", allocationSize = 1)
|
||||
private Integer id;
|
||||
|
||||
@Column(name = "email", unique = true, length = 64)
|
||||
/**
|
||||
* Contains the email used to register the user.
|
||||
*/
|
||||
@Column(name = "email", length = 64)
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* Contains the unique id generated fot the user.
|
||||
*/
|
||||
@Column(name = "token", length = 48)
|
||||
private String token;
|
||||
|
||||
/**
|
||||
* Expiration date of this registration data.
|
||||
*/
|
||||
@Column(name = "expires")
|
||||
private Instant expires;
|
||||
|
||||
/**
|
||||
* Metadata linked to this registration data
|
||||
*/
|
||||
@SortNatural
|
||||
@OneToMany(
|
||||
fetch = FetchType.LAZY,
|
||||
mappedBy = "registrationData",
|
||||
cascade = CascadeType.ALL,
|
||||
orphanRemoval = true
|
||||
)
|
||||
private SortedSet<RegistrationDataMetadata> metadata = new TreeSet<>();
|
||||
|
||||
/**
|
||||
* External service used to register the user.
|
||||
* Allowed values are inside {@link RegistrationTypeEnum}
|
||||
*/
|
||||
@Column(name = "registration_type")
|
||||
@Enumerated(EnumType.STRING)
|
||||
private RegistrationTypeEnum registrationType;
|
||||
|
||||
/**
|
||||
* Contains the external id provided by the external service
|
||||
* accordingly to the registration type.
|
||||
*/
|
||||
@Column(name = "net_id", length = 64)
|
||||
private final String netId;
|
||||
|
||||
/**
|
||||
* Protected constructor, create object using:
|
||||
* {@link org.dspace.eperson.service.RegistrationDataService#create(Context)}
|
||||
*/
|
||||
protected RegistrationData() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Protected constructor, create object using:
|
||||
* {@link org.dspace.eperson.service.RegistrationDataService#create(Context, String)}
|
||||
*/
|
||||
protected RegistrationData(String netId) {
|
||||
this.netId = netId;
|
||||
}
|
||||
|
||||
public Integer getID() {
|
||||
@@ -59,7 +111,7 @@ public class RegistrationData implements ReloadableEntity<Integer> {
|
||||
return email;
|
||||
}
|
||||
|
||||
void setEmail(String email) {
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
@@ -78,4 +130,24 @@ public class RegistrationData implements ReloadableEntity<Integer> {
|
||||
void setExpires(Instant expires) {
|
||||
this.expires = expires;
|
||||
}
|
||||
|
||||
public RegistrationTypeEnum getRegistrationType() {
|
||||
return registrationType;
|
||||
}
|
||||
|
||||
public void setRegistrationType(RegistrationTypeEnum registrationType) {
|
||||
this.registrationType = registrationType;
|
||||
}
|
||||
|
||||
public SortedSet<RegistrationDataMetadata> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
public void setMetadata(SortedSet<RegistrationDataMetadata> metadata) {
|
||||
this.metadata = metadata;
|
||||
}
|
||||
|
||||
public String getNetId() {
|
||||
return netId;
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* 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.eperson;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dspace.services.ConfigurationService;
|
||||
import org.dspace.services.factory.DSpaceServicesFactory;
|
||||
|
||||
/**
|
||||
* Singleton that encapsulates the configuration of each different token {@link RegistrationTypeEnum} duration. <br/>
|
||||
* Contains also utility methods to compute the expiration date of the registered token.
|
||||
*
|
||||
* @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com)
|
||||
**/
|
||||
public class RegistrationDataExpirationConfiguration {
|
||||
|
||||
private static final String EXPIRATION_PROP = "eperson.registration-data.token.{0}.expiration";
|
||||
private static final String DURATION_FORMAT = "PT{0}";
|
||||
|
||||
public static final RegistrationDataExpirationConfiguration INSTANCE =
|
||||
new RegistrationDataExpirationConfiguration();
|
||||
|
||||
public static RegistrationDataExpirationConfiguration getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
private final Map<RegistrationTypeEnum, Duration> expirationMap;
|
||||
|
||||
private RegistrationDataExpirationConfiguration() {
|
||||
this.expirationMap =
|
||||
Stream.of(RegistrationTypeEnum.values())
|
||||
.map(type -> Optional.ofNullable(getDurationOf(type))
|
||||
.map(duration -> Map.entry(type, duration))
|
||||
.orElse(null)
|
||||
)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
}
|
||||
|
||||
private Duration getDurationOf(RegistrationTypeEnum type) {
|
||||
String format = MessageFormat.format(EXPIRATION_PROP, type.toString().toLowerCase());
|
||||
ConfigurationService config = DSpaceServicesFactory.getInstance().getConfigurationService();
|
||||
String typeValue = config.getProperty(format);
|
||||
|
||||
if (StringUtils.isBlank(typeValue)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Duration.parse(MessageFormat.format(DURATION_FORMAT, typeValue));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the {@link Duration} configuration of a given {@link RegistrationTypeEnum}.
|
||||
*
|
||||
* @param type is the type of the given registration token
|
||||
* @return the {@link Duration} of that specific token.
|
||||
*/
|
||||
public Duration getExpiration(RegistrationTypeEnum type) {
|
||||
return expirationMap.get(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the expiration date of the given {@link RegistrationTypeEnum}.
|
||||
*
|
||||
* @param type is the RegistrationTypeEnum of the token
|
||||
* @return a Date that represents the expiration date.
|
||||
*/
|
||||
public Instant computeExpirationDate(RegistrationTypeEnum type) {
|
||||
|
||||
if (type == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Duration duration = this.expirationMap.get(type);
|
||||
|
||||
if (duration == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Instant.now().plus(duration);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* 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.eperson;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.SequenceGenerator;
|
||||
import jakarta.persistence.Table;
|
||||
import org.dspace.content.MetadataField;
|
||||
import org.dspace.core.ReloadableEntity;
|
||||
import org.hibernate.Length;
|
||||
|
||||
/**
|
||||
* Metadata related to a registration data {@link RegistrationData}
|
||||
*
|
||||
* @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com)
|
||||
**/
|
||||
@Entity
|
||||
@Table(name = "registrationdata_metadata")
|
||||
public class RegistrationDataMetadata implements ReloadableEntity<Integer>, Comparable<RegistrationDataMetadata> {
|
||||
|
||||
@Id
|
||||
@Column(name = "registrationdata_metadata_id")
|
||||
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "registrationdata_metadatavalue_seq")
|
||||
@SequenceGenerator(
|
||||
name = "registrationdata_metadatavalue_seq",
|
||||
sequenceName = "registrationdata_metadatavalue_seq",
|
||||
allocationSize = 1
|
||||
)
|
||||
private final Integer id;
|
||||
|
||||
/**
|
||||
* {@link RegistrationData} linked to this metadata value
|
||||
*/
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "registrationdata_id")
|
||||
private RegistrationData registrationData = null;
|
||||
|
||||
/**
|
||||
* The linked {@link MetadataField} instance
|
||||
*/
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "metadata_field_id")
|
||||
private MetadataField metadataField = null;
|
||||
|
||||
/**
|
||||
* Value represented by this {@link RegistrationDataMetadata} instance
|
||||
* related to the metadataField {@link MetadataField}
|
||||
*/
|
||||
@Column(name = "text_value", length = Length.LONG32)
|
||||
private String value = null;
|
||||
|
||||
/**
|
||||
* Protected constructor
|
||||
*/
|
||||
protected RegistrationDataMetadata() {
|
||||
id = 0;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Integer getID() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public MetadataField getMetadataField() {
|
||||
return metadataField;
|
||||
}
|
||||
|
||||
void setMetadataField(MetadataField metadataField) {
|
||||
this.metadataField = metadataField;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(RegistrationDataMetadata o) {
|
||||
return Integer.compare(this.id, o.id);
|
||||
}
|
||||
|
||||
void setRegistrationData(RegistrationData registrationData) {
|
||||
this.registrationData = registrationData;
|
||||
}
|
||||
|
||||
public RegistrationData getRegistrationData() {
|
||||
return registrationData;
|
||||
}
|
||||
}
|
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* 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.eperson;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
import org.dspace.content.MetadataField;
|
||||
import org.dspace.content.service.MetadataFieldService;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.eperson.dao.RegistrationDataMetadataDAO;
|
||||
import org.dspace.eperson.service.RegistrationDataMetadataService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com)
|
||||
**/
|
||||
public class RegistrationDataMetadataServiceImpl implements RegistrationDataMetadataService {
|
||||
|
||||
@Autowired
|
||||
private RegistrationDataMetadataDAO registrationDataMetadataDAO;
|
||||
|
||||
@Autowired
|
||||
private MetadataFieldService metadataFieldService;
|
||||
|
||||
@Override
|
||||
public RegistrationDataMetadata create(Context context, RegistrationData registrationData, String schema,
|
||||
String element, String qualifier, String value) throws SQLException {
|
||||
return create(
|
||||
context, registrationData,
|
||||
metadataFieldService.findByElement(context, schema, element, qualifier),
|
||||
value
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegistrationDataMetadata create(Context context, RegistrationData registrationData,
|
||||
MetadataField metadataField) throws SQLException {
|
||||
RegistrationDataMetadata metadata = new RegistrationDataMetadata();
|
||||
metadata.setRegistrationData(registrationData);
|
||||
metadata.setMetadataField(metadataField);
|
||||
return registrationDataMetadataDAO.create(context, metadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegistrationDataMetadata create(
|
||||
Context context, RegistrationData registrationData, MetadataField metadataField, String value
|
||||
) throws SQLException {
|
||||
RegistrationDataMetadata metadata = new RegistrationDataMetadata();
|
||||
metadata.setRegistrationData(registrationData);
|
||||
metadata.setMetadataField(metadataField);
|
||||
metadata.setValue(value);
|
||||
return registrationDataMetadataDAO.create(context, metadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegistrationDataMetadata create(Context context) throws SQLException, AuthorizeException {
|
||||
return registrationDataMetadataDAO.create(context, new RegistrationDataMetadata());
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegistrationDataMetadata find(Context context, int id) throws SQLException {
|
||||
return registrationDataMetadataDAO.findByID(context, RegistrationDataMetadata.class, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Context context, RegistrationDataMetadata registrationDataMetadata)
|
||||
throws SQLException, AuthorizeException {
|
||||
registrationDataMetadataDAO.save(context, registrationDataMetadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Context context, List<RegistrationDataMetadata> t) throws SQLException, AuthorizeException {
|
||||
for (RegistrationDataMetadata registrationDataMetadata : t) {
|
||||
update(context, registrationDataMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(Context context, RegistrationDataMetadata registrationDataMetadata)
|
||||
throws SQLException, AuthorizeException {
|
||||
registrationDataMetadataDAO.delete(context, registrationDataMetadata);
|
||||
}
|
||||
}
|
@@ -8,13 +8,26 @@
|
||||
package org.dspace.eperson;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
import org.dspace.content.MetadataField;
|
||||
import org.dspace.content.MetadataValue;
|
||||
import org.dspace.content.service.MetadataFieldService;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.core.Utils;
|
||||
import org.dspace.eperson.dao.RegistrationDataDAO;
|
||||
import org.dspace.eperson.dto.RegistrationDataChanges;
|
||||
import org.dspace.eperson.dto.RegistrationDataPatch;
|
||||
import org.dspace.eperson.service.RegistrationDataMetadataService;
|
||||
import org.dspace.eperson.service.RegistrationDataService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
@@ -26,19 +39,67 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
* @author kevinvandevelde at atmire.com
|
||||
*/
|
||||
public class RegistrationDataServiceImpl implements RegistrationDataService {
|
||||
@Autowired(required = true)
|
||||
@Autowired()
|
||||
protected RegistrationDataDAO registrationDataDAO;
|
||||
|
||||
@Autowired()
|
||||
protected RegistrationDataMetadataService registrationDataMetadataService;
|
||||
|
||||
@Autowired()
|
||||
protected MetadataFieldService metadataFieldService;
|
||||
|
||||
protected RegistrationDataExpirationConfiguration expirationConfiguration =
|
||||
RegistrationDataExpirationConfiguration.getInstance();
|
||||
|
||||
protected RegistrationDataServiceImpl() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegistrationData create(Context context) throws SQLException, AuthorizeException {
|
||||
return registrationDataDAO.create(context, new RegistrationData());
|
||||
return create(context, null, null);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RegistrationData create(Context context, String netId) throws SQLException, AuthorizeException {
|
||||
return this.create(context, netId, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegistrationData create(Context context, String netId, RegistrationTypeEnum type)
|
||||
throws SQLException, AuthorizeException {
|
||||
return registrationDataDAO.create(context, newInstance(netId, type, null));
|
||||
}
|
||||
|
||||
private RegistrationData newInstance(String netId, RegistrationTypeEnum type, String email) {
|
||||
RegistrationData rd = new RegistrationData(netId);
|
||||
rd.setToken(Utils.generateHexKey());
|
||||
rd.setRegistrationType(type);
|
||||
rd.setExpires(expirationConfiguration.computeExpirationDate(type));
|
||||
rd.setEmail(email);
|
||||
return rd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegistrationData clone(
|
||||
Context context, RegistrationDataPatch registrationDataPatch
|
||||
) throws SQLException, AuthorizeException {
|
||||
RegistrationData old = registrationDataPatch.getOldRegistration();
|
||||
RegistrationDataChanges changes = registrationDataPatch.getChanges();
|
||||
RegistrationData rd = newInstance(old.getNetId(), changes.getRegistrationType(), changes.getEmail());
|
||||
|
||||
for (RegistrationDataMetadata metadata : old.getMetadata()) {
|
||||
addMetadata(context, rd, metadata.getMetadataField(), metadata.getValue());
|
||||
}
|
||||
|
||||
return registrationDataDAO.create(context, rd);
|
||||
}
|
||||
|
||||
private boolean isEmailConfirmed(RegistrationData old, String newEmail) {
|
||||
return newEmail.equals(old.getEmail());
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegistrationData findByToken(Context context, String token) throws SQLException {
|
||||
return registrationDataDAO.findByToken(context, token);
|
||||
@@ -49,12 +110,124 @@ public class RegistrationDataServiceImpl implements RegistrationDataService {
|
||||
return registrationDataDAO.findByEmail(context, email);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegistrationData findBy(Context context, String email, RegistrationTypeEnum type) throws SQLException {
|
||||
return registrationDataDAO.findBy(context, email, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteByToken(Context context, String token) throws SQLException {
|
||||
registrationDataDAO.deleteByToken(context, token);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Map.Entry<RegistrationDataMetadata, Optional<MetadataValue>>> groupEpersonMetadataByRegistrationData(
|
||||
EPerson ePerson, RegistrationData registrationData
|
||||
)
|
||||
throws SQLException {
|
||||
Map<MetadataField, List<MetadataValue>> epersonMeta =
|
||||
ePerson.getMetadata()
|
||||
.stream()
|
||||
.collect(
|
||||
Collectors.groupingBy(
|
||||
MetadataValue::getMetadataField
|
||||
)
|
||||
);
|
||||
return registrationData.getMetadata()
|
||||
.stream()
|
||||
.map(meta ->
|
||||
Map.entry(
|
||||
meta,
|
||||
Optional.ofNullable(epersonMeta.get(meta.getMetadataField()))
|
||||
.filter(list -> list.size() == 1)
|
||||
.map(values -> values.get(0))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRegistrationMetadataValue(
|
||||
Context context, RegistrationData registration, String schema, String element, String qualifier, String value
|
||||
) throws SQLException, AuthorizeException {
|
||||
|
||||
List<RegistrationDataMetadata> metadata =
|
||||
registration.getMetadata()
|
||||
.stream()
|
||||
.filter(m -> areEquals(m, schema, element, qualifier))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (metadata.size() > 1) {
|
||||
throw new IllegalStateException("Find more than one registration metadata to update!");
|
||||
}
|
||||
|
||||
RegistrationDataMetadata registrationDataMetadata;
|
||||
if (metadata.isEmpty()) {
|
||||
registrationDataMetadata =
|
||||
createMetadata(context, registration, schema, element, qualifier, value);
|
||||
} else {
|
||||
registrationDataMetadata = metadata.get(0);
|
||||
registrationDataMetadata.setValue(value);
|
||||
}
|
||||
registrationDataMetadataService.update(context, registrationDataMetadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addMetadata(
|
||||
Context context, RegistrationData registration, MetadataField mf, String value
|
||||
) throws SQLException, AuthorizeException {
|
||||
registration.getMetadata().add(
|
||||
registrationDataMetadataService.create(context, registration, mf, value)
|
||||
);
|
||||
this.update(context, registration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addMetadata(
|
||||
Context context, RegistrationData registration, String schema, String element, String qualifier, String value
|
||||
) throws SQLException, AuthorizeException {
|
||||
MetadataField mf = metadataFieldService.findByElement(context, schema, element, qualifier);
|
||||
registration.getMetadata().add(
|
||||
registrationDataMetadataService.create(context, registration, mf, value)
|
||||
);
|
||||
this.update(context, registration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegistrationDataMetadata getMetadataByMetadataString(RegistrationData registrationData, String field) {
|
||||
return registrationData.getMetadata().stream()
|
||||
.filter(m -> field.equals(m.getMetadataField().toString('.')))
|
||||
.findFirst().orElse(null);
|
||||
}
|
||||
|
||||
private boolean areEquals(RegistrationDataMetadata m, String schema, String element, String qualifier) {
|
||||
return m.getMetadataField().getMetadataSchema().getName().equals(schema)
|
||||
&& m.getMetadataField().getElement().equals(element)
|
||||
&& StringUtils.equals(m.getMetadataField().getQualifier(), qualifier);
|
||||
}
|
||||
|
||||
private RegistrationDataMetadata createMetadata(
|
||||
Context context, RegistrationData registration,
|
||||
String schema, String element, String qualifier,
|
||||
String value
|
||||
) {
|
||||
try {
|
||||
return registrationDataMetadataService.create(
|
||||
context, registration, schema, element, qualifier, value
|
||||
);
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private RegistrationDataMetadata createMetadata(Context context, RegistrationData registration, MetadataField mf) {
|
||||
try {
|
||||
return registrationDataMetadataService.create(context, registration, mf);
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegistrationData find(Context context, int id) throws SQLException {
|
||||
return registrationDataDAO.findByID(context, RegistrationData.class, id);
|
||||
@@ -75,8 +248,25 @@ public class RegistrationDataServiceImpl implements RegistrationDataService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markAsExpired(Context context, RegistrationData registrationData) throws SQLException {
|
||||
registrationData.setExpires(Instant.now());
|
||||
registrationDataDAO.save(context, registrationData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(Context context, RegistrationData registrationData) throws SQLException, AuthorizeException {
|
||||
registrationDataDAO.delete(context, registrationData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteExpiredRegistrations(Context context) throws SQLException {
|
||||
registrationDataDAO.deleteExpiredBy(context, Instant.now());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(RegistrationData rd) {
|
||||
return rd.getExpires() == null || rd.getExpires().isAfter(Instant.now());
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 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.eperson;
|
||||
|
||||
/**
|
||||
* External provider allowed to register e-persons stored with {@link RegistrationData}
|
||||
*
|
||||
* @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com)
|
||||
**/
|
||||
public enum RegistrationTypeEnum {
|
||||
|
||||
ORCID("external-login"),
|
||||
VALIDATION_ORCID("review-account"),
|
||||
FORGOT("forgot"),
|
||||
REGISTER("register"),
|
||||
INVITATION("invitation"),
|
||||
CHANGE_PASSWORD("change-password");
|
||||
|
||||
private final String link;
|
||||
|
||||
RegistrationTypeEnum(String link) {
|
||||
this.link = link;
|
||||
}
|
||||
|
||||
public String getLink() {
|
||||
return link;
|
||||
}
|
||||
}
|
@@ -8,10 +8,12 @@
|
||||
package org.dspace.eperson.dao;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.time.Instant;
|
||||
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.core.GenericDAO;
|
||||
import org.dspace.eperson.RegistrationData;
|
||||
import org.dspace.eperson.RegistrationTypeEnum;
|
||||
|
||||
/**
|
||||
* Database Access Object interface class for the RegistrationData object.
|
||||
@@ -23,9 +25,52 @@ import org.dspace.eperson.RegistrationData;
|
||||
*/
|
||||
public interface RegistrationDataDAO extends GenericDAO<RegistrationData> {
|
||||
|
||||
/**
|
||||
* Finds {@link RegistrationData} by email.
|
||||
*
|
||||
* @param context Context for the current request
|
||||
* @param email The email
|
||||
* @return
|
||||
* @throws SQLException
|
||||
*/
|
||||
public RegistrationData findByEmail(Context context, String email) throws SQLException;
|
||||
|
||||
/**
|
||||
* Finds {@link RegistrationData} by email and type.
|
||||
*
|
||||
* @param context Context for the current request
|
||||
* @param email The email
|
||||
* @param type The type of the {@link RegistrationData}
|
||||
* @return
|
||||
* @throws SQLException
|
||||
*/
|
||||
public RegistrationData findBy(Context context, String email, RegistrationTypeEnum type) throws SQLException;
|
||||
|
||||
/**
|
||||
* Finds {@link RegistrationData} by token.
|
||||
*
|
||||
* @param context the context
|
||||
* @param token The token related to the {@link RegistrationData}.
|
||||
* @return
|
||||
* @throws SQLException
|
||||
*/
|
||||
public RegistrationData findByToken(Context context, String token) throws SQLException;
|
||||
|
||||
/**
|
||||
* Deletes {@link RegistrationData} by token.
|
||||
*
|
||||
* @param context Context for the current request
|
||||
* @param token The token to delete registrations for
|
||||
* @throws SQLException
|
||||
*/
|
||||
public void deleteByToken(Context context, String token) throws SQLException;
|
||||
|
||||
/**
|
||||
* Deletes expired {@link RegistrationData}.
|
||||
*
|
||||
* @param context Context for the current request
|
||||
* @param instant The date to delete expired registrations for
|
||||
* @throws SQLException
|
||||
*/
|
||||
void deleteExpiredBy(Context context, Instant instant) throws SQLException;
|
||||
}
|
||||
|
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* 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.eperson.dao;
|
||||
|
||||
import org.dspace.core.GenericDAO;
|
||||
import org.dspace.eperson.RegistrationDataMetadata;
|
||||
|
||||
/**
|
||||
* Database Access Object interface class for the {@link org.dspace.eperson.RegistrationDataMetadata} object.
|
||||
* The implementation of this class is responsible for all database calls for the RegistrationData object and is
|
||||
* autowired by spring
|
||||
*
|
||||
* @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com)
|
||||
**/
|
||||
public interface RegistrationDataMetadataDAO extends GenericDAO<RegistrationDataMetadata> {
|
||||
|
||||
}
|
@@ -8,15 +8,18 @@
|
||||
package org.dspace.eperson.dao.impl;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.time.Instant;
|
||||
|
||||
import jakarta.persistence.Query;
|
||||
import jakarta.persistence.criteria.CriteriaBuilder;
|
||||
import jakarta.persistence.criteria.CriteriaDelete;
|
||||
import jakarta.persistence.criteria.CriteriaQuery;
|
||||
import jakarta.persistence.criteria.Root;
|
||||
import org.dspace.core.AbstractHibernateDAO;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.eperson.RegistrationData;
|
||||
import org.dspace.eperson.RegistrationData_;
|
||||
import org.dspace.eperson.RegistrationTypeEnum;
|
||||
import org.dspace.eperson.dao.RegistrationDataDAO;
|
||||
|
||||
/**
|
||||
@@ -42,6 +45,21 @@ public class RegistrationDataDAOImpl extends AbstractHibernateDAO<RegistrationDa
|
||||
return uniqueResult(context, criteriaQuery, false, RegistrationData.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegistrationData findBy(Context context, String email, RegistrationTypeEnum type) throws SQLException {
|
||||
CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
|
||||
CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, RegistrationData.class);
|
||||
Root<RegistrationData> registrationDataRoot = criteriaQuery.from(RegistrationData.class);
|
||||
criteriaQuery.select(registrationDataRoot);
|
||||
criteriaQuery.where(
|
||||
criteriaBuilder.and(
|
||||
criteriaBuilder.equal(registrationDataRoot.get(RegistrationData_.email), email),
|
||||
criteriaBuilder.equal(registrationDataRoot.get(RegistrationData_.registrationType), type)
|
||||
)
|
||||
);
|
||||
return uniqueResult(context, criteriaQuery, false, RegistrationData.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegistrationData findByToken(Context context, String token) throws SQLException {
|
||||
CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
|
||||
@@ -59,4 +77,15 @@ public class RegistrationDataDAOImpl extends AbstractHibernateDAO<RegistrationDa
|
||||
query.setParameter("token", token);
|
||||
query.executeUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteExpiredBy(Context context, Instant instant) throws SQLException {
|
||||
CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
|
||||
CriteriaDelete<RegistrationData> deleteQuery = criteriaBuilder.createCriteriaDelete(RegistrationData.class);
|
||||
Root<RegistrationData> deleteRoot = deleteQuery.from(RegistrationData.class);
|
||||
deleteQuery.where(
|
||||
criteriaBuilder.lessThanOrEqualTo(deleteRoot.get(RegistrationData_.expires), instant)
|
||||
);
|
||||
getHibernateSession(context).createQuery(deleteQuery).executeUpdate();
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* 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.eperson.dao.impl;
|
||||
|
||||
import org.dspace.core.AbstractHibernateDAO;
|
||||
import org.dspace.eperson.RegistrationDataMetadata;
|
||||
import org.dspace.eperson.dao.RegistrationDataMetadataDAO;
|
||||
|
||||
/**
|
||||
* @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com)
|
||||
**/
|
||||
public class RegistrationDataMetadataDAOImpl extends AbstractHibernateDAO<RegistrationDataMetadata>
|
||||
implements RegistrationDataMetadataDAO {
|
||||
}
|
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 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.eperson.dto;
|
||||
|
||||
import org.dspace.eperson.RegistrationTypeEnum;
|
||||
|
||||
/**
|
||||
* Class that embeds a change done for the {@link org.dspace.eperson.RegistrationData}
|
||||
*
|
||||
* @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com)
|
||||
**/
|
||||
public class RegistrationDataChanges {
|
||||
|
||||
@SuppressWarnings("checkstyle:LineLength")
|
||||
private static final String EMAIL_PATTERN = "^[a-zA-Z0-9.!#$%&'*+\\\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$";
|
||||
|
||||
private final String email;
|
||||
private final RegistrationTypeEnum registrationType;
|
||||
|
||||
public RegistrationDataChanges(String email, RegistrationTypeEnum type) {
|
||||
if (email == null || email.trim().isBlank()) {
|
||||
throw new IllegalArgumentException("Cannot update with an empty email address");
|
||||
}
|
||||
if (type == null) {
|
||||
throw new IllegalArgumentException("Cannot update with a null registration type");
|
||||
}
|
||||
this.email = email;
|
||||
if (!isValidEmail()) {
|
||||
throw new IllegalArgumentException("Invalid email address provided!");
|
||||
}
|
||||
this.registrationType = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the email is valid using the EMAIL_PATTERN.
|
||||
* @return true if valid, false otherwise
|
||||
*/
|
||||
public boolean isValidEmail() {
|
||||
return email.matches(EMAIL_PATTERN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the email of change.
|
||||
*
|
||||
* @return the email of the change
|
||||
*/
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link RegistrationTypeEnum} of the registration.
|
||||
*
|
||||
* @return the type of the change
|
||||
*/
|
||||
public RegistrationTypeEnum getRegistrationType() {
|
||||
return registrationType;
|
||||
}
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* 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.eperson.dto;
|
||||
|
||||
import org.dspace.eperson.RegistrationData;
|
||||
|
||||
/**
|
||||
* This POJO encapsulates the details of the PATCH request that updates the {@link RegistrationData}.
|
||||
*
|
||||
* @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com)
|
||||
**/
|
||||
public class RegistrationDataPatch {
|
||||
|
||||
private final RegistrationData oldRegistration;
|
||||
private final RegistrationDataChanges changes;
|
||||
|
||||
public RegistrationDataPatch(RegistrationData oldRegistration, RegistrationDataChanges changes) {
|
||||
this.oldRegistration = oldRegistration;
|
||||
this.changes = changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the previous registration
|
||||
*
|
||||
* @return RegistrationData
|
||||
*/
|
||||
public RegistrationData getOldRegistration() {
|
||||
return oldRegistration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the changes related to the registration
|
||||
*
|
||||
* @return RegistrationDataChanges
|
||||
*/
|
||||
public RegistrationDataChanges getChanges() {
|
||||
return changes;
|
||||
}
|
||||
}
|
@@ -10,6 +10,7 @@ package org.dspace.eperson.factory;
|
||||
import org.dspace.eperson.service.AccountService;
|
||||
import org.dspace.eperson.service.EPersonService;
|
||||
import org.dspace.eperson.service.GroupService;
|
||||
import org.dspace.eperson.service.RegistrationDataMetadataService;
|
||||
import org.dspace.eperson.service.RegistrationDataService;
|
||||
import org.dspace.eperson.service.SubscribeService;
|
||||
import org.dspace.services.factory.DSpaceServicesFactory;
|
||||
@@ -28,6 +29,8 @@ public abstract class EPersonServiceFactory {
|
||||
|
||||
public abstract RegistrationDataService getRegistrationDataService();
|
||||
|
||||
public abstract RegistrationDataMetadataService getRegistrationDAtaDataMetadataService();
|
||||
|
||||
public abstract AccountService getAccountService();
|
||||
|
||||
public abstract SubscribeService getSubscribeService();
|
||||
|
@@ -10,6 +10,7 @@ package org.dspace.eperson.factory;
|
||||
import org.dspace.eperson.service.AccountService;
|
||||
import org.dspace.eperson.service.EPersonService;
|
||||
import org.dspace.eperson.service.GroupService;
|
||||
import org.dspace.eperson.service.RegistrationDataMetadataService;
|
||||
import org.dspace.eperson.service.RegistrationDataService;
|
||||
import org.dspace.eperson.service.SubscribeService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -29,6 +30,8 @@ public class EPersonServiceFactoryImpl extends EPersonServiceFactory {
|
||||
@Autowired(required = true)
|
||||
private RegistrationDataService registrationDataService;
|
||||
@Autowired(required = true)
|
||||
private RegistrationDataMetadataService registrationDataMetadataService;
|
||||
@Autowired(required = true)
|
||||
private AccountService accountService;
|
||||
@Autowired(required = true)
|
||||
private SubscribeService subscribeService;
|
||||
@@ -58,4 +61,8 @@ public class EPersonServiceFactoryImpl extends EPersonServiceFactory {
|
||||
return subscribeService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegistrationDataMetadataService getRegistrationDAtaDataMetadataService() {
|
||||
return registrationDataMetadataService;
|
||||
}
|
||||
}
|
||||
|
@@ -9,11 +9,15 @@ package org.dspace.eperson.service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import jakarta.mail.MessagingException;
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.eperson.EPerson;
|
||||
import org.dspace.eperson.RegistrationData;
|
||||
import org.dspace.eperson.dto.RegistrationDataPatch;
|
||||
|
||||
/**
|
||||
* Methods for handling registration by email and forgotten passwords. When
|
||||
@@ -30,20 +34,79 @@ import org.dspace.eperson.EPerson;
|
||||
* @version $Revision$
|
||||
*/
|
||||
public interface AccountService {
|
||||
|
||||
public void sendRegistrationInfo(Context context, String email)
|
||||
throws SQLException, IOException, MessagingException, AuthorizeException;
|
||||
|
||||
public void sendForgotPasswordInfo(Context context, String email)
|
||||
throws SQLException, IOException, MessagingException, AuthorizeException;
|
||||
|
||||
/**
|
||||
* Checks if exists an account related to the token provided
|
||||
*
|
||||
* @param context DSpace context
|
||||
* @param token Account token
|
||||
* @return true if exists, false otherwise
|
||||
* @throws SQLException
|
||||
* @throws AuthorizeException
|
||||
*/
|
||||
boolean existsAccountFor(Context context, String token)
|
||||
throws SQLException, AuthorizeException;
|
||||
|
||||
/**
|
||||
* Checks if exists an account related to the email provided
|
||||
*
|
||||
* @param context DSpace context
|
||||
* @param email String email to search for
|
||||
* @return true if exists, false otherwise
|
||||
* @throws SQLException
|
||||
*/
|
||||
boolean existsAccountWithEmail(Context context, String email)
|
||||
throws SQLException;
|
||||
|
||||
public EPerson getEPerson(Context context, String token)
|
||||
throws SQLException, AuthorizeException;
|
||||
|
||||
public String getEmail(Context context, String token) throws SQLException;
|
||||
|
||||
public String getEmail(Context context, String token)
|
||||
throws SQLException;
|
||||
public void deleteToken(Context context, String token) throws SQLException;
|
||||
|
||||
public void deleteToken(Context context, String token)
|
||||
throws SQLException;
|
||||
/**
|
||||
* Merge registration data with an existing EPerson or create a new one.
|
||||
*
|
||||
* @param context DSpace context
|
||||
* @param userId The ID of the EPerson to merge with or create
|
||||
* @param token The token to use for registration data
|
||||
* @param overrides List of fields to override in the EPerson
|
||||
* @return The merged or created EPerson
|
||||
* @throws AuthorizeException If the user is not authorized to perform the action
|
||||
* @throws SQLException If a database error occurs
|
||||
*/
|
||||
EPerson mergeRegistration(
|
||||
Context context,
|
||||
UUID userId,
|
||||
String token,
|
||||
List<String> overrides
|
||||
) throws AuthorizeException, SQLException;
|
||||
|
||||
/**
|
||||
* This method creates a fresh new {@link RegistrationData} based on the {@link RegistrationDataPatch} requested
|
||||
* by a given user.
|
||||
*
|
||||
* @param context - The DSapce Context
|
||||
* @param registrationDataPatch - Details of the patch request coming from the Controller
|
||||
* @return a newly created {@link RegistrationData}
|
||||
* @throws AuthorizeException
|
||||
*/
|
||||
RegistrationData renewRegistrationForEmail(
|
||||
Context context,
|
||||
RegistrationDataPatch registrationDataPatch
|
||||
) throws AuthorizeException;
|
||||
|
||||
/**
|
||||
* Checks if the {@link RegistrationData#token} is valid.
|
||||
*
|
||||
* @param registrationData that will be checked
|
||||
* @return true if valid, false otherwise
|
||||
*/
|
||||
boolean isTokenValidForCreation(RegistrationData registrationData);
|
||||
}
|
||||
|
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* 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.eperson.service;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
import org.dspace.content.MetadataField;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.eperson.RegistrationData;
|
||||
import org.dspace.eperson.RegistrationDataMetadata;
|
||||
import org.dspace.service.DSpaceCRUDService;
|
||||
|
||||
/**
|
||||
* This class contains business-logic to handle {@link RegistrationDataMetadata}.
|
||||
*
|
||||
* @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com)
|
||||
**/
|
||||
public interface RegistrationDataMetadataService extends DSpaceCRUDService<RegistrationDataMetadata> {
|
||||
|
||||
/**
|
||||
* Creates a new {@link RegistrationDataMetadata} that will be stored starting from the parameters of the method.
|
||||
*
|
||||
* @param context - the DSpace Context
|
||||
* @param registrationData - the Registration data that will contain the metadata
|
||||
* @param schema - the schema of the metadata field
|
||||
* @param element - the element of the metadata field
|
||||
* @param qualifier - the qualifier of the metadata field
|
||||
* @param value - the value of that metadata
|
||||
* @return the newly created RegistrationDataMetadata
|
||||
* @throws SQLException
|
||||
*/
|
||||
RegistrationDataMetadata create(Context context, RegistrationData registrationData, String schema,
|
||||
String element, String qualifier, String value) throws SQLException;
|
||||
|
||||
/**
|
||||
* Creates a new {@link RegistrationDataMetadata}
|
||||
*
|
||||
* @param context - the DSpace Context
|
||||
* @param registrationData - the RegistrationData that will contain that metadata
|
||||
* @param metadataField - the metadataField
|
||||
* @return the newly created RegistrationDataMetadata
|
||||
* @throws SQLException
|
||||
*/
|
||||
RegistrationDataMetadata create(
|
||||
Context context, RegistrationData registrationData, MetadataField metadataField
|
||||
) throws SQLException;
|
||||
|
||||
/**
|
||||
* Creates a new {@link RegistrationDataMetadata}
|
||||
*
|
||||
* @param context - the DSpace Context
|
||||
* @param registrationData - the RegistrationData that will contain that metadata
|
||||
* @param metadataField - the metadataField that will be stored
|
||||
* @param value - the value that will be placed inside the RegistrationDataMetadata
|
||||
* @return the newly created {@link RegistrationDataMetadata}
|
||||
* @throws SQLException
|
||||
*/
|
||||
RegistrationDataMetadata create(
|
||||
Context context, RegistrationData registrationData, MetadataField metadataField, String value
|
||||
) throws SQLException;
|
||||
}
|
@@ -8,13 +8,23 @@
|
||||
package org.dspace.eperson.service;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
import org.dspace.content.MetadataField;
|
||||
import org.dspace.content.MetadataValue;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.eperson.EPerson;
|
||||
import org.dspace.eperson.RegistrationData;
|
||||
import org.dspace.eperson.RegistrationDataMetadata;
|
||||
import org.dspace.eperson.RegistrationTypeEnum;
|
||||
import org.dspace.eperson.dto.RegistrationDataPatch;
|
||||
import org.dspace.service.DSpaceCRUDService;
|
||||
|
||||
/**
|
||||
* Service interface class for the RegistrationData object.
|
||||
* Service interface class for the {@link RegistrationData} object.
|
||||
* The implementation of this class is responsible for all business logic calls for the RegistrationData object and
|
||||
* is autowired by spring
|
||||
*
|
||||
@@ -22,10 +32,45 @@ import org.dspace.service.DSpaceCRUDService;
|
||||
*/
|
||||
public interface RegistrationDataService extends DSpaceCRUDService<RegistrationData> {
|
||||
|
||||
RegistrationData create(Context context) throws SQLException, AuthorizeException;
|
||||
|
||||
RegistrationData create(Context context, String netId) throws SQLException, AuthorizeException;
|
||||
|
||||
RegistrationData create(Context context, String netId, RegistrationTypeEnum type)
|
||||
throws SQLException, AuthorizeException;
|
||||
|
||||
RegistrationData clone(
|
||||
Context context, RegistrationDataPatch registrationDataPatch
|
||||
) throws SQLException, AuthorizeException;
|
||||
|
||||
public RegistrationData findByToken(Context context, String token) throws SQLException;
|
||||
|
||||
public RegistrationData findByEmail(Context context, String email) throws SQLException;
|
||||
|
||||
RegistrationData findBy(Context context, String email, RegistrationTypeEnum type) throws SQLException;
|
||||
|
||||
public void deleteByToken(Context context, String token) throws SQLException;
|
||||
|
||||
Stream<Map.Entry<RegistrationDataMetadata, Optional<MetadataValue>>> groupEpersonMetadataByRegistrationData(
|
||||
EPerson ePerson, RegistrationData registrationData
|
||||
) throws SQLException;
|
||||
|
||||
void setRegistrationMetadataValue(
|
||||
Context context, RegistrationData registration, String schema, String element, String qualifier, String value
|
||||
) throws SQLException, AuthorizeException;
|
||||
|
||||
void addMetadata(
|
||||
Context context, RegistrationData registration, String schema, String element, String qualifier, String value
|
||||
) throws SQLException, AuthorizeException;
|
||||
|
||||
RegistrationDataMetadata getMetadataByMetadataString(RegistrationData registrationData, String field);
|
||||
|
||||
void addMetadata(Context context, RegistrationData rd, MetadataField metadataField, String value)
|
||||
throws SQLException, AuthorizeException;
|
||||
|
||||
void markAsExpired(Context context, RegistrationData registrationData) throws SQLException, AuthorizeException;
|
||||
|
||||
void deleteExpiredRegistrations(Context context) throws SQLException;
|
||||
|
||||
boolean isValid(RegistrationData rd);
|
||||
}
|
||||
|
@@ -36,10 +36,12 @@ import org.dspace.discovery.SearchServiceException;
|
||||
import org.dspace.discovery.indexobject.IndexableItem;
|
||||
import org.dspace.eperson.EPerson;
|
||||
import org.dspace.eperson.service.EPersonService;
|
||||
import org.dspace.orcid.OrcidQueue;
|
||||
import org.dspace.orcid.OrcidToken;
|
||||
import org.dspace.orcid.client.OrcidClient;
|
||||
import org.dspace.orcid.model.OrcidEntityType;
|
||||
import org.dspace.orcid.model.OrcidTokenResponseDTO;
|
||||
import org.dspace.orcid.service.OrcidQueueService;
|
||||
import org.dspace.orcid.service.OrcidSynchronizationService;
|
||||
import org.dspace.orcid.service.OrcidTokenService;
|
||||
import org.dspace.profile.OrcidEntitySyncPreference;
|
||||
@@ -61,9 +63,13 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
public class OrcidSynchronizationServiceImpl implements OrcidSynchronizationService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(OrcidSynchronizationServiceImpl.class);
|
||||
|
||||
@Autowired
|
||||
private ItemService itemService;
|
||||
|
||||
@Autowired
|
||||
private OrcidQueueService orcidQueueService;
|
||||
|
||||
@Autowired
|
||||
private ConfigurationService configurationService;
|
||||
|
||||
@@ -120,7 +126,6 @@ public class OrcidSynchronizationServiceImpl implements OrcidSynchronizationServ
|
||||
|
||||
@Override
|
||||
public void unlinkProfile(Context context, Item profile) throws SQLException {
|
||||
|
||||
clearOrcidProfileMetadata(context, profile);
|
||||
|
||||
clearSynchronizationSettings(context, profile);
|
||||
@@ -129,6 +134,11 @@ public class OrcidSynchronizationServiceImpl implements OrcidSynchronizationServ
|
||||
|
||||
updateItem(context, profile);
|
||||
|
||||
List<OrcidQueue> queueRecords = orcidQueueService.findByProfileItemId(context, profile.getID());
|
||||
for (OrcidQueue queueRecord : queueRecords) {
|
||||
orcidQueueService.delete(context, queueRecord);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void clearOrcidToken(Context context, Item profile) {
|
||||
|
@@ -0,0 +1,46 @@
|
||||
--
|
||||
-- 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/
|
||||
--
|
||||
|
||||
-----------------------------------------------------------------------------------
|
||||
-- ALTER table registrationdata
|
||||
-----------------------------------------------------------------------------------
|
||||
|
||||
EXECUTE IMMEDIATE 'ALTER TABLE registrationdata DROP CONSTRAINT ' ||
|
||||
QUOTE_IDENT((SELECT CONSTRAINT_NAME
|
||||
FROM information_schema.key_column_usage
|
||||
WHERE TABLE_SCHEMA = 'PUBLIC' AND TABLE_NAME = 'REGISTRATIONDATA' AND COLUMN_NAME = 'EMAIL'));
|
||||
|
||||
ALTER TABLE registrationdata
|
||||
ADD COLUMN registration_type VARCHAR2(255);
|
||||
|
||||
ALTER TABLE registrationdata
|
||||
ADD COLUMN net_id VARCHAR2(64);
|
||||
|
||||
CREATE SEQUENCE IF NOT EXISTS registrationdata_metadatavalue_seq START WITH 1 INCREMENT BY 1;
|
||||
|
||||
-----------------------------------------------------------------------------------
|
||||
-- Creates table registrationdata_metadata
|
||||
-----------------------------------------------------------------------------------
|
||||
|
||||
CREATE TABLE registrationdata_metadata (
|
||||
registrationdata_metadata_id INTEGER NOT NULL,
|
||||
registrationdata_id INTEGER,
|
||||
metadata_field_id INTEGER,
|
||||
text_value CLOB,
|
||||
CONSTRAINT pk_registrationdata_metadata PRIMARY KEY (registrationdata_metadata_id)
|
||||
);
|
||||
|
||||
ALTER TABLE registrationdata_metadata
|
||||
ADD CONSTRAINT FK_REGISTRATIONDATA_METADATA_ON_METADATA_FIELD
|
||||
FOREIGN KEY (metadata_field_id)
|
||||
REFERENCES metadatafieldregistry (metadata_field_id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE registrationdata_metadata
|
||||
ADD CONSTRAINT FK_REGISTRATIONDATA_METADATA_ON_REGISTRATIONDATA
|
||||
FOREIGN KEY (registrationdata_id)
|
||||
REFERENCES registrationdata (registrationdata_id) ON DELETE CASCADE;
|
@@ -0,0 +1,52 @@
|
||||
--
|
||||
-- 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/
|
||||
--
|
||||
|
||||
-----------------------------------------------------------------------------------
|
||||
-- ALTER table registrationdata
|
||||
-----------------------------------------------------------------------------------
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
EXECUTE 'ALTER TABLE registrationdata DROP CONSTRAINT IF EXISTS ' ||
|
||||
QUOTE_IDENT((
|
||||
SELECT CONSTRAINT_NAME
|
||||
FROM information_schema.key_column_usage
|
||||
WHERE TABLE_NAME = 'registrationdata' AND COLUMN_NAME = 'email'
|
||||
));
|
||||
end
|
||||
$$;
|
||||
|
||||
ALTER TABLE registrationdata
|
||||
ADD COLUMN registration_type VARCHAR(255);
|
||||
|
||||
ALTER TABLE registrationdata
|
||||
ADD COLUMN net_id VARCHAR(64);
|
||||
|
||||
CREATE SEQUENCE IF NOT EXISTS registrationdata_metadatavalue_seq START WITH 1 INCREMENT BY 1;
|
||||
|
||||
-----------------------------------------------------------------------------------
|
||||
-- Creates table registrationdata_metadata
|
||||
-----------------------------------------------------------------------------------
|
||||
|
||||
CREATE TABLE registrationdata_metadata (
|
||||
registrationdata_metadata_id INTEGER NOT NULL,
|
||||
registrationdata_id INTEGER,
|
||||
metadata_field_id INTEGER,
|
||||
text_value TEXT,
|
||||
CONSTRAINT pk_registrationdata_metadata PRIMARY KEY (registrationdata_metadata_id)
|
||||
);
|
||||
|
||||
ALTER TABLE registrationdata_metadata
|
||||
ADD CONSTRAINT FK_REGISTRATIONDATA_METADATA_ON_METADATA_FIELD
|
||||
FOREIGN KEY (metadata_field_id)
|
||||
REFERENCES metadatafieldregistry (metadata_field_id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE registrationdata_metadata
|
||||
ADD CONSTRAINT FK_REGISTRATIONDATA_METADATA_ON_REGISTRATIONDATA
|
||||
FOREIGN KEY (registrationdata_id)
|
||||
REFERENCES registrationdata (registrationdata_id) ON DELETE CASCADE;
|
@@ -0,0 +1,263 @@
|
||||
/**
|
||||
* 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.eperson;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||
import static org.hamcrest.CoreMatchers.nullValue;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
|
||||
import org.dspace.AbstractIntegrationTestWithDatabase;
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
import org.dspace.builder.EPersonBuilder;
|
||||
import org.dspace.builder.MetadataFieldBuilder;
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.content.MetadataField;
|
||||
import org.dspace.eperson.factory.EPersonServiceFactory;
|
||||
import org.dspace.eperson.service.AccountService;
|
||||
import org.dspace.eperson.service.EPersonService;
|
||||
import org.dspace.eperson.service.RegistrationDataService;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class AccountServiceImplIT extends AbstractIntegrationTestWithDatabase {
|
||||
|
||||
public static final String ORCID_NETID = "vins01";
|
||||
public static final String ORCID_EMAIL = "vins-01@fake.mail";
|
||||
public static final String CUSTOM_METADATA_VALUE = "vins01-customID";
|
||||
|
||||
AccountService accountService =
|
||||
EPersonServiceFactory.getInstance().getAccountService();
|
||||
|
||||
EPersonService ePersonService =
|
||||
EPersonServiceFactory.getInstance().getEPersonService();
|
||||
|
||||
RegistrationDataService registrationDataService =
|
||||
EPersonServiceFactory.getInstance().getRegistrationDataService();
|
||||
;
|
||||
|
||||
EPerson tokenPerson;
|
||||
RegistrationData orcidToken;
|
||||
MetadataField metadataField;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
context.turnOffAuthorisationSystem();
|
||||
|
||||
tokenPerson =
|
||||
EPersonBuilder.createEPerson(context)
|
||||
.withNameInMetadata("Vincenzo", "Mecca")
|
||||
.withEmail(null)
|
||||
.withNetId(null)
|
||||
.withCanLogin(true)
|
||||
.build();
|
||||
|
||||
metadataField =
|
||||
MetadataFieldBuilder.createMetadataField(context, "identifier", "custom", null)
|
||||
.build();
|
||||
|
||||
orcidToken =
|
||||
registrationDataService.create(context, ORCID_NETID, RegistrationTypeEnum.ORCID);
|
||||
orcidToken.setEmail(ORCID_EMAIL);
|
||||
|
||||
registrationDataService.addMetadata(context, orcidToken, metadataField, CUSTOM_METADATA_VALUE);
|
||||
registrationDataService.update(context, orcidToken);
|
||||
|
||||
context.restoreAuthSystemState();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testMergedORCIDRegistration() throws SQLException, AuthorizeException {
|
||||
|
||||
// set current logged-in eperson
|
||||
context.setCurrentUser(tokenPerson);
|
||||
|
||||
// try to update account details with the ORCID token
|
||||
EPerson updatedEperson =
|
||||
accountService.mergeRegistration(
|
||||
context, tokenPerson.getID(), orcidToken.getToken(),
|
||||
List.of()
|
||||
);
|
||||
|
||||
// updates value with the one inside the ORCID token
|
||||
assertThat(updatedEperson, notNullValue());
|
||||
assertThat(updatedEperson.getEmail(), is(ORCID_EMAIL));
|
||||
assertThat(updatedEperson.getNetid(), is(ORCID_NETID));
|
||||
|
||||
String customMetadataFound =
|
||||
ePersonService.getMetadataFirstValue(
|
||||
updatedEperson, metadataField.getMetadataSchema().getName(), metadataField.getElement(),
|
||||
metadataField.getQualifier(), Item.ANY
|
||||
);
|
||||
|
||||
// updates the metadata with the one set in the ORCID token
|
||||
assertThat(customMetadataFound, is(CUSTOM_METADATA_VALUE));
|
||||
// deletes the token
|
||||
assertThat(registrationDataService.findByToken(context, orcidToken.getToken()), nullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMergedORCIDRegistrationWithOverwrittenMetadata() throws SQLException, AuthorizeException {
|
||||
|
||||
// set current logged-in eperson
|
||||
context.setCurrentUser(tokenPerson);
|
||||
|
||||
registrationDataService.addMetadata(
|
||||
context, orcidToken, "eperson", "firstname", null, "Vins"
|
||||
);
|
||||
registrationDataService.addMetadata(
|
||||
context, orcidToken, "eperson", "lastname", null, "4Science"
|
||||
);
|
||||
registrationDataService.update(context, orcidToken);
|
||||
|
||||
// try to update account details with the ORCID token
|
||||
EPerson updatedEperson =
|
||||
accountService.mergeRegistration(context, tokenPerson.getID(), orcidToken.getToken(),
|
||||
List.of("eperson.firstname", "eperson.lastname"));
|
||||
|
||||
// updates value with the one inside the ORCID token
|
||||
assertThat(updatedEperson, notNullValue());
|
||||
assertThat(updatedEperson.getEmail(), is(ORCID_EMAIL));
|
||||
assertThat(updatedEperson.getNetid(), is(ORCID_NETID));
|
||||
// overwrites values with the one from the token
|
||||
assertThat(updatedEperson.getFirstName(), is("Vins"));
|
||||
assertThat(updatedEperson.getLastName(), is("4Science"));
|
||||
|
||||
String customMetadataFound =
|
||||
ePersonService.getMetadataFirstValue(
|
||||
updatedEperson, metadataField.getMetadataSchema().getName(), metadataField.getElement(),
|
||||
metadataField.getQualifier(), Item.ANY
|
||||
);
|
||||
|
||||
// updates the metadata with the one set in the ORCID token
|
||||
assertThat(customMetadataFound, is(CUSTOM_METADATA_VALUE));
|
||||
// deletes the token
|
||||
assertThat(registrationDataService.findByToken(context, orcidToken.getToken()), nullValue());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCannotMergedORCIDRegistrationWithDifferentLoggedEperson() {
|
||||
|
||||
// set current logged-in admin
|
||||
context.setCurrentUser(admin);
|
||||
|
||||
// try to update eperson details with the ORCID token while logged in as admin
|
||||
assertThrows(
|
||||
AuthorizeException.class,
|
||||
() -> accountService.mergeRegistration(context, tokenPerson.getID(), orcidToken.getToken(), List.of())
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateUserWithRegistration() throws SQLException, AuthorizeException, IOException {
|
||||
|
||||
// set current logged-in eperson
|
||||
context.setCurrentUser(null);
|
||||
|
||||
context.turnOffAuthorisationSystem();
|
||||
// create an orcid validation token
|
||||
RegistrationData orcidRegistration =
|
||||
registrationDataService.create(context, ORCID_NETID, RegistrationTypeEnum.VALIDATION_ORCID);
|
||||
registrationDataService.addMetadata(
|
||||
context, orcidRegistration, "eperson", "firstname", null, "Vincenzo"
|
||||
);
|
||||
registrationDataService.addMetadata(
|
||||
context, orcidRegistration, "eperson", "lastname", null, "Mecca"
|
||||
);
|
||||
orcidRegistration.setEmail(ORCID_EMAIL);
|
||||
registrationDataService.update(context, orcidRegistration);
|
||||
|
||||
context.commit();
|
||||
context.restoreAuthSystemState();
|
||||
|
||||
EPerson createdEPerson = null;
|
||||
try {
|
||||
|
||||
// try to create a new account during orcid registration
|
||||
createdEPerson =
|
||||
accountService.mergeRegistration(context, null, orcidRegistration.getToken(), List.of());
|
||||
|
||||
// updates value with the one inside the validation token
|
||||
assertThat(createdEPerson, notNullValue());
|
||||
assertThat(createdEPerson.getFirstName(), is("Vincenzo"));
|
||||
assertThat(createdEPerson.getLastName(), is("Mecca"));
|
||||
assertThat(createdEPerson.getEmail(), is(ORCID_EMAIL));
|
||||
assertThat(createdEPerson.getNetid(), is(ORCID_NETID));
|
||||
|
||||
// deletes the token
|
||||
assertThat(registrationDataService.findByToken(context, orcidRegistration.getToken()), nullValue());
|
||||
} finally {
|
||||
context.turnOffAuthorisationSystem();
|
||||
ePersonService.delete(context, context.reloadEntity(createdEPerson));
|
||||
RegistrationData found = context.reloadEntity(orcidRegistration);
|
||||
if (found != null) {
|
||||
registrationDataService.delete(context, found);
|
||||
}
|
||||
context.restoreAuthSystemState();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testInvalidMergeWithoutValidToken() throws SQLException, AuthorizeException {
|
||||
|
||||
// create a register token
|
||||
RegistrationData anyToken =
|
||||
registrationDataService.create(context, ORCID_NETID, RegistrationTypeEnum.REGISTER);
|
||||
|
||||
try {
|
||||
|
||||
assertThrows(
|
||||
AuthorizeException.class,
|
||||
() -> accountService.mergeRegistration(context, null, anyToken.getToken(), List.of())
|
||||
);
|
||||
|
||||
// sets as forgot token
|
||||
anyToken.setRegistrationType(RegistrationTypeEnum.FORGOT);
|
||||
registrationDataService.update(context, anyToken);
|
||||
|
||||
assertThrows(
|
||||
AuthorizeException.class,
|
||||
() -> accountService.mergeRegistration(context, null, anyToken.getToken(), List.of())
|
||||
);
|
||||
|
||||
// sets as change_password token
|
||||
anyToken.setRegistrationType(RegistrationTypeEnum.CHANGE_PASSWORD);
|
||||
registrationDataService.update(context, anyToken);
|
||||
|
||||
assertThrows(
|
||||
AuthorizeException.class,
|
||||
() -> accountService.mergeRegistration(context, null, anyToken.getToken(), List.of())
|
||||
);
|
||||
|
||||
// sets as invitation token
|
||||
anyToken.setRegistrationType(RegistrationTypeEnum.INVITATION);
|
||||
registrationDataService.update(context, anyToken);
|
||||
|
||||
assertThrows(
|
||||
AuthorizeException.class,
|
||||
() -> accountService.mergeRegistration(context, null, anyToken.getToken(), List.of())
|
||||
);
|
||||
|
||||
} finally {
|
||||
registrationDataService.delete(context, context.reloadEntity(anyToken));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,167 @@
|
||||
/**
|
||||
* 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.eperson;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||
import static org.hamcrest.CoreMatchers.nullValue;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
import org.dspace.AbstractIntegrationTestWithDatabase;
|
||||
import org.dspace.builder.MetadataFieldBuilder;
|
||||
import org.dspace.content.MetadataField;
|
||||
import org.dspace.eperson.factory.EPersonServiceFactory;
|
||||
import org.dspace.eperson.service.RegistrationDataMetadataService;
|
||||
import org.dspace.eperson.service.RegistrationDataService;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com)
|
||||
*/
|
||||
public class RegistrationDataMetadataServiceImplIT extends AbstractIntegrationTestWithDatabase {
|
||||
|
||||
RegistrationDataMetadataService registrationDataMetadataService =
|
||||
EPersonServiceFactory.getInstance().getRegistrationDAtaDataMetadataService();
|
||||
|
||||
RegistrationDataService registrationDataService =
|
||||
EPersonServiceFactory.getInstance().getRegistrationDataService();
|
||||
|
||||
MetadataField metadataField;
|
||||
RegistrationData registrationData;
|
||||
RegistrationDataMetadata metadata;
|
||||
|
||||
@Before
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
context.turnOffAuthorisationSystem();
|
||||
this.registrationData =
|
||||
this.registrationDataService.create(context);
|
||||
|
||||
this.metadataField =
|
||||
MetadataFieldBuilder.createMetadataField(context, "dc", "identifier", "custom")
|
||||
.build();
|
||||
|
||||
context.restoreAuthSystemState();
|
||||
}
|
||||
|
||||
@After
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
this.registrationDataService.delete(context, registrationData);
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testEmptyMetadataCreation() throws Exception {
|
||||
try {
|
||||
metadata = registrationDataMetadataService.create(context, registrationData, metadataField);
|
||||
|
||||
assertThat(metadata, notNullValue());
|
||||
assertThat(metadata.getValue(), nullValue());
|
||||
assertThat(metadata.getRegistrationData().getID(), is(registrationData.getID()));
|
||||
assertThat(metadata.getMetadataField(), is(metadataField));
|
||||
|
||||
} finally {
|
||||
registrationDataMetadataService.delete(context, metadata);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidMetadataCreation() throws Exception {
|
||||
try {
|
||||
metadata =
|
||||
registrationDataMetadataService.create(context, registrationData, metadataField, "my-identifier");
|
||||
|
||||
assertThat(metadata, notNullValue());
|
||||
assertThat(metadata.getValue(), is("my-identifier"));
|
||||
assertThat(metadata.getRegistrationData().getID(), is(registrationData.getID()));
|
||||
assertThat(metadata.getMetadataField(), is(metadataField));
|
||||
|
||||
} finally {
|
||||
registrationDataMetadataService.delete(context, metadata);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExistingMetadataFieldMetadataCreation() throws Exception {
|
||||
try {
|
||||
metadata =
|
||||
registrationDataMetadataService.create(
|
||||
context, registrationData, "dc", "identifier", "other", "my-identifier"
|
||||
);
|
||||
|
||||
assertThat(metadata, notNullValue());
|
||||
assertThat(metadata.getValue(), is("my-identifier"));
|
||||
assertThat(metadata.getRegistrationData().getID(), is(registrationData.getID()));
|
||||
|
||||
} finally {
|
||||
registrationDataMetadataService.delete(context, metadata);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testFindMetadata() throws Exception {
|
||||
try {
|
||||
metadata = registrationDataMetadataService.create(context, registrationData, metadataField);
|
||||
|
||||
RegistrationDataMetadata found =
|
||||
registrationDataMetadataService.find(context, metadata.getID());
|
||||
|
||||
assertThat(found.getID(), is(metadata.getID()));
|
||||
|
||||
} finally {
|
||||
registrationDataMetadataService.delete(context, metadata);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateMetadata() throws Exception {
|
||||
try {
|
||||
metadata = registrationDataMetadataService.create(context, registrationData, metadataField);
|
||||
metadata.setValue("custom-value");
|
||||
registrationDataMetadataService.update(context, metadata);
|
||||
|
||||
RegistrationDataMetadata found =
|
||||
registrationDataMetadataService.find(context, metadata.getID());
|
||||
|
||||
assertThat(found.getID(), is(metadata.getID()));
|
||||
assertThat(found.getValue(), is("custom-value"));
|
||||
|
||||
} finally {
|
||||
registrationDataMetadataService.delete(context, metadata);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteMetadata() throws Exception {
|
||||
try {
|
||||
metadata = registrationDataMetadataService.create(context, registrationData, metadataField);
|
||||
|
||||
RegistrationDataMetadata found =
|
||||
registrationDataMetadataService.find(context, metadata.getID());
|
||||
|
||||
assertThat(found, notNullValue());
|
||||
|
||||
registrationDataMetadataService.delete(context, metadata);
|
||||
|
||||
found = registrationDataMetadataService.find(context, metadata.getID());
|
||||
|
||||
assertThat(found, nullValue());
|
||||
|
||||
} finally {
|
||||
registrationDataMetadataService.delete(context, metadata);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -36,21 +36,28 @@ public class VersionedHandleIdentifierProviderIT extends AbstractIdentifierProvi
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
context.turnOffAuthorisationSystem();
|
||||
|
||||
parentCommunity = CommunityBuilder.createCommunity(context)
|
||||
.withName("Parent Community")
|
||||
.build();
|
||||
collection = CollectionBuilder.createCollection(context, parentCommunity)
|
||||
.withName("Collection")
|
||||
.build();
|
||||
|
||||
context.restoreAuthSystemState();
|
||||
}
|
||||
|
||||
private void createVersions() throws SQLException, AuthorizeException {
|
||||
context.turnOffAuthorisationSystem();
|
||||
|
||||
itemV1 = ItemBuilder.createItem(context, collection)
|
||||
.withTitle("First version")
|
||||
.build();
|
||||
firstHandle = itemV1.getHandle();
|
||||
itemV2 = VersionBuilder.createVersion(context, itemV1, "Second version").build().getItem();
|
||||
itemV3 = VersionBuilder.createVersion(context, itemV1, "Third version").build().getItem();
|
||||
|
||||
context.restoreAuthSystemState();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* 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.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import org.dspace.app.rest.converter.ConverterService;
|
||||
import org.dspace.app.rest.model.EPersonRest;
|
||||
import org.dspace.app.rest.model.hateoas.EPersonResource;
|
||||
import org.dspace.app.rest.repository.EPersonRestRepository;
|
||||
import org.dspace.app.rest.utils.ContextUtil;
|
||||
import org.dspace.core.Context;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.rest.webmvc.ControllerUtils;
|
||||
import org.springframework.hateoas.RepresentationModel;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
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;
|
||||
|
||||
/**
|
||||
* This controller is responsible to handle {@link org.dspace.eperson.RegistrationData}
|
||||
* of a given {@link org.dspace.eperson.EPerson}
|
||||
*
|
||||
* @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com)
|
||||
**/
|
||||
@RestController
|
||||
@RequestMapping("/api/" + EPersonRest.CATEGORY + "/" + EPersonRest.PLURAL_NAME)
|
||||
public class EPersonRegistrationRestController {
|
||||
|
||||
@Autowired
|
||||
private EPersonRestRepository ePersonRestRepository;
|
||||
|
||||
@Autowired
|
||||
private ConverterService converter;
|
||||
|
||||
/**
|
||||
* This method will merge the data coming from a {@link org.dspace.eperson.RegistrationData} into the current
|
||||
* logged-in user.
|
||||
* <br/>
|
||||
* The request must have an empty body, and a token parameter should be provided:
|
||||
* <pre>
|
||||
* <code>
|
||||
* curl -X POST http://${dspace.url}/api/eperson/epersons/${id-eperson}?token=${token}&override=${metadata-fields}
|
||||
* -H "Content-Type: application/json"
|
||||
* -H "Authorization: Bearer ${bearer-token}"
|
||||
* </code>
|
||||
* </pre>
|
||||
* @param request httpServletRequest incoming
|
||||
* @param uuid uuid of the eperson
|
||||
* @param token registration token
|
||||
* @param override fields to override inside from the registration data to the eperson
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
@RequestMapping(method = RequestMethod.POST, value = "/{uuid}")
|
||||
public ResponseEntity<RepresentationModel<?>> post(
|
||||
HttpServletRequest request,
|
||||
@PathVariable String uuid,
|
||||
@RequestParam @NotNull String token,
|
||||
@RequestParam(required = false) List<String> override
|
||||
) throws Exception {
|
||||
Context context = ContextUtil.obtainContext(request);
|
||||
try {
|
||||
context.turnOffAuthorisationSystem();
|
||||
EPersonRest epersonRest =
|
||||
ePersonRestRepository.mergeFromRegistrationData(context, UUID.fromString(uuid), token, override);
|
||||
EPersonResource resource = converter.toResource(epersonRest);
|
||||
return ControllerUtils.toResponseEntity(HttpStatus.CREATED, new HttpHeaders(), resource);
|
||||
} catch (Exception e) {
|
||||
throw e;
|
||||
} finally {
|
||||
context.restoreAuthSystemState();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -35,7 +35,7 @@ import org.springframework.stereotype.Component;
|
||||
* Converter to translate between lists of domain {@link MetadataValue}s and {@link MetadataRest} representations.
|
||||
*/
|
||||
@Component
|
||||
public class MetadataConverter implements DSpaceConverter<MetadataValueList, MetadataRest> {
|
||||
public class MetadataConverter implements DSpaceConverter<MetadataValueList, MetadataRest<MetadataValueRest>> {
|
||||
|
||||
@Autowired
|
||||
private ContentServiceFactory contentServiceFactory;
|
||||
@@ -46,7 +46,7 @@ public class MetadataConverter implements DSpaceConverter<MetadataValueList, Met
|
||||
private ConverterService converter;
|
||||
|
||||
@Override
|
||||
public MetadataRest convert(MetadataValueList metadataValues,
|
||||
public MetadataRest<MetadataValueRest> convert(MetadataValueList metadataValues,
|
||||
Projection projection) {
|
||||
// Convert each value to a DTO while retaining place order in a map of key -> SortedSet
|
||||
Map<String, SortedSet<MetadataValueRest>> mapOfSortedSets = new HashMap<>();
|
||||
@@ -60,7 +60,7 @@ public class MetadataConverter implements DSpaceConverter<MetadataValueList, Met
|
||||
set.add(converter.toRest(metadataValue, projection));
|
||||
}
|
||||
|
||||
MetadataRest metadataRest = new MetadataRest();
|
||||
MetadataRest<MetadataValueRest> metadataRest = new MetadataRest<>();
|
||||
|
||||
// Populate MetadataRest's map of key -> List while respecting SortedSet's order
|
||||
Map<String, List<MetadataValueRest>> mapOfLists = metadataRest.getMap();
|
||||
@@ -80,14 +80,14 @@ public class MetadataConverter implements DSpaceConverter<MetadataValueList, Met
|
||||
* Sets a DSpace object's domain metadata values from a rest representation.
|
||||
* Any existing metadata value is deleted or overwritten.
|
||||
*
|
||||
* @param context the context to use.
|
||||
* @param dso the DSpace object.
|
||||
* @param context the context to use.
|
||||
* @param dso the DSpace object.
|
||||
* @param metadataRest the rest representation of the new metadata.
|
||||
* @throws SQLException if a database error occurs.
|
||||
* @throws SQLException if a database error occurs.
|
||||
* @throws AuthorizeException if an authorization error occurs.
|
||||
*/
|
||||
public <T extends DSpaceObject> void setMetadata(Context context, T dso, MetadataRest metadataRest)
|
||||
throws SQLException, AuthorizeException {
|
||||
throws SQLException, AuthorizeException {
|
||||
DSpaceObjectService<T> dsoService = contentServiceFactory.getDSpaceObjectService(dso);
|
||||
dsoService.clearMetadata(context, dso, Item.ANY, Item.ANY, Item.ANY, Item.ANY);
|
||||
persistMetadataRest(context, dso, metadataRest, dsoService);
|
||||
@@ -97,14 +97,14 @@ public class MetadataConverter implements DSpaceConverter<MetadataValueList, Met
|
||||
* Add to a DSpace object's domain metadata values from a rest representation.
|
||||
* Any existing metadata value is preserved.
|
||||
*
|
||||
* @param context the context to use.
|
||||
* @param dso the DSpace object.
|
||||
* @param context the context to use.
|
||||
* @param dso the DSpace object.
|
||||
* @param metadataRest the rest representation of the new metadata.
|
||||
* @throws SQLException if a database error occurs.
|
||||
* @throws SQLException if a database error occurs.
|
||||
* @throws AuthorizeException if an authorization error occurs.
|
||||
*/
|
||||
public <T extends DSpaceObject> void addMetadata(Context context, T dso, MetadataRest metadataRest)
|
||||
throws SQLException, AuthorizeException {
|
||||
throws SQLException, AuthorizeException {
|
||||
DSpaceObjectService<T> dsoService = contentServiceFactory.getDSpaceObjectService(dso);
|
||||
persistMetadataRest(context, dso, metadataRest, dsoService);
|
||||
}
|
||||
@@ -113,33 +113,34 @@ public class MetadataConverter implements DSpaceConverter<MetadataValueList, Met
|
||||
* Merge into a DSpace object's domain metadata values from a rest representation.
|
||||
* Any existing metadata value is preserved or overwritten with the new ones
|
||||
*
|
||||
* @param context the context to use.
|
||||
* @param dso the DSpace object.
|
||||
* @param context the context to use.
|
||||
* @param dso the DSpace object.
|
||||
* @param metadataRest the rest representation of the new metadata.
|
||||
* @throws SQLException if a database error occurs.
|
||||
* @throws SQLException if a database error occurs.
|
||||
* @throws AuthorizeException if an authorization error occurs.
|
||||
*/
|
||||
public <T extends DSpaceObject> void mergeMetadata(Context context, T dso, MetadataRest metadataRest)
|
||||
throws SQLException, AuthorizeException {
|
||||
public <T extends DSpaceObject> void mergeMetadata(
|
||||
Context context, T dso, MetadataRest<MetadataValueRest> metadataRest
|
||||
) throws SQLException, AuthorizeException {
|
||||
DSpaceObjectService<T> dsoService = contentServiceFactory.getDSpaceObjectService(dso);
|
||||
for (Map.Entry<String, List<MetadataValueRest>> entry: metadataRest.getMap().entrySet()) {
|
||||
for (Map.Entry<String, List<MetadataValueRest>> entry : metadataRest.getMap().entrySet()) {
|
||||
List<MetadataValue> metadataByMetadataString = dsoService.getMetadataByMetadataString(dso, entry.getKey());
|
||||
dsoService.removeMetadataValues(context, dso, metadataByMetadataString);
|
||||
}
|
||||
persistMetadataRest(context, dso, metadataRest, dsoService);
|
||||
}
|
||||
|
||||
private <T extends DSpaceObject> void persistMetadataRest(Context context, T dso, MetadataRest metadataRest,
|
||||
DSpaceObjectService<T> dsoService)
|
||||
throws SQLException, AuthorizeException {
|
||||
for (Map.Entry<String, List<MetadataValueRest>> entry: metadataRest.getMap().entrySet()) {
|
||||
private <T extends DSpaceObject> void persistMetadataRest(
|
||||
Context context, T dso, MetadataRest<MetadataValueRest> metadataRest, DSpaceObjectService<T> dsoService
|
||||
) throws SQLException, AuthorizeException {
|
||||
for (Map.Entry<String, List<MetadataValueRest>> entry : metadataRest.getMap().entrySet()) {
|
||||
String[] seq = entry.getKey().split("\\.");
|
||||
String schema = seq[0];
|
||||
String element = seq[1];
|
||||
String qualifier = seq.length == 3 ? seq[2] : null;
|
||||
for (MetadataValueRest mvr: entry.getValue()) {
|
||||
for (MetadataValueRest mvr : entry.getValue()) {
|
||||
dsoService.addMetadata(context, dso, schema, element, qualifier, mvr.getLanguage(),
|
||||
mvr.getValue(), mvr.getAuthority(), mvr.getConfidence());
|
||||
mvr.getValue(), mvr.getAuthority(), mvr.getConfidence());
|
||||
}
|
||||
}
|
||||
dsoService.update(context, dso);
|
||||
|
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* 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.converter;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.Optional;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.dspace.app.rest.model.MetadataRest;
|
||||
import org.dspace.app.rest.model.RegistrationMetadataRest;
|
||||
import org.dspace.app.rest.model.RegistrationRest;
|
||||
import org.dspace.app.rest.projection.Projection;
|
||||
import org.dspace.app.rest.utils.ContextUtil;
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
import org.dspace.content.MetadataValue;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.eperson.EPerson;
|
||||
import org.dspace.eperson.RegistrationData;
|
||||
import org.dspace.eperson.RegistrationTypeEnum;
|
||||
import org.dspace.eperson.factory.EPersonServiceFactory;
|
||||
import org.dspace.eperson.service.AccountService;
|
||||
import org.dspace.eperson.service.RegistrationDataService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Converts a given {@link RegistrationRest} DTO into a {@link RegistrationData} entity.
|
||||
*
|
||||
* @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com)
|
||||
**/
|
||||
@Component
|
||||
public class RegistrationDataConverter implements DSpaceConverter<RegistrationData, RegistrationRest> {
|
||||
|
||||
@Autowired
|
||||
private HttpServletRequest request;
|
||||
|
||||
@Autowired
|
||||
private RegistrationDataService registrationDataService;
|
||||
|
||||
@Override
|
||||
public RegistrationRest convert(RegistrationData registrationData, Projection projection) {
|
||||
|
||||
if (registrationData == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Context context = ContextUtil.obtainContext(request);
|
||||
|
||||
AccountService accountService = EPersonServiceFactory.getInstance().getAccountService();
|
||||
RegistrationRest registrationRest = new RegistrationRest();
|
||||
registrationRest.setId(registrationData.getID());
|
||||
registrationRest.setEmail(registrationData.getEmail());
|
||||
registrationRest.setNetId(registrationData.getNetId());
|
||||
registrationRest.setRegistrationType(
|
||||
Optional.ofNullable(registrationData.getRegistrationType())
|
||||
.map(RegistrationTypeEnum::toString)
|
||||
.orElse(null)
|
||||
);
|
||||
|
||||
EPerson ePerson = null;
|
||||
try {
|
||||
ePerson = accountService.getEPerson(context, registrationData.getToken());
|
||||
} catch (SQLException | AuthorizeException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
if (ePerson != null) {
|
||||
registrationRest.setUser(ePerson.getID());
|
||||
try {
|
||||
MetadataRest<RegistrationMetadataRest> metadataRest = getMetadataRest(ePerson, registrationData);
|
||||
if (registrationData.getEmail() != null) {
|
||||
metadataRest.put(
|
||||
"email",
|
||||
new RegistrationMetadataRest(registrationData.getEmail(), ePerson.getEmail())
|
||||
);
|
||||
}
|
||||
registrationRest.setRegistrationMetadata(metadataRest);
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else {
|
||||
registrationRest.setRegistrationMetadata(getMetadataRest(registrationData));
|
||||
}
|
||||
|
||||
return registrationRest;
|
||||
}
|
||||
|
||||
|
||||
private MetadataRest<RegistrationMetadataRest> getMetadataRest(EPerson ePerson, RegistrationData registrationData)
|
||||
throws SQLException {
|
||||
return registrationDataService.groupEpersonMetadataByRegistrationData(ePerson, registrationData)
|
||||
.reduce(
|
||||
new MetadataRest<>(),
|
||||
(map, entry) -> map.put(
|
||||
entry.getKey().getMetadataField().toString('.'),
|
||||
new RegistrationMetadataRest(
|
||||
entry.getKey().getValue(),
|
||||
entry.getValue().map(MetadataValue::getValue).orElse(null)
|
||||
)
|
||||
),
|
||||
(m1, m2) -> {
|
||||
m1.getMap().putAll(m2.getMap());
|
||||
return m1;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private MetadataRest<RegistrationMetadataRest> getMetadataRest(RegistrationData registrationData) {
|
||||
MetadataRest<RegistrationMetadataRest> metadataRest = new MetadataRest<>();
|
||||
registrationData.getMetadata().forEach(
|
||||
(m) -> metadataRest.put(
|
||||
m.getMetadataField().toString('.'),
|
||||
new RegistrationMetadataRest(m.getValue())
|
||||
)
|
||||
);
|
||||
return metadataRest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<RegistrationData> getModelClass() {
|
||||
return RegistrationData.class;
|
||||
}
|
||||
|
||||
}
|
@@ -20,7 +20,7 @@ public abstract class DSpaceObjectRest extends BaseObjectRest<String> {
|
||||
private String name;
|
||||
private String handle;
|
||||
|
||||
MetadataRest metadata = new MetadataRest();
|
||||
MetadataRest<MetadataValueRest> metadata = new MetadataRest<>();
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
@@ -56,11 +56,11 @@ public abstract class DSpaceObjectRest extends BaseObjectRest<String> {
|
||||
*
|
||||
* @return the metadata.
|
||||
*/
|
||||
public MetadataRest getMetadata() {
|
||||
public MetadataRest<MetadataValueRest> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
public void setMetadata(MetadataRest metadata) {
|
||||
public void setMetadata(MetadataRest<MetadataValueRest> metadata) {
|
||||
this.metadata = metadata;
|
||||
}
|
||||
|
||||
|
@@ -19,10 +19,10 @@ import org.apache.commons.lang3.builder.HashCodeBuilder;
|
||||
/**
|
||||
* Rest representation of a map of metadata keys to ordered lists of values.
|
||||
*/
|
||||
public class MetadataRest {
|
||||
public class MetadataRest<T extends MetadataValueRest> {
|
||||
|
||||
@JsonAnySetter
|
||||
private SortedMap<String, List<MetadataValueRest>> map = new TreeMap();
|
||||
private SortedMap<String, List<T>> map = new TreeMap();
|
||||
|
||||
/**
|
||||
* Gets the map.
|
||||
@@ -30,7 +30,7 @@ public class MetadataRest {
|
||||
* @return the map of keys to ordered values.
|
||||
*/
|
||||
@JsonAnyGetter
|
||||
public SortedMap<String, List<MetadataValueRest>> getMap() {
|
||||
public SortedMap<String, List<T>> getMap() {
|
||||
return map;
|
||||
}
|
||||
|
||||
@@ -44,16 +44,16 @@ public class MetadataRest {
|
||||
* they are passed to this method.
|
||||
* @return this instance, to support chaining calls for easy initialization.
|
||||
*/
|
||||
public MetadataRest put(String key, MetadataValueRest... values) {
|
||||
public MetadataRest put(String key, T... values) {
|
||||
// determine highest explicitly ordered value
|
||||
int highest = -1;
|
||||
for (MetadataValueRest value : values) {
|
||||
for (T value : values) {
|
||||
if (value.getPlace() > highest) {
|
||||
highest = value.getPlace();
|
||||
}
|
||||
}
|
||||
// add any non-explicitly ordered values after highest
|
||||
for (MetadataValueRest value : values) {
|
||||
for (T value : values) {
|
||||
if (value.getPlace() < 0) {
|
||||
highest++;
|
||||
value.setPlace(highest);
|
||||
|
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* 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.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
|
||||
/**
|
||||
* This POJO represents a {@link MetadataValueRest} that will be placed inside a given
|
||||
* {@link org.dspace.eperson.RegistrationData} that is coming directly from the REST controller.
|
||||
*
|
||||
* @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com)
|
||||
**/
|
||||
public class RegistrationMetadataRest extends MetadataValueRest {
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
private String overrides;
|
||||
|
||||
public RegistrationMetadataRest(String value, String overrides) {
|
||||
super();
|
||||
this.value = value;
|
||||
this.overrides = overrides;
|
||||
}
|
||||
|
||||
public RegistrationMetadataRest(String value) {
|
||||
this(value, null);
|
||||
}
|
||||
|
||||
public String getOverrides() {
|
||||
return overrides;
|
||||
}
|
||||
|
||||
public void setOverrides(String overrides) {
|
||||
this.overrides = overrides;
|
||||
}
|
||||
}
|
@@ -9,6 +9,7 @@ package org.dspace.app.rest.model;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.dspace.app.rest.RestResourceController;
|
||||
|
||||
@@ -24,11 +25,25 @@ public class RegistrationRest extends RestAddressableModel {
|
||||
public static final String PLURAL_NAME = "registrations";
|
||||
public static final String CATEGORY = EPERSON;
|
||||
|
||||
private Integer id;
|
||||
private String email;
|
||||
private UUID user;
|
||||
private String registrationType;
|
||||
private String netId;
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
private MetadataRest<RegistrationMetadataRest> registrationMetadata;
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic getter for the email
|
||||
*
|
||||
* @return the email value of this RegisterRest
|
||||
*/
|
||||
public String getEmail() {
|
||||
@@ -37,7 +52,8 @@ public class RegistrationRest extends RestAddressableModel {
|
||||
|
||||
/**
|
||||
* Generic setter for the email
|
||||
* @param email The email to be set on this RegisterRest
|
||||
*
|
||||
* @param email The email to be set on this RegisterRest
|
||||
*/
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
@@ -45,6 +61,7 @@ public class RegistrationRest extends RestAddressableModel {
|
||||
|
||||
/**
|
||||
* Generic getter for the user
|
||||
*
|
||||
* @return the user value of this RegisterRest
|
||||
*/
|
||||
public UUID getUser() {
|
||||
@@ -53,12 +70,38 @@ public class RegistrationRest extends RestAddressableModel {
|
||||
|
||||
/**
|
||||
* Generic setter for the user
|
||||
* @param user The user to be set on this RegisterRest
|
||||
*
|
||||
* @param user The user to be set on this RegisterRest
|
||||
*/
|
||||
public void setUser(UUID user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public String getRegistrationType() {
|
||||
return registrationType;
|
||||
}
|
||||
|
||||
public void setRegistrationType(String registrationType) {
|
||||
this.registrationType = registrationType;
|
||||
}
|
||||
|
||||
public String getNetId() {
|
||||
return netId;
|
||||
}
|
||||
|
||||
public void setNetId(String netId) {
|
||||
this.netId = netId;
|
||||
}
|
||||
|
||||
public MetadataRest<RegistrationMetadataRest> getRegistrationMetadata() {
|
||||
return registrationMetadata;
|
||||
}
|
||||
|
||||
public void setRegistrationMetadata(
|
||||
MetadataRest<RegistrationMetadataRest> registrationMetadata) {
|
||||
this.registrationMetadata = registrationMetadata;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return CATEGORY;
|
||||
|
@@ -19,6 +19,7 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.dspace.app.rest.DiscoverableEndpointsService;
|
||||
import org.dspace.app.rest.EPersonRegistrationRestController;
|
||||
import org.dspace.app.rest.Parameter;
|
||||
import org.dspace.app.rest.SearchRestMethod;
|
||||
import org.dspace.app.rest.exception.DSpaceBadRequestException;
|
||||
@@ -190,7 +191,7 @@ public class EPersonRestRepository extends DSpaceObjectRestRepository<EPerson, E
|
||||
throw new DSpaceBadRequestException("The self registered property cannot be set to false using this method"
|
||||
+ " with a token");
|
||||
}
|
||||
checkRequiredProperties(epersonRest);
|
||||
checkRequiredProperties(registrationData, epersonRest);
|
||||
// We'll turn off authorisation system because this call isn't admin based as it's token based
|
||||
context.turnOffAuthorisationSystem();
|
||||
EPerson ePerson = createEPersonFromRestObject(context, epersonRest);
|
||||
@@ -203,8 +204,8 @@ public class EPersonRestRepository extends DSpaceObjectRestRepository<EPerson, E
|
||||
return converter.toRest(ePerson, utils.obtainProjection());
|
||||
}
|
||||
|
||||
private void checkRequiredProperties(EPersonRest epersonRest) {
|
||||
MetadataRest metadataRest = epersonRest.getMetadata();
|
||||
private void checkRequiredProperties(RegistrationData registration, EPersonRest epersonRest) {
|
||||
MetadataRest<MetadataValueRest> metadataRest = epersonRest.getMetadata();
|
||||
if (metadataRest != null) {
|
||||
List<MetadataValueRest> epersonFirstName = metadataRest.getMap().get("eperson.firstname");
|
||||
List<MetadataValueRest> epersonLastName = metadataRest.getMap().get("eperson.lastname");
|
||||
@@ -213,10 +214,25 @@ public class EPersonRestRepository extends DSpaceObjectRestRepository<EPerson, E
|
||||
throw new EPersonNameNotProvidedException();
|
||||
}
|
||||
}
|
||||
|
||||
String password = epersonRest.getPassword();
|
||||
if (StringUtils.isBlank(password)) {
|
||||
throw new DSpaceBadRequestException("A password is required");
|
||||
String netId = epersonRest.getNetid();
|
||||
if (StringUtils.isBlank(password) && StringUtils.isBlank(netId)) {
|
||||
throw new DSpaceBadRequestException(
|
||||
"You must provide a password or register using an external account"
|
||||
);
|
||||
}
|
||||
|
||||
if (StringUtils.isBlank(password) && !canRegisterExternalAccount(registration, epersonRest)) {
|
||||
throw new DSpaceBadRequestException(
|
||||
"Cannot register external account with netId: " + netId
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean canRegisterExternalAccount(RegistrationData registration, EPersonRest epersonRest) {
|
||||
return accountService.isTokenValidForCreation(registration) &&
|
||||
StringUtils.equals(registration.getNetId(), epersonRest.getNetid());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -369,6 +385,40 @@ public class EPersonRestRepository extends DSpaceObjectRestRepository<EPerson, E
|
||||
return EPersonRest.class;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method tries to merge the details coming from the {@link EPersonRegistrationRestController} of a given
|
||||
* {@code uuid} eperson. <br/>
|
||||
*
|
||||
* @param context - The Dspace Context
|
||||
* @param uuid - The uuid of the eperson
|
||||
* @param token - A valid registration token
|
||||
* @param override - An optional list of metadata fields that will be overwritten
|
||||
* @return a EPersonRest entity updated with the registration data.
|
||||
* @throws AuthorizeException
|
||||
*/
|
||||
public EPersonRest mergeFromRegistrationData(
|
||||
Context context, UUID uuid, String token, List<String> override
|
||||
) throws AuthorizeException {
|
||||
try {
|
||||
|
||||
if (uuid == null) {
|
||||
throw new DSpaceBadRequestException("The uuid of the person cannot be null");
|
||||
}
|
||||
|
||||
if (token == null) {
|
||||
throw new DSpaceBadRequestException("You must provide a token for the eperson");
|
||||
}
|
||||
|
||||
return converter.toRest(
|
||||
accountService.mergeRegistration(context, uuid, token, override),
|
||||
utils.obtainProjection()
|
||||
);
|
||||
} catch (SQLException e) {
|
||||
log.error(e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
discoverableEndpointsService.register(this, Arrays.asList(
|
||||
|
@@ -16,6 +16,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.mail.MessagingException;
|
||||
import jakarta.servlet.ServletInputStream;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.ws.rs.BadRequestException;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
@@ -25,6 +26,10 @@ import org.dspace.app.rest.exception.DSpaceBadRequestException;
|
||||
import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException;
|
||||
import org.dspace.app.rest.exception.UnprocessableEntityException;
|
||||
import org.dspace.app.rest.model.RegistrationRest;
|
||||
import org.dspace.app.rest.model.patch.Patch;
|
||||
import org.dspace.app.rest.repository.patch.ResourcePatch;
|
||||
import org.dspace.app.rest.repository.patch.operation.RegistrationEmailPatchOperation;
|
||||
import org.dspace.app.rest.utils.Utils;
|
||||
import org.dspace.app.util.AuthorizeUtil;
|
||||
import org.dspace.authenticate.service.AuthenticationService;
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
@@ -32,6 +37,7 @@ import org.dspace.core.Context;
|
||||
import org.dspace.eperson.EPerson;
|
||||
import org.dspace.eperson.InvalidReCaptchaException;
|
||||
import org.dspace.eperson.RegistrationData;
|
||||
import org.dspace.eperson.RegistrationTypeEnum;
|
||||
import org.dspace.eperson.service.AccountService;
|
||||
import org.dspace.eperson.service.CaptchaService;
|
||||
import org.dspace.eperson.service.EPersonService;
|
||||
@@ -54,9 +60,10 @@ public class RegistrationRestRepository extends DSpaceRestRepository<Registratio
|
||||
|
||||
private static Logger log = LogManager.getLogger(RegistrationRestRepository.class);
|
||||
|
||||
public static final String TOKEN_QUERY_PARAM = "token";
|
||||
public static final String TYPE_QUERY_PARAM = "accountRequestType";
|
||||
public static final String TYPE_REGISTER = "register";
|
||||
public static final String TYPE_FORGOT = "forgot";
|
||||
public static final String TYPE_REGISTER = RegistrationTypeEnum.REGISTER.toString().toLowerCase();
|
||||
public static final String TYPE_FORGOT = RegistrationTypeEnum.FORGOT.toString().toLowerCase();
|
||||
|
||||
@Autowired
|
||||
private EPersonService ePersonService;
|
||||
@@ -79,6 +86,12 @@ public class RegistrationRestRepository extends DSpaceRestRepository<Registratio
|
||||
@Autowired
|
||||
private RegistrationDataService registrationDataService;
|
||||
|
||||
@Autowired
|
||||
private Utils utils;
|
||||
|
||||
@Autowired
|
||||
private ResourcePatch<RegistrationData> resourcePatch;
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper mapper;
|
||||
|
||||
@@ -144,46 +157,42 @@ public class RegistrationRestRepository extends DSpaceRestRepository<Registratio
|
||||
+ registrationRest.getEmail(), e);
|
||||
}
|
||||
} else if (accountType.equalsIgnoreCase(TYPE_REGISTER)) {
|
||||
if (eperson == null) {
|
||||
try {
|
||||
String email = registrationRest.getEmail();
|
||||
if (!AuthorizeUtil.authorizeNewAccountRegistration(context, request)) {
|
||||
throw new AccessDeniedException(
|
||||
"Registration is disabled, you are not authorized to create a new Authorization");
|
||||
}
|
||||
if (!authenticationService.canSelfRegister(context, request, email)) {
|
||||
throw new UnprocessableEntityException(
|
||||
String.format("Registration is not allowed with email address" +
|
||||
try {
|
||||
String email = registrationRest.getEmail();
|
||||
if (!AuthorizeUtil.authorizeNewAccountRegistration(context, request)) {
|
||||
throw new AccessDeniedException(
|
||||
"Registration is disabled, you are not authorized to create a new Authorization");
|
||||
}
|
||||
|
||||
if (!authenticationService.canSelfRegister(context, request, registrationRest.getEmail())) {
|
||||
throw new UnprocessableEntityException(
|
||||
String.format("Registration is not allowed with email address" +
|
||||
" %s", email));
|
||||
}
|
||||
accountService.sendRegistrationInfo(context, email);
|
||||
} catch (SQLException | IOException | MessagingException | AuthorizeException e) {
|
||||
log.error("Something went wrong with sending registration info email: "
|
||||
+ registrationRest.getEmail(), e);
|
||||
}
|
||||
} else {
|
||||
// if an eperson with this email already exists then send "forgot password" email instead
|
||||
try {
|
||||
accountService.sendForgotPasswordInfo(context, registrationRest.getEmail());
|
||||
} catch (SQLException | IOException | MessagingException | AuthorizeException e) {
|
||||
log.error("Something went wrong with sending forgot password info email: "
|
||||
|
||||
accountService.sendRegistrationInfo(context, registrationRest.getEmail());
|
||||
} catch (SQLException | IOException | MessagingException | AuthorizeException e) {
|
||||
log.error("Something went wrong with sending registration info email: "
|
||||
+ registrationRest.getEmail(), e);
|
||||
}
|
||||
} else {
|
||||
// if an eperson with this email already exists then send "forgot password" email instead
|
||||
try {
|
||||
accountService.sendForgotPasswordInfo(context, registrationRest.getEmail());
|
||||
} catch (SQLException | IOException | MessagingException | AuthorizeException e) {
|
||||
log.error("Something went wrong with sending forgot password info email: "
|
||||
+ registrationRest.getEmail(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<RegistrationRest> getDomainClass() {
|
||||
return RegistrationRest.class;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will find the RegistrationRest object that is associated with the token given
|
||||
*
|
||||
* @param token The token to be found and for which a RegistrationRest object will be found
|
||||
* @return A RegistrationRest object for the given token
|
||||
* @throws SQLException If something goes wrong
|
||||
* @return A RegistrationRest object for the given token
|
||||
* @throws SQLException If something goes wrong
|
||||
* @throws AuthorizeException If something goes wrong
|
||||
*/
|
||||
@SearchRestMethod(name = "findByToken")
|
||||
@@ -194,17 +203,62 @@ public class RegistrationRestRepository extends DSpaceRestRepository<Registratio
|
||||
if (registrationData == null) {
|
||||
throw new ResourceNotFoundException("The token: " + token + " couldn't be found");
|
||||
}
|
||||
RegistrationRest registrationRest = new RegistrationRest();
|
||||
registrationRest.setEmail(registrationData.getEmail());
|
||||
EPerson ePerson = accountService.getEPerson(context, token);
|
||||
if (ePerson != null) {
|
||||
registrationRest.setUser(ePerson.getID());
|
||||
return converter.toRest(registrationData, utils.obtainProjection());
|
||||
}
|
||||
|
||||
private void validateToken(Context context, String token) {
|
||||
try {
|
||||
RegistrationData registrationData =
|
||||
registrationDataService.findByToken(context, token);
|
||||
if (registrationData == null || !registrationDataService.isValid(registrationData)) {
|
||||
throw new AccessDeniedException("The token is invalid");
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return registrationRest;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method can be used to update a {@link RegistrationData} with a given {@code id} that has a valid
|
||||
* {@code token} with the actions described in the {@link Patch} object.
|
||||
* This method is used to patch the email value, and will generate a completely new {@code token} that will be
|
||||
* sent with an email {@link RegistrationEmailPatchOperation}.
|
||||
*
|
||||
*/
|
||||
@Override
|
||||
public RegistrationRest patch(
|
||||
HttpServletRequest request, String apiCategory, String model, Integer id, Patch patch
|
||||
) throws UnprocessableEntityException, DSpaceBadRequestException {
|
||||
if (id == null || id <= 0) {
|
||||
throw new BadRequestException("The id of the registration cannot be null or negative");
|
||||
}
|
||||
if (patch == null || patch.getOperations() == null || patch.getOperations().isEmpty()) {
|
||||
throw new BadRequestException("Patch request is incomplete: cannot find operations");
|
||||
}
|
||||
String token = request.getParameter("token");
|
||||
if (token == null || token.trim().isBlank()) {
|
||||
throw new AccessDeniedException("The token is required");
|
||||
}
|
||||
Context context = obtainContext();
|
||||
|
||||
validateToken(context, token);
|
||||
|
||||
try {
|
||||
resourcePatch.patch(context, registrationDataService.find(context, id), patch.getOperations());
|
||||
context.commit();
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e.getMessage(), e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setCaptchaService(CaptchaService captchaService) {
|
||||
this.captchaService = captchaService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<RegistrationRest> getDomainClass() {
|
||||
return RegistrationRest.class;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* 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.repository.patch.operation;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.Optional;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.dspace.app.rest.exception.DSpaceBadRequestException;
|
||||
import org.dspace.app.rest.exception.UnprocessableEntityException;
|
||||
import org.dspace.app.rest.model.patch.JsonValueEvaluator;
|
||||
import org.dspace.app.rest.model.patch.Operation;
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.eperson.RegistrationData;
|
||||
import org.dspace.eperson.RegistrationTypeEnum;
|
||||
import org.dspace.eperson.dto.RegistrationDataChanges;
|
||||
import org.dspace.eperson.dto.RegistrationDataPatch;
|
||||
import org.dspace.eperson.service.AccountService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Implementation for RegistrationData email patches.
|
||||
*
|
||||
* Example: <code>
|
||||
* curl -X PATCH http://${dspace.server.url}/api/eperson/registration/<:registration-id>?token=<:token> -H "
|
||||
* Content-Type: application/json" -d '[{ "op": "replace", "path": "/email", "value": "new@email"]'
|
||||
* </code>
|
||||
*
|
||||
* @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com)
|
||||
**/
|
||||
@Component
|
||||
public class RegistrationEmailPatchOperation<R extends RegistrationData> extends PatchOperation<R> {
|
||||
|
||||
/**
|
||||
* Path in json body of patch that uses this operation
|
||||
*/
|
||||
private static final String OPERATION_PATH_EMAIL = "/email";
|
||||
|
||||
@Autowired
|
||||
private AccountService accountService;
|
||||
|
||||
@Override
|
||||
public R perform(Context context, R object, Operation operation) {
|
||||
checkOperationValue(operation.getValue());
|
||||
|
||||
RegistrationDataPatch registrationDataPatch;
|
||||
try {
|
||||
String email = getTextValue(operation);
|
||||
registrationDataPatch =
|
||||
new RegistrationDataPatch(
|
||||
object,
|
||||
new RegistrationDataChanges(
|
||||
email,
|
||||
registrationTypeFor(context, object, email)
|
||||
)
|
||||
);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new UnprocessableEntityException(
|
||||
"Cannot perform the patch operation",
|
||||
e
|
||||
);
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
if (!supports(object, operation)) {
|
||||
throw new UnprocessableEntityException(
|
||||
MessageFormat.format(
|
||||
"RegistrationEmailReplaceOperation does not support {0} operation",
|
||||
operation.getOp()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (!isOperationAllowed(operation, object)) {
|
||||
throw new UnprocessableEntityException(
|
||||
MessageFormat.format(
|
||||
"Attempting to perform {0} operation over {1} value (e-mail).",
|
||||
operation.getOp(),
|
||||
object.getEmail() == null ? "null" : "not null"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
return (R) accountService.renewRegistrationForEmail(context, registrationDataPatch);
|
||||
} catch (AuthorizeException e) {
|
||||
throw new DSpaceBadRequestException(
|
||||
MessageFormat.format(
|
||||
"Cannot perform {0} operation over {1} value (e-mail).",
|
||||
operation.getOp(),
|
||||
object.getEmail() == null ? "null" : "not null"
|
||||
),
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getTextValue(Operation operation) {
|
||||
Object value = operation.getValue();
|
||||
|
||||
if (value instanceof String) {
|
||||
return ((String) value);
|
||||
}
|
||||
|
||||
if (value instanceof JsonValueEvaluator) {
|
||||
return Optional.of((JsonValueEvaluator) value)
|
||||
.map(JsonValueEvaluator::getValueNode)
|
||||
.filter(nodes -> !nodes.isEmpty())
|
||||
.map(nodes -> nodes.get(0))
|
||||
.map(JsonNode::asText)
|
||||
.orElseThrow(() -> new DSpaceBadRequestException("No value provided for operation"));
|
||||
}
|
||||
throw new DSpaceBadRequestException("Invalid patch value for operation!");
|
||||
}
|
||||
|
||||
private RegistrationTypeEnum registrationTypeFor(
|
||||
Context context, R object, String email
|
||||
)
|
||||
throws SQLException {
|
||||
if (accountService.existsAccountWithEmail(context, email)) {
|
||||
return RegistrationTypeEnum.VALIDATION_ORCID;
|
||||
}
|
||||
return object.getRegistrationType();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether the email of RegistrationData has an existing value to replace or adds a new value.
|
||||
*
|
||||
* @param operation operation to check
|
||||
* @param registrationData Object on which patch is being done
|
||||
*/
|
||||
private boolean isOperationAllowed(Operation operation, RegistrationData registrationData) {
|
||||
return isReplaceOperationAllowed(operation, registrationData) ||
|
||||
isAddOperationAllowed(operation, registrationData);
|
||||
}
|
||||
|
||||
private boolean isAddOperationAllowed(Operation operation, RegistrationData registrationData) {
|
||||
return operation.getOp().trim().equalsIgnoreCase(OPERATION_ADD) && registrationData.getEmail() == null;
|
||||
}
|
||||
|
||||
private static boolean isReplaceOperationAllowed(Operation operation, RegistrationData registrationData) {
|
||||
return operation.getOp().trim().equalsIgnoreCase(OPERATION_REPLACE) && registrationData.getEmail() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Object objectToMatch, Operation operation) {
|
||||
return (objectToMatch instanceof RegistrationData &&
|
||||
(
|
||||
operation.getOp().trim().equalsIgnoreCase(OPERATION_REPLACE) ||
|
||||
operation.getOp().trim().equalsIgnoreCase(OPERATION_ADD)
|
||||
) &&
|
||||
operation.getPath().trim().equalsIgnoreCase(OPERATION_PATH_EMAIL));
|
||||
}
|
||||
}
|
||||
|
@@ -7,7 +7,12 @@
|
||||
*/
|
||||
package org.dspace.app.rest.security;
|
||||
|
||||
import static org.dspace.authenticate.OrcidAuthenticationBean.ORCID_AUTH_ATTRIBUTE;
|
||||
import static org.dspace.authenticate.OrcidAuthenticationBean.ORCID_DEFAULT_REGISTRATION_URL;
|
||||
import static org.dspace.authenticate.OrcidAuthenticationBean.ORCID_REGISTRATION_TOKEN;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
@@ -45,7 +50,8 @@ public class OrcidLoginFilter extends StatelessLoginFilter {
|
||||
private ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService();
|
||||
|
||||
private OrcidAuthenticationBean orcidAuthentication = new DSpace().getServiceManager()
|
||||
.getServiceByName("orcidAuthentication", OrcidAuthenticationBean.class);
|
||||
.getServiceByName("orcidAuthentication",
|
||||
OrcidAuthenticationBean.class);
|
||||
|
||||
public OrcidLoginFilter(String url, String httpMethod, AuthenticationManager authenticationManager,
|
||||
RestAuthenticationService restAuthenticationService) {
|
||||
@@ -66,13 +72,13 @@ public class OrcidLoginFilter extends StatelessLoginFilter {
|
||||
|
||||
@Override
|
||||
protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain,
|
||||
Authentication auth) throws IOException, ServletException {
|
||||
Authentication auth) throws IOException, ServletException {
|
||||
|
||||
|
||||
DSpaceAuthentication dSpaceAuthentication = (DSpaceAuthentication) auth;
|
||||
|
||||
log.debug("Orcid authentication successful for EPerson {}. Sending back temporary auth cookie",
|
||||
dSpaceAuthentication.getName());
|
||||
dSpaceAuthentication.getName());
|
||||
|
||||
restAuthenticationService.addAuthenticationDataForUser(req, res, dSpaceAuthentication, true);
|
||||
|
||||
@@ -81,26 +87,41 @@ public class OrcidLoginFilter extends StatelessLoginFilter {
|
||||
|
||||
@Override
|
||||
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
|
||||
AuthenticationException failed) throws IOException, ServletException {
|
||||
AuthenticationException failed) throws IOException, ServletException {
|
||||
|
||||
Context context = ContextUtil.obtainContext(request);
|
||||
|
||||
if (orcidAuthentication.isUsed(context, request)) {
|
||||
String baseRediredirectUrl = configurationService.getProperty("dspace.ui.url");
|
||||
String redirectUrl = baseRediredirectUrl + "/error?status=401&code=orcid.generic-error";
|
||||
response.sendRedirect(redirectUrl); // lgtm [java/unvalidated-url-redirection]
|
||||
} else {
|
||||
if (!orcidAuthentication.isUsed(context, request)) {
|
||||
super.unsuccessfulAuthentication(request, response, failed);
|
||||
return;
|
||||
}
|
||||
|
||||
String baseRediredirectUrl = configurationService.getProperty("dspace.ui.url");
|
||||
String redirectUrl = baseRediredirectUrl + "/error?status=401&code=orcid.generic-error";
|
||||
Object registrationToken = request.getAttribute(ORCID_REGISTRATION_TOKEN);
|
||||
if (registrationToken != null) {
|
||||
final String orcidRegistrationDataUrl =
|
||||
configurationService.getProperty("orcid.registration-data.url", ORCID_DEFAULT_REGISTRATION_URL);
|
||||
redirectUrl = baseRediredirectUrl + MessageFormat.format(orcidRegistrationDataUrl, registrationToken);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(
|
||||
"Orcid authentication failed for user with ORCID {}.",
|
||||
request.getAttribute(ORCID_AUTH_ATTRIBUTE)
|
||||
);
|
||||
log.debug("Redirecting to {} for registration completion.", redirectUrl);
|
||||
}
|
||||
}
|
||||
|
||||
response.sendRedirect(redirectUrl); // lgtm [java/unvalidated-url-redirection]
|
||||
}
|
||||
|
||||
/**
|
||||
* After successful login, redirect to the DSpace URL specified by this Orcid
|
||||
* request (in the "redirectUrl" request parameter). If that 'redirectUrl' is
|
||||
* not valid or trusted for this DSpace site, then return a 400 error.
|
||||
* @param request
|
||||
* @param response
|
||||
*
|
||||
* @param request
|
||||
* @param response
|
||||
* @throws IOException
|
||||
*/
|
||||
private void redirectAfterSuccess(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
@@ -128,9 +149,9 @@ public class OrcidLoginFilter extends StatelessLoginFilter {
|
||||
response.sendRedirect(redirectUrl);
|
||||
} else {
|
||||
log.error("Invalid Orcid redirectURL=" + redirectUrl +
|
||||
". URL doesn't match hostname of server or UI!");
|
||||
". URL doesn't match hostname of server or UI!");
|
||||
response.sendError(HttpServletResponse.SC_BAD_REQUEST,
|
||||
"Invalid redirectURL! Must match server or ui hostname.");
|
||||
"Invalid redirectURL! Must match server or ui hostname.");
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -32,10 +32,13 @@ import org.springframework.context.annotation.Configuration;
|
||||
"org.dspace.app.rest.converter",
|
||||
"org.dspace.app.rest.repository",
|
||||
"org.dspace.app.rest.utils",
|
||||
"org.dspace.app.rest.link",
|
||||
"org.dspace.app.rest.converter.factory",
|
||||
"org.dspace.app.configuration",
|
||||
"org.dspace.iiif",
|
||||
"org.dspace.app.iiif",
|
||||
"org.dspace.app.ldn"
|
||||
"org.dspace.app.ldn",
|
||||
"org.dspace.app.scheduler"
|
||||
})
|
||||
public class ApplicationConfig {
|
||||
// Allowed CORS origins ("Access-Control-Allow-Origin" header)
|
||||
|
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.scheduler.eperson;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.eperson.RegistrationData;
|
||||
import org.dspace.eperson.service.RegistrationDataService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* Contains all the schedulable task related to {@link RegistrationData} entities.
|
||||
* Can be enabled via the configuration property {@code eperson.registration-data.scheduler.enabled}
|
||||
*
|
||||
* @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com)
|
||||
**/
|
||||
@Service
|
||||
@ConditionalOnProperty(prefix = "eperson.registration-data.scheduler", name = "enabled", havingValue = "true")
|
||||
public class RegistrationDataScheduler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(RegistrationDataScheduler.class);
|
||||
|
||||
@Autowired
|
||||
private RegistrationDataService registrationDataService;
|
||||
|
||||
/**
|
||||
* Deletes expired {@link RegistrationData}.
|
||||
* This task is scheduled to be run by the cron expression defined in the configuration file.
|
||||
*
|
||||
*/
|
||||
@Scheduled(cron = "${eperson.registration-data.scheduler.expired-registration-data.cron:-}")
|
||||
protected void deleteExpiredRegistrationData() throws SQLException {
|
||||
Context context = new Context();
|
||||
context.turnOffAuthorisationSystem();
|
||||
try {
|
||||
|
||||
registrationDataService.deleteExpiredRegistrations(context);
|
||||
|
||||
context.restoreAuthSystemState();
|
||||
context.complete();
|
||||
} catch (Exception e) {
|
||||
context.abort();
|
||||
log.error("Failed to delete expired registrations", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,404 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.rest;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import org.dspace.app.rest.matcher.MetadataMatcher;
|
||||
import org.dspace.app.rest.test.AbstractControllerIntegrationTest;
|
||||
import org.dspace.builder.EPersonBuilder;
|
||||
import org.dspace.content.MetadataField;
|
||||
import org.dspace.content.service.MetadataFieldService;
|
||||
import org.dspace.core.Email;
|
||||
import org.dspace.eperson.EPerson;
|
||||
import org.dspace.eperson.RegistrationData;
|
||||
import org.dspace.eperson.RegistrationTypeEnum;
|
||||
import org.dspace.eperson.dto.RegistrationDataChanges;
|
||||
import org.dspace.eperson.dto.RegistrationDataPatch;
|
||||
import org.dspace.eperson.service.AccountService;
|
||||
import org.dspace.eperson.service.RegistrationDataService;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com)
|
||||
**/
|
||||
public class EPersonRegistrationRestControllerIT extends AbstractControllerIntegrationTest {
|
||||
|
||||
private static MockedStatic<Email> emailMockedStatic;
|
||||
|
||||
@Autowired
|
||||
private AccountService accountService;
|
||||
@Autowired
|
||||
private RegistrationDataService registrationDataService;
|
||||
@Autowired
|
||||
private MetadataFieldService metadataFieldService;
|
||||
|
||||
private RegistrationData orcidRegistration;
|
||||
private MetadataField orcidMf;
|
||||
private MetadataField firstNameMf;
|
||||
private MetadataField lastNameMf;
|
||||
private EPerson customEPerson;
|
||||
private String customPassword;
|
||||
|
||||
|
||||
@BeforeClass
|
||||
public static void init() throws Exception {
|
||||
emailMockedStatic = Mockito.mockStatic(Email.class);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDownClass() throws Exception {
|
||||
emailMockedStatic.close();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
context.turnOffAuthorisationSystem();
|
||||
|
||||
orcidRegistration =
|
||||
registrationDataService.create(context, "0000-0000-0000-0000", RegistrationTypeEnum.ORCID);
|
||||
|
||||
orcidMf =
|
||||
metadataFieldService.findByElement(context, "eperson", "orcid", null);
|
||||
firstNameMf =
|
||||
metadataFieldService.findByElement(context, "eperson", "firstname", null);
|
||||
lastNameMf =
|
||||
metadataFieldService.findByElement(context, "eperson", "lastname", null);
|
||||
|
||||
registrationDataService.addMetadata(
|
||||
context, orcidRegistration, orcidMf, "0000-0000-0000-0000"
|
||||
);
|
||||
registrationDataService.addMetadata(
|
||||
context, orcidRegistration, firstNameMf, "Vincenzo"
|
||||
);
|
||||
registrationDataService.addMetadata(
|
||||
context, orcidRegistration, lastNameMf, "Mecca"
|
||||
);
|
||||
|
||||
registrationDataService.update(context, orcidRegistration);
|
||||
|
||||
customPassword = "vins-01";
|
||||
customEPerson =
|
||||
EPersonBuilder.createEPerson(context)
|
||||
.withEmail("vins-01@fake.mail")
|
||||
.withNameInMetadata("Vins", "4Science")
|
||||
.withPassword(customPassword)
|
||||
.withCanLogin(true)
|
||||
.build();
|
||||
|
||||
context.restoreAuthSystemState();
|
||||
}
|
||||
|
||||
@After
|
||||
public void destroy() throws Exception {
|
||||
RegistrationData found = context.reloadEntity(orcidRegistration);
|
||||
if (found != null) {
|
||||
this.registrationDataService.delete(context, found);
|
||||
}
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void givenOrcidToken_whenPostForMerge_thenUnauthorized() throws Exception {
|
||||
|
||||
getClient().perform(
|
||||
post("/api/eperson/epersons/" + customEPerson.getID())
|
||||
.param("token", orcidRegistration.getToken())
|
||||
.param("override", "eperson.firtname,eperson.lastname,eperson.orcid")
|
||||
).andExpect(status().isUnauthorized());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenExpiredToken_whenPostForMerge_thenUnauthorized() throws Exception {
|
||||
|
||||
context.turnOffAuthorisationSystem();
|
||||
registrationDataService.markAsExpired(context, orcidRegistration);
|
||||
context.restoreAuthSystemState();
|
||||
|
||||
getClient().perform(
|
||||
post("/api/eperson/epersons/" + customEPerson.getID())
|
||||
.param("token", orcidRegistration.getToken())
|
||||
.param("override", "eperson.firtname,eperson.lastname,eperson.orcid")
|
||||
).andExpect(status().isUnauthorized());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenExpiredToken_whenPostAuthForMerge_thenForbidden() throws Exception {
|
||||
|
||||
context.turnOffAuthorisationSystem();
|
||||
registrationDataService.markAsExpired(context, orcidRegistration);
|
||||
context.restoreAuthSystemState();
|
||||
|
||||
String tokenAdmin = getAuthToken(admin.getEmail(), password);
|
||||
|
||||
getClient(tokenAdmin).perform(
|
||||
post("/api/eperson/epersons/" + customEPerson.getID())
|
||||
.param("token", orcidRegistration.getToken())
|
||||
.param("override", "eperson.firtname,eperson.lastname,eperson.orcid")
|
||||
).andExpect(status().isForbidden());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenValidationRegistration_whenPostAuthDiffersFromIdPathParam_thenForbidden() throws Exception {
|
||||
|
||||
context.turnOffAuthorisationSystem();
|
||||
RegistrationData validationRegistration =
|
||||
registrationDataService.create(context, "0000-0000-0000-0000", RegistrationTypeEnum.VALIDATION_ORCID);
|
||||
context.restoreAuthSystemState();
|
||||
|
||||
try {
|
||||
String tokenAdmin = getAuthToken(admin.getEmail(), password);
|
||||
|
||||
getClient(tokenAdmin).perform(
|
||||
post("/api/eperson/epersons/" + customEPerson.getID())
|
||||
.param("token", validationRegistration.getToken())
|
||||
).andExpect(status().isForbidden());
|
||||
} finally {
|
||||
RegistrationData found = context.reloadEntity(validationRegistration);
|
||||
if (found != null) {
|
||||
this.registrationDataService.delete(context, found);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenValidationRegistration_whenPostWithoutOverride_thenCreated() throws Exception {
|
||||
|
||||
Email spy = Mockito.spy(Email.class);
|
||||
doNothing().when(spy).send();
|
||||
|
||||
emailMockedStatic.when(() -> Email.getEmail(any())).thenReturn(spy);
|
||||
|
||||
context.turnOffAuthorisationSystem();
|
||||
RegistrationDataChanges changes =
|
||||
new RegistrationDataChanges("vins-01@fake.mail", RegistrationTypeEnum.VALIDATION_ORCID);
|
||||
RegistrationData validationRegistration =
|
||||
this.accountService.renewRegistrationForEmail(
|
||||
context, new RegistrationDataPatch(orcidRegistration, changes)
|
||||
);
|
||||
context.restoreAuthSystemState();
|
||||
|
||||
try {
|
||||
String customToken = getAuthToken(customEPerson.getEmail(), customPassword);
|
||||
|
||||
getClient(customToken).perform(
|
||||
post("/api/eperson/epersons/" + customEPerson.getID())
|
||||
.param("token", validationRegistration.getToken())
|
||||
).andExpect(status().isCreated());
|
||||
|
||||
} finally {
|
||||
RegistrationData found = context.reloadEntity(validationRegistration);
|
||||
if (found != null) {
|
||||
this.registrationDataService.delete(context, found);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenValidationRegistration_whenPostWithOverride_thenCreated() throws Exception {
|
||||
|
||||
Email spy = Mockito.spy(Email.class);
|
||||
doNothing().when(spy).send();
|
||||
|
||||
emailMockedStatic.when(() -> Email.getEmail(any())).thenReturn(spy);
|
||||
|
||||
context.turnOffAuthorisationSystem();
|
||||
RegistrationDataChanges changes =
|
||||
new RegistrationDataChanges("vins-01@fake.mail", RegistrationTypeEnum.VALIDATION_ORCID);
|
||||
RegistrationData validationRegistration =
|
||||
this.accountService.renewRegistrationForEmail(
|
||||
context, new RegistrationDataPatch(orcidRegistration, changes)
|
||||
);
|
||||
context.restoreAuthSystemState();
|
||||
|
||||
try {
|
||||
|
||||
String customToken = getAuthToken(customEPerson.getEmail(), customPassword);
|
||||
|
||||
getClient(customToken).perform(
|
||||
post("/api/eperson/epersons/" + customEPerson.getID())
|
||||
.param("token", validationRegistration.getToken())
|
||||
.param("override", "eperson.firstname,eperson.lastname")
|
||||
).andExpect(status().isCreated());
|
||||
|
||||
} finally {
|
||||
RegistrationData found = context.reloadEntity(validationRegistration);
|
||||
if (found != null) {
|
||||
this.registrationDataService.delete(context, found);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenValidationRegistration_whenPostWithoutOverride_thenOnlyNewMetadataAdded() throws Exception {
|
||||
|
||||
Email spy = Mockito.spy(Email.class);
|
||||
doNothing().when(spy).send();
|
||||
|
||||
emailMockedStatic.when(() -> Email.getEmail(any())).thenReturn(spy);
|
||||
|
||||
context.turnOffAuthorisationSystem();
|
||||
RegistrationDataChanges changes =
|
||||
new RegistrationDataChanges("vins-01@fake.mail", RegistrationTypeEnum.VALIDATION_ORCID);
|
||||
RegistrationData validationRegistration =
|
||||
this.accountService.renewRegistrationForEmail(
|
||||
context, new RegistrationDataPatch(orcidRegistration, changes)
|
||||
);
|
||||
context.restoreAuthSystemState();
|
||||
|
||||
try {
|
||||
String customToken = getAuthToken(customEPerson.getEmail(), customPassword);
|
||||
|
||||
getClient(customToken).perform(
|
||||
post("/api/eperson/epersons/" + customEPerson.getID())
|
||||
.param("token", validationRegistration.getToken())
|
||||
).andExpect(status().isCreated())
|
||||
.andExpect(
|
||||
jsonPath("$.netid", equalTo("0000-0000-0000-0000"))
|
||||
)
|
||||
.andExpect(
|
||||
jsonPath("$.metadata",
|
||||
Matchers.allOf(
|
||||
MetadataMatcher.matchMetadata("eperson.firstname", "Vins"),
|
||||
MetadataMatcher.matchMetadata("eperson.lastname", "4Science"),
|
||||
MetadataMatcher.matchMetadata("eperson.orcid", "0000-0000-0000-0000")
|
||||
)
|
||||
)
|
||||
);
|
||||
} finally {
|
||||
RegistrationData found = context.reloadEntity(validationRegistration);
|
||||
if (found != null) {
|
||||
this.registrationDataService.delete(context, found);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenValidationRegistration_whenPostWithOverride_thenMetadataReplaced() throws Exception {
|
||||
|
||||
Email spy = Mockito.spy(Email.class);
|
||||
doNothing().when(spy).send();
|
||||
|
||||
emailMockedStatic.when(() -> Email.getEmail(any())).thenReturn(spy);
|
||||
|
||||
context.turnOffAuthorisationSystem();
|
||||
RegistrationDataChanges changes =
|
||||
new RegistrationDataChanges("vins-01@fake.mail", RegistrationTypeEnum.VALIDATION_ORCID);
|
||||
RegistrationData validationRegistration =
|
||||
this.accountService.renewRegistrationForEmail(
|
||||
context, new RegistrationDataPatch(orcidRegistration, changes)
|
||||
);
|
||||
context.restoreAuthSystemState();
|
||||
|
||||
try {
|
||||
String customToken = getAuthToken(customEPerson.getEmail(), customPassword);
|
||||
|
||||
getClient(customToken).perform(
|
||||
post("/api/eperson/epersons/" + customEPerson.getID())
|
||||
.param("token", validationRegistration.getToken())
|
||||
.param("override", "eperson.firstname,eperson.lastname")
|
||||
)
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(
|
||||
jsonPath("$.netid", equalTo("0000-0000-0000-0000"))
|
||||
)
|
||||
.andExpect(
|
||||
jsonPath("$.metadata",
|
||||
Matchers.allOf(
|
||||
MetadataMatcher.matchMetadata("eperson.firstname", "Vincenzo"),
|
||||
MetadataMatcher.matchMetadata("eperson.lastname", "Mecca"),
|
||||
MetadataMatcher.matchMetadata("eperson.orcid", "0000-0000-0000-0000")
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
} finally {
|
||||
RegistrationData found = context.reloadEntity(validationRegistration);
|
||||
if (found != null) {
|
||||
this.registrationDataService.delete(context, found);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenValidationRegistration_whenPostWithOverrideAndMetadataNotFound_thenBadRequest() throws Exception {
|
||||
|
||||
Email spy = Mockito.spy(Email.class);
|
||||
doNothing().when(spy).send();
|
||||
|
||||
emailMockedStatic.when(() -> Email.getEmail(any())).thenReturn(spy);
|
||||
|
||||
context.turnOffAuthorisationSystem();
|
||||
RegistrationDataChanges changes =
|
||||
new RegistrationDataChanges("vins-01@fake.mail", RegistrationTypeEnum.VALIDATION_ORCID);
|
||||
RegistrationData validationRegistration =
|
||||
this.accountService.renewRegistrationForEmail(
|
||||
context, new RegistrationDataPatch(orcidRegistration, changes)
|
||||
);
|
||||
|
||||
try {
|
||||
|
||||
context.restoreAuthSystemState();
|
||||
|
||||
String customToken = getAuthToken(customEPerson.getEmail(), customPassword);
|
||||
|
||||
getClient(customToken).perform(
|
||||
post("/api/eperson/epersons/" + customEPerson.getID())
|
||||
.param("token", validationRegistration.getToken())
|
||||
.param("override", "eperson.phone")
|
||||
).andExpect(status().isBadRequest());
|
||||
|
||||
context.turnOffAuthorisationSystem();
|
||||
MetadataField phoneMf =
|
||||
metadataFieldService.findByElement(context, "eperson", "phone", null);
|
||||
|
||||
registrationDataService.addMetadata(
|
||||
context, validationRegistration, phoneMf, "1234567890"
|
||||
);
|
||||
context.restoreAuthSystemState();
|
||||
|
||||
getClient(customToken).perform(
|
||||
post("/api/eperson/epersons/" + customEPerson.getID())
|
||||
.param("token", validationRegistration.getToken())
|
||||
.param("override", "eperson.phone")
|
||||
).andExpect(status().isBadRequest());
|
||||
|
||||
} finally {
|
||||
RegistrationData found = context.reloadEntity(validationRegistration);
|
||||
if (found != null) {
|
||||
this.registrationDataService.delete(context, found);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -36,6 +36,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@@ -65,6 +66,7 @@ import org.dspace.app.rest.model.patch.Operation;
|
||||
import org.dspace.app.rest.model.patch.ReplaceOperation;
|
||||
import org.dspace.app.rest.test.AbstractControllerIntegrationTest;
|
||||
import org.dspace.app.rest.test.MetadataPatchSuite;
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
import org.dspace.builder.CollectionBuilder;
|
||||
import org.dspace.builder.CommunityBuilder;
|
||||
import org.dspace.builder.EPersonBuilder;
|
||||
@@ -72,10 +74,14 @@ import org.dspace.builder.GroupBuilder;
|
||||
import org.dspace.builder.WorkflowItemBuilder;
|
||||
import org.dspace.content.Collection;
|
||||
import org.dspace.content.Community;
|
||||
import org.dspace.content.MetadataField;
|
||||
import org.dspace.content.service.MetadataFieldService;
|
||||
import org.dspace.core.I18nUtil;
|
||||
import org.dspace.eperson.EPerson;
|
||||
import org.dspace.eperson.Group;
|
||||
import org.dspace.eperson.PasswordHash;
|
||||
import org.dspace.eperson.RegistrationData;
|
||||
import org.dspace.eperson.RegistrationTypeEnum;
|
||||
import org.dspace.eperson.service.AccountService;
|
||||
import org.dspace.eperson.service.EPersonService;
|
||||
import org.dspace.eperson.service.GroupService;
|
||||
@@ -102,6 +108,9 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
|
||||
@Autowired
|
||||
private ConfigurationService configurationService;
|
||||
|
||||
@Autowired
|
||||
private MetadataFieldService metadataFieldService;
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper mapper;
|
||||
|
||||
@@ -3176,6 +3185,138 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void postEpersonFromOrcidRegistrationToken() throws Exception {
|
||||
|
||||
context.turnOffAuthorisationSystem();
|
||||
|
||||
String registrationEmail = "vins-01@fake.mail";
|
||||
RegistrationData orcidRegistration =
|
||||
createRegistrationData(RegistrationTypeEnum.ORCID, registrationEmail);
|
||||
|
||||
context.restoreAuthSystemState();
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
EPersonRest ePersonRest = new EPersonRest();
|
||||
MetadataRest metadataRest = new MetadataRest();
|
||||
ePersonRest.setEmail(registrationEmail);
|
||||
ePersonRest.setCanLogIn(true);
|
||||
ePersonRest.setNetid(orcidRegistration.getNetId());
|
||||
MetadataValueRest surname = new MetadataValueRest();
|
||||
surname.setValue("Doe");
|
||||
metadataRest.put("eperson.lastname", surname);
|
||||
MetadataValueRest firstname = new MetadataValueRest();
|
||||
firstname.setValue("John");
|
||||
metadataRest.put("eperson.firstname", firstname);
|
||||
ePersonRest.setMetadata(metadataRest);
|
||||
|
||||
AtomicReference<UUID> idRef = new AtomicReference<UUID>();
|
||||
|
||||
try {
|
||||
getClient().perform(post("/api/eperson/epersons")
|
||||
.param("token", orcidRegistration.getToken())
|
||||
.content(mapper.writeValueAsBytes(ePersonRest))
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isCreated())
|
||||
.andDo(result -> idRef
|
||||
.set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))));
|
||||
} finally {
|
||||
EPersonBuilder.deleteEPerson(idRef.get());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void postEPersonFromOrcidValidationRegistrationToken() throws Exception {
|
||||
|
||||
context.turnOffAuthorisationSystem();
|
||||
|
||||
String registrationEmail = "vins-01@fake.mail";
|
||||
RegistrationData orcidRegistration =
|
||||
createRegistrationData(RegistrationTypeEnum.VALIDATION_ORCID, registrationEmail);
|
||||
|
||||
context.restoreAuthSystemState();
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
EPersonRest ePersonRest = createEPersonRest(registrationEmail, orcidRegistration.getNetId());
|
||||
|
||||
AtomicReference<UUID> idRef = new AtomicReference<>();
|
||||
|
||||
try {
|
||||
getClient().perform(post("/api/eperson/epersons")
|
||||
.param("token", orcidRegistration.getToken())
|
||||
.content(mapper.writeValueAsBytes(ePersonRest))
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$", Matchers.allOf(
|
||||
hasJsonPath("$.uuid", not(empty())),
|
||||
// is it what you expect? EPerson.getName() returns the email...
|
||||
//hasJsonPath("$.name", is("Doe John")),
|
||||
hasJsonPath("$.email", is(registrationEmail)),
|
||||
hasJsonPath("$.type", is("eperson")),
|
||||
hasJsonPath("$.netid", is("0000-0000-0000-0000")),
|
||||
hasJsonPath("$._links.self.href", not(empty())),
|
||||
hasJsonPath("$.metadata", Matchers.allOf(
|
||||
matchMetadata("eperson.firstname", "Vincenzo"),
|
||||
matchMetadata("eperson.lastname", "Mecca"),
|
||||
matchMetadata("eperson.orcid", "0000-0000-0000-0000")
|
||||
)))))
|
||||
.andDo(result -> idRef
|
||||
.set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))));
|
||||
} finally {
|
||||
EPersonBuilder.deleteEPerson(idRef.get());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postEpersonNetIdWithoutPasswordNotExternalRegistrationToken() throws Exception {
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
String newRegisterEmail = "new-register@fake-email.com";
|
||||
RegistrationRest registrationRest = new RegistrationRest();
|
||||
registrationRest.setEmail(newRegisterEmail);
|
||||
registrationRest.setNetId("0000-0000-0000-0000");
|
||||
getClient().perform(post("/api/eperson/registrations")
|
||||
.param(TYPE_QUERY_PARAM, TYPE_REGISTER)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(mapper.writeValueAsBytes(registrationRest)))
|
||||
.andExpect(status().isCreated());
|
||||
|
||||
RegistrationData byEmail = registrationDataService.findByEmail(context, newRegisterEmail);
|
||||
|
||||
String newRegisterToken = byEmail.getToken();
|
||||
|
||||
EPersonRest ePersonRest = new EPersonRest();
|
||||
MetadataRest metadataRest = new MetadataRest();
|
||||
ePersonRest.setEmail(newRegisterEmail);
|
||||
ePersonRest.setCanLogIn(true);
|
||||
ePersonRest.setNetid("0000-0000-0000-0000");
|
||||
MetadataValueRest surname = new MetadataValueRest();
|
||||
surname.setValue("Doe");
|
||||
metadataRest.put("eperson.lastname", surname);
|
||||
MetadataValueRest firstname = new MetadataValueRest();
|
||||
firstname.setValue("John");
|
||||
metadataRest.put("eperson.firstname", firstname);
|
||||
ePersonRest.setMetadata(metadataRest);
|
||||
|
||||
String token = getAuthToken(admin.getEmail(), password);
|
||||
|
||||
try {
|
||||
getClient().perform(post("/api/eperson/epersons")
|
||||
.param("token", newRegisterToken)
|
||||
.content(mapper.writeValueAsBytes(ePersonRest))
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isBadRequest());
|
||||
} finally {
|
||||
context.turnOffAuthorisationSystem();
|
||||
registrationDataService.delete(context, byEmail);
|
||||
context.restoreAuthSystemState();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void findByMetadataByCommAdminAndByColAdminTest() throws Exception {
|
||||
context.turnOffAuthorisationSystem();
|
||||
@@ -3732,4 +3873,51 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
|
||||
|
||||
}
|
||||
|
||||
private static EPersonRest createEPersonRest(String registrationEmail, String netId) {
|
||||
EPersonRest ePersonRest = new EPersonRest();
|
||||
MetadataRest metadataRest = new MetadataRest();
|
||||
ePersonRest.setEmail(registrationEmail);
|
||||
ePersonRest.setCanLogIn(true);
|
||||
ePersonRest.setNetid(netId);
|
||||
MetadataValueRest surname = new MetadataValueRest();
|
||||
surname.setValue("Mecca");
|
||||
metadataRest.put("eperson.lastname", surname);
|
||||
MetadataValueRest firstname = new MetadataValueRest();
|
||||
firstname.setValue("Vincenzo");
|
||||
metadataRest.put("eperson.firstname", firstname);
|
||||
MetadataValueRest orcid = new MetadataValueRest();
|
||||
orcid.setValue("0000-0000-0000-0000");
|
||||
metadataRest.put("eperson.orcid", orcid);
|
||||
ePersonRest.setMetadata(metadataRest);
|
||||
return ePersonRest;
|
||||
}
|
||||
|
||||
private RegistrationData createRegistrationData(RegistrationTypeEnum validationOrcid, String registrationEmail)
|
||||
throws SQLException, AuthorizeException {
|
||||
RegistrationData orcidRegistration =
|
||||
registrationDataService.create(context, "0000-0000-0000-0000", validationOrcid);
|
||||
orcidRegistration.setEmail(registrationEmail);
|
||||
|
||||
MetadataField orcidMf =
|
||||
metadataFieldService.findByElement(context, "eperson", "orcid", null);
|
||||
MetadataField firstNameMf =
|
||||
metadataFieldService.findByElement(context, "eperson", "firstname", null);
|
||||
MetadataField lastNameMf =
|
||||
metadataFieldService.findByElement(context, "eperson", "lastname", null);
|
||||
|
||||
registrationDataService.addMetadata(
|
||||
context, orcidRegistration, orcidMf, "0000-0000-0000-0000"
|
||||
);
|
||||
registrationDataService.addMetadata(
|
||||
context, orcidRegistration, firstNameMf, "Vincenzo"
|
||||
);
|
||||
registrationDataService.addMetadata(
|
||||
context, orcidRegistration, lastNameMf, "Mecca"
|
||||
);
|
||||
|
||||
registrationDataService.update(context, orcidRegistration);
|
||||
return orcidRegistration;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@@ -10,9 +10,11 @@ package org.dspace.app.rest;
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.dspace.app.matcher.MetadataValueMatcher.with;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.emptyString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.hasItem;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
@@ -21,6 +23,7 @@ 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.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.cookie;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
|
||||
@@ -29,11 +32,14 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||
import java.sql.SQLException;
|
||||
import java.text.ParseException;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.jayway.jsonpath.JsonPath;
|
||||
import com.nimbusds.jose.JOSEException;
|
||||
import com.nimbusds.jwt.SignedJWT;
|
||||
import jakarta.servlet.http.Cookie;
|
||||
import org.dspace.app.rest.matcher.MetadataMatcher;
|
||||
import org.dspace.app.rest.model.AuthnRest;
|
||||
import org.dspace.app.rest.security.OrcidLoginFilter;
|
||||
import org.dspace.app.rest.security.jwt.EPersonClaimProvider;
|
||||
@@ -46,14 +52,16 @@ import org.dspace.content.Community;
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.content.service.ItemService;
|
||||
import org.dspace.eperson.EPerson;
|
||||
import org.dspace.eperson.RegistrationTypeEnum;
|
||||
import org.dspace.eperson.service.EPersonService;
|
||||
import org.dspace.eperson.service.RegistrationDataService;
|
||||
import org.dspace.orcid.OrcidToken;
|
||||
import org.dspace.orcid.client.OrcidClient;
|
||||
import org.dspace.orcid.exception.OrcidClientException;
|
||||
import org.dspace.orcid.model.OrcidTokenResponseDTO;
|
||||
import org.dspace.orcid.service.OrcidTokenService;
|
||||
import org.dspace.services.ConfigurationService;
|
||||
import org.dspace.util.UUIDUtils;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -104,6 +112,9 @@ public class OrcidLoginFilterIT extends AbstractControllerIntegrationTest {
|
||||
@Autowired
|
||||
private OrcidTokenService orcidTokenService;
|
||||
|
||||
@Autowired
|
||||
private RegistrationDataService registrationDataService;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
originalOrcidClient = orcidAuthentication.getOrcidClient();
|
||||
@@ -137,45 +148,76 @@ public class OrcidLoginFilterIT extends AbstractControllerIntegrationTest {
|
||||
@Test
|
||||
public void testEPersonCreationViaOrcidLogin() throws Exception {
|
||||
|
||||
when(orcidClientMock.getAccessToken(CODE)).thenReturn(buildOrcidTokenResponse(ORCID, ACCESS_TOKEN));
|
||||
when(orcidClientMock.getPerson(ACCESS_TOKEN, ORCID)).thenReturn(buildPerson("Test", "User", "test@email.it"));
|
||||
String defaultProp = configurationService.getProperty("orcid.registration-data.url");
|
||||
configurationService.setProperty("orcid.registration-data.url", "/test-redirect?random-token={0}");
|
||||
try {
|
||||
when(orcidClientMock.getAccessToken(CODE)).thenReturn(buildOrcidTokenResponse(ORCID, ACCESS_TOKEN));
|
||||
when(orcidClientMock.getPerson(ACCESS_TOKEN, ORCID)).thenReturn(
|
||||
buildPerson("Test", "User", "test@email.it"));
|
||||
|
||||
MvcResult mvcResult = getClient().perform(get("/api/" + AuthnRest.CATEGORY + "/orcid")
|
||||
.param("code", CODE))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andExpect(redirectedUrl(configurationService.getProperty("dspace.ui.url")))
|
||||
.andExpect(cookie().exists("Authorization-cookie"))
|
||||
.andReturn();
|
||||
MvcResult mvcResult =
|
||||
getClient().perform(get("/api/" + AuthnRest.CATEGORY + "/orcid").param("code", CODE))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andReturn();
|
||||
|
||||
verify(orcidClientMock).getAccessToken(CODE);
|
||||
verify(orcidClientMock).getPerson(ACCESS_TOKEN, ORCID);
|
||||
verifyNoMoreInteractions(orcidClientMock);
|
||||
String redirectedUrl = mvcResult.getResponse().getRedirectedUrl();
|
||||
assertThat(redirectedUrl, not(emptyString()));
|
||||
|
||||
String ePersonId = getEPersonIdFromAuthorizationCookie(mvcResult);
|
||||
verify(orcidClientMock).getAccessToken(CODE);
|
||||
verify(orcidClientMock).getPerson(ACCESS_TOKEN, ORCID);
|
||||
verifyNoMoreInteractions(orcidClientMock);
|
||||
|
||||
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(ORCID));
|
||||
assertThat(createdEperson.canLogIn(), equalTo(true));
|
||||
assertThat(createdEperson.getMetadata(), hasItem(with("eperson.orcid", ORCID)));
|
||||
assertThat(createdEperson.getMetadata(), hasItem(with("eperson.orcid.scope", ORCID_SCOPES[0], 0)));
|
||||
assertThat(createdEperson.getMetadata(), hasItem(with("eperson.orcid.scope", ORCID_SCOPES[1], 1)));
|
||||
final Pattern pattern = Pattern.compile("test-redirect\\?random-token=([a-zA-Z0-9]+)");
|
||||
final Matcher matcher = pattern.matcher(redirectedUrl);
|
||||
matcher.find();
|
||||
|
||||
assertThat(getOrcidAccessToken(createdEperson), is(ACCESS_TOKEN));
|
||||
assertThat(matcher.groupCount(), is(1));
|
||||
assertThat(matcher.group(1), not(emptyString()));
|
||||
|
||||
String rdToken = matcher.group(1);
|
||||
|
||||
getClient().perform(get("/api/eperson/registrations/search/findByToken")
|
||||
.param("token", rdToken))
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpect(content().contentType(contentType))
|
||||
.andExpect(jsonPath("$.netId", equalTo(ORCID)))
|
||||
.andExpect(jsonPath("$.registrationType", equalTo(RegistrationTypeEnum.ORCID.toString())))
|
||||
.andExpect(jsonPath("$.email", equalTo("test@email.it")))
|
||||
.andExpect(
|
||||
jsonPath("$.registrationMetadata",
|
||||
Matchers.allOf(
|
||||
MetadataMatcher.matchMetadata("eperson.orcid", ORCID),
|
||||
MetadataMatcher.matchMetadata("eperson.firstname", "Test"),
|
||||
MetadataMatcher.matchMetadata("eperson.lastname", "User")
|
||||
)
|
||||
)
|
||||
);
|
||||
} finally {
|
||||
configurationService.setProperty("orcid.registration-data.url", defaultProp);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEPersonCreationViaOrcidLoginWithoutEmail() throws Exception {
|
||||
public void testRedirectiViaOrcidLoginWithoutEmail() throws Exception {
|
||||
|
||||
when(orcidClientMock.getAccessToken(CODE)).thenReturn(buildOrcidTokenResponse(ORCID, ACCESS_TOKEN));
|
||||
when(orcidClientMock.getPerson(ACCESS_TOKEN, ORCID)).thenReturn(buildPerson("Test", "User"));
|
||||
|
||||
getClient().perform(get("/api/" + AuthnRest.CATEGORY + "/orcid")
|
||||
.param("code", CODE))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andExpect(redirectedUrl("http://localhost:4000/error?status=401&code=orcid.generic-error"));
|
||||
MvcResult orcidLogin =
|
||||
getClient().perform(get("/api/" + AuthnRest.CATEGORY + "/orcid").param("code", CODE))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andReturn();
|
||||
|
||||
String redirectedUrl = orcidLogin.getResponse().getRedirectedUrl();
|
||||
|
||||
assertThat(redirectedUrl, notNullValue());
|
||||
|
||||
final Pattern pattern = Pattern.compile("external-login/([a-zA-Z0-9]+)");
|
||||
final Matcher matcher = pattern.matcher(redirectedUrl);
|
||||
matcher.find();
|
||||
|
||||
assertThat(matcher.groupCount(), is(1));
|
||||
assertThat(matcher.group(1), not(emptyString()));
|
||||
|
||||
verify(orcidClientMock).getAccessToken(CODE);
|
||||
verify(orcidClientMock).getPerson(ACCESS_TOKEN, ORCID);
|
||||
|
@@ -7,21 +7,32 @@
|
||||
*/
|
||||
package org.dspace.app.rest;
|
||||
|
||||
import static org.dspace.app.rest.repository.RegistrationRestRepository.TOKEN_QUERY_PARAM;
|
||||
import static org.dspace.app.rest.repository.RegistrationRestRepository.TYPE_FORGOT;
|
||||
import static org.dspace.app.rest.repository.RegistrationRestRepository.TYPE_QUERY_PARAM;
|
||||
import static org.dspace.app.rest.repository.RegistrationRestRepository.TYPE_REGISTER;
|
||||
import static org.hamcrest.Matchers.emptyOrNullString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
@@ -30,17 +41,30 @@ import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dspace.app.rest.matcher.RegistrationMatcher;
|
||||
import org.dspace.app.rest.model.RegistrationRest;
|
||||
import org.dspace.app.rest.model.patch.AddOperation;
|
||||
import org.dspace.app.rest.model.patch.ReplaceOperation;
|
||||
import org.dspace.app.rest.repository.RegistrationRestRepository;
|
||||
import org.dspace.app.rest.test.AbstractControllerIntegrationTest;
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
import org.dspace.builder.EPersonBuilder;
|
||||
import org.dspace.core.Email;
|
||||
import org.dspace.eperson.CaptchaServiceImpl;
|
||||
import org.dspace.eperson.EPerson;
|
||||
import org.dspace.eperson.InvalidReCaptchaException;
|
||||
import org.dspace.eperson.RegistrationData;
|
||||
import org.dspace.eperson.RegistrationTypeEnum;
|
||||
import org.dspace.eperson.dao.RegistrationDataDAO;
|
||||
import org.dspace.eperson.service.CaptchaService;
|
||||
import org.dspace.eperson.service.RegistrationDataService;
|
||||
import org.dspace.services.ConfigurationService;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
public class RegistrationRestRepositoryIT extends AbstractControllerIntegrationTest {
|
||||
@@ -50,12 +74,35 @@ public class RegistrationRestRepositoryIT extends AbstractControllerIntegrationT
|
||||
@Autowired
|
||||
private RegistrationDataDAO registrationDataDAO;
|
||||
@Autowired
|
||||
private RegistrationDataService registrationDataService;
|
||||
@Autowired
|
||||
private ConfigurationService configurationService;
|
||||
@Autowired
|
||||
private RegistrationRestRepository registrationRestRepository;
|
||||
@Autowired
|
||||
private ObjectMapper mapper;
|
||||
|
||||
private static MockedStatic<Email> emailMockedStatic;
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
Iterator<RegistrationData> iterator = registrationDataDAO.findAll(context, RegistrationData.class).iterator();
|
||||
while (iterator.hasNext()) {
|
||||
RegistrationData registrationData = iterator.next();
|
||||
registrationDataDAO.delete(context, registrationData);
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void init() throws Exception {
|
||||
emailMockedStatic = Mockito.mockStatic(Email.class);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDownClass() throws Exception {
|
||||
emailMockedStatic.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findByTokenTestExistingUserTest() throws Exception {
|
||||
String email = eperson.getEmail();
|
||||
@@ -462,4 +509,507 @@ public class RegistrationRestRepositoryIT extends AbstractControllerIntegrationT
|
||||
.andExpect(status().isBadRequest());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenRegistrationData_whenPatchInvalidValue_thenUnprocessableEntityResponse()
|
||||
throws Exception {
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
RegistrationRest registrationRest = new RegistrationRest();
|
||||
registrationRest.setEmail(eperson.getEmail());
|
||||
registrationRest.setUser(eperson.getID());
|
||||
|
||||
Email spy = Mockito.spy(Email.class);
|
||||
doNothing().when(spy).send();
|
||||
|
||||
emailMockedStatic.when(() -> Email.getEmail(any())).thenReturn(spy);
|
||||
|
||||
// given RegistrationData with email
|
||||
getClient().perform(post("/api/eperson/registrations")
|
||||
.param(TYPE_QUERY_PARAM, TYPE_REGISTER)
|
||||
.content(mapper.writeValueAsBytes(registrationRest))
|
||||
.contentType(contentType))
|
||||
.andExpect(status().isCreated());
|
||||
|
||||
RegistrationData registrationData =
|
||||
registrationDataService.findByEmail(context, registrationRest.getEmail());
|
||||
|
||||
assertThat(registrationData, notNullValue());
|
||||
assertThat(registrationData.getToken(), not(emptyOrNullString()));
|
||||
|
||||
String token = registrationData.getToken();
|
||||
String newMail = null;
|
||||
String patchContent = getPatchContent(
|
||||
List.of(new ReplaceOperation("/email", newMail))
|
||||
);
|
||||
|
||||
// when patch for replace email
|
||||
getClient().perform(patch("/api/eperson/registrations/" + registrationData.getID())
|
||||
.param(TOKEN_QUERY_PARAM, token)
|
||||
.content(patchContent)
|
||||
.contentType(contentType))
|
||||
// then succesful response returned
|
||||
.andExpect(status().isBadRequest());
|
||||
|
||||
newMail = "test@email.com";
|
||||
patchContent = getPatchContent(
|
||||
List.of(new AddOperation("/email", newMail))
|
||||
);
|
||||
|
||||
getClient().perform(patch("/api/eperson/registrations/" + registrationData.getID())
|
||||
.param(TOKEN_QUERY_PARAM, token)
|
||||
.content(patchContent)
|
||||
.contentType(contentType))
|
||||
// then succesful response returned
|
||||
.andExpect(status().isUnprocessableEntity());
|
||||
|
||||
newMail = "invalidemail!!!!";
|
||||
patchContent = getPatchContent(
|
||||
List.of(new ReplaceOperation("/email", newMail))
|
||||
);
|
||||
|
||||
getClient().perform(patch("/api/eperson/registrations/" + registrationData.getID())
|
||||
.param(TOKEN_QUERY_PARAM, token)
|
||||
.content(patchContent)
|
||||
.contentType(contentType))
|
||||
// then succesful response returned
|
||||
.andExpect(status().isUnprocessableEntity());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenRegistrationData_whenPatchWithInvalidToken_thenUnprocessableEntityResponse()
|
||||
throws Exception {
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
RegistrationRest registrationRest = new RegistrationRest();
|
||||
registrationRest.setEmail(eperson.getEmail());
|
||||
registrationRest.setUser(eperson.getID());
|
||||
|
||||
Email spy = Mockito.spy(Email.class);
|
||||
doNothing().when(spy).send();
|
||||
|
||||
emailMockedStatic.when(() -> Email.getEmail(any())).thenReturn(spy);
|
||||
|
||||
// given RegistrationData with email
|
||||
getClient().perform(post("/api/eperson/registrations")
|
||||
.param(TYPE_QUERY_PARAM, TYPE_REGISTER)
|
||||
.content(mapper.writeValueAsBytes(registrationRest))
|
||||
.contentType(contentType))
|
||||
.andExpect(status().isCreated());
|
||||
|
||||
RegistrationData registrationData =
|
||||
registrationDataService.findByEmail(context, registrationRest.getEmail());
|
||||
|
||||
|
||||
assertThat(registrationData, notNullValue());
|
||||
assertThat(registrationData.getToken(), not(emptyOrNullString()));
|
||||
|
||||
String token = null;
|
||||
String newMail = "validemail@email.com";
|
||||
String patchContent = getPatchContent(
|
||||
List.of(new ReplaceOperation("/email", newMail))
|
||||
);
|
||||
|
||||
// when patch for replace email
|
||||
getClient().perform(patch("/api/eperson/registrations/" + registrationData.getID())
|
||||
.param(TOKEN_QUERY_PARAM, token)
|
||||
.content(patchContent)
|
||||
.contentType(contentType))
|
||||
// then succesful response returned
|
||||
.andExpect(status().isUnauthorized());
|
||||
|
||||
token = "notexistingtoken";
|
||||
|
||||
// when patch for replace email
|
||||
getClient().perform(patch("/api/eperson/registrations/" + registrationData.getID())
|
||||
.param(TOKEN_QUERY_PARAM, token)
|
||||
.content(patchContent)
|
||||
.contentType(contentType))
|
||||
// then succesful response returned
|
||||
.andExpect(status().isUnauthorized());
|
||||
|
||||
context.turnOffAuthorisationSystem();
|
||||
registrationData = context.reloadEntity(registrationData);
|
||||
registrationDataService.markAsExpired(context, registrationData);
|
||||
context.commit();
|
||||
context.restoreAuthSystemState();
|
||||
|
||||
registrationData = context.reloadEntity(registrationData);
|
||||
|
||||
assertThat(registrationData.getExpires(), notNullValue());
|
||||
|
||||
token = registrationData.getToken();
|
||||
newMail = "validemail@email.com";
|
||||
patchContent = getPatchContent(
|
||||
List.of(new ReplaceOperation("/email", newMail))
|
||||
);
|
||||
|
||||
// when patch for replace email
|
||||
getClient().perform(patch("/api/eperson/registrations/" + registrationData.getID())
|
||||
.param(TOKEN_QUERY_PARAM, token)
|
||||
.content(patchContent)
|
||||
.contentType(contentType))
|
||||
// then succesful response returned
|
||||
.andExpect(status().isUnauthorized());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenRegistrationDataWithEmail_whenPatchForReplaceEmail_thenSuccessfullResponse()
|
||||
throws Exception {
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
RegistrationRest registrationRest = new RegistrationRest();
|
||||
registrationRest.setEmail(eperson.getEmail());
|
||||
registrationRest.setUser(eperson.getID());
|
||||
|
||||
// given RegistrationData with email
|
||||
getClient().perform(post("/api/eperson/registrations")
|
||||
.param(TYPE_QUERY_PARAM, TYPE_REGISTER)
|
||||
.content(mapper.writeValueAsBytes(registrationRest))
|
||||
.contentType(contentType))
|
||||
.andExpect(status().isCreated());
|
||||
|
||||
RegistrationData registrationData =
|
||||
registrationDataService.findByEmail(context, registrationRest.getEmail());
|
||||
|
||||
assertThat(registrationData, notNullValue());
|
||||
assertThat(registrationData.getToken(), not(emptyOrNullString()));
|
||||
|
||||
String token = registrationData.getToken();
|
||||
String newMail = "vins-01@fake.mail";
|
||||
String patchContent = getPatchContent(
|
||||
List.of(new ReplaceOperation("/email", newMail))
|
||||
);
|
||||
|
||||
// when patch for replace email
|
||||
getClient().perform(patch("/api/eperson/registrations/" + registrationData.getID())
|
||||
.param(TOKEN_QUERY_PARAM, token)
|
||||
.content(patchContent)
|
||||
.contentType(contentType))
|
||||
// then succesful response returned
|
||||
.andExpect(status().is2xxSuccessful());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenRegistrationDataWithoutEmail_whenPatchForAddEmail_thenSuccessfullResponse()
|
||||
throws Exception {
|
||||
|
||||
RegistrationData registrationData =
|
||||
createNewRegistrationData("0000-1111-2222-3333", RegistrationTypeEnum.ORCID);
|
||||
|
||||
assertThat(registrationData, notNullValue());
|
||||
assertThat(registrationData.getToken(), not(emptyOrNullString()));
|
||||
|
||||
String token = registrationData.getToken();
|
||||
String newMail = "vins-01@fake.mail";
|
||||
String patchContent = getPatchContent(
|
||||
List.of(new AddOperation("/email", newMail))
|
||||
);
|
||||
|
||||
// when patch for replace email
|
||||
getClient().perform(patch("/api/eperson/registrations/" + registrationData.getID())
|
||||
.param(TOKEN_QUERY_PARAM, token)
|
||||
.content(patchContent)
|
||||
.contentType(contentType))
|
||||
// then succesful response returned
|
||||
.andExpect(status().is2xxSuccessful());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenRegistrationDataWithEmail_whenPatchForReplaceEmail_thenNewRegistrationDataCreated()
|
||||
throws Exception {
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
RegistrationRest registrationRest = new RegistrationRest();
|
||||
registrationRest.setEmail(eperson.getEmail());
|
||||
registrationRest.setUser(eperson.getID());
|
||||
|
||||
// given RegistrationData with email
|
||||
getClient().perform(post("/api/eperson/registrations")
|
||||
.param(TYPE_QUERY_PARAM, TYPE_REGISTER)
|
||||
.content(mapper.writeValueAsBytes(registrationRest))
|
||||
.contentType(contentType))
|
||||
.andExpect(status().isCreated());
|
||||
|
||||
RegistrationData registrationData =
|
||||
registrationDataService.findByEmail(context, registrationRest.getEmail());
|
||||
|
||||
assertThat(registrationData, notNullValue());
|
||||
assertThat(registrationData.getToken(), not(emptyOrNullString()));
|
||||
|
||||
String token = registrationData.getToken();
|
||||
String newMail = "vins-01@fake.mail";
|
||||
String patchContent = getPatchContent(
|
||||
List.of(new ReplaceOperation("/email", newMail))
|
||||
);
|
||||
|
||||
// when patch for replace email
|
||||
getClient().perform(patch("/api/eperson/registrations/" + registrationData.getID())
|
||||
.param(TOKEN_QUERY_PARAM, token)
|
||||
.content(patchContent)
|
||||
.contentType(contentType))
|
||||
.andExpect(status().is2xxSuccessful());
|
||||
|
||||
// then email updated with new registration
|
||||
RegistrationData newRegistration = registrationDataService.findByEmail(context, newMail);
|
||||
assertThat(newRegistration, notNullValue());
|
||||
assertThat(newRegistration.getToken(), not(emptyOrNullString()));
|
||||
assertThat(newRegistration.getEmail(), equalTo(newMail));
|
||||
|
||||
assertThat(newRegistration.getEmail(), not(equalTo(registrationData.getEmail())));
|
||||
assertThat(newRegistration.getToken(), not(equalTo(registrationData.getToken())));
|
||||
|
||||
registrationData = context.reloadEntity(registrationData);
|
||||
assertThat(registrationData, nullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenRegistrationDataWithoutEmail_whenPatchForReplaceEmail_thenNewRegistrationDataCreated()
|
||||
throws Exception {
|
||||
RegistrationData registrationData =
|
||||
createNewRegistrationData("0000-1111-2222-3333", RegistrationTypeEnum.ORCID);
|
||||
|
||||
assertThat(registrationData.getToken(), not(emptyOrNullString()));
|
||||
|
||||
String token = registrationData.getToken();
|
||||
String newMail = "vins-01@fake.mail";
|
||||
String patchContent = getPatchContent(
|
||||
List.of(new AddOperation("/email", newMail))
|
||||
);
|
||||
|
||||
// when patch for replace email
|
||||
getClient().perform(patch("/api/eperson/registrations/" + registrationData.getID())
|
||||
.param(TOKEN_QUERY_PARAM, token)
|
||||
.content(patchContent)
|
||||
.contentType(contentType))
|
||||
.andExpect(status().is2xxSuccessful());
|
||||
|
||||
// then email updated with new registration
|
||||
RegistrationData newRegistration = registrationDataService.findByEmail(context, newMail);
|
||||
assertThat(newRegistration, notNullValue());
|
||||
assertThat(newRegistration.getToken(), not(emptyOrNullString()));
|
||||
assertThat(newRegistration.getEmail(), equalTo(newMail));
|
||||
|
||||
assertThat(newRegistration.getEmail(), not(equalTo(registrationData.getEmail())));
|
||||
assertThat(newRegistration.getToken(), not(equalTo(registrationData.getToken())));
|
||||
|
||||
registrationData = context.reloadEntity(registrationData);
|
||||
assertThat(registrationData, nullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenRegistrationDataWithoutEmail_whenPatchForAddEmail_thenExternalLoginSent() throws Exception {
|
||||
RegistrationData registrationData =
|
||||
createNewRegistrationData("0000-1111-2222-3333", RegistrationTypeEnum.ORCID);
|
||||
|
||||
assertThat(registrationData, notNullValue());
|
||||
assertThat(registrationData.getToken(), not(emptyOrNullString()));
|
||||
|
||||
String token = registrationData.getToken();
|
||||
String newMail = "vins-01@fake.mail";
|
||||
String patchContent = getPatchContent(
|
||||
List.of(new AddOperation("/email", newMail))
|
||||
);
|
||||
|
||||
Email spy = Mockito.spy(Email.class);
|
||||
doNothing().when(spy).send();
|
||||
|
||||
emailMockedStatic.when(() -> Email.getEmail(any())).thenReturn(spy);
|
||||
|
||||
// when patch for replace email
|
||||
getClient().perform(patch("/api/eperson/registrations/" + registrationData.getID())
|
||||
.param(TOKEN_QUERY_PARAM, token)
|
||||
.content(patchContent)
|
||||
.contentType(contentType))
|
||||
.andExpect(status().is2xxSuccessful());
|
||||
|
||||
// then verification email sent
|
||||
verify(spy, times(1)).addRecipient(newMail);
|
||||
verify(spy).addArgument(
|
||||
ArgumentMatchers.contains(
|
||||
RegistrationTypeEnum.ORCID.getLink()
|
||||
)
|
||||
);
|
||||
verify(spy, times(1)).send();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenRegistrationDataWithEmail_whenPatchForNewEmail_thenExternalLoginSent() throws Exception {
|
||||
RegistrationData registrationData =
|
||||
createNewRegistrationData("0000-1111-2222-3333", RegistrationTypeEnum.ORCID);
|
||||
|
||||
String token = registrationData.getToken();
|
||||
String newMail = "vincenzo.mecca@orcid.com";
|
||||
String patchContent = getPatchContent(
|
||||
List.of(new AddOperation("/email", newMail))
|
||||
);
|
||||
|
||||
Email spy = Mockito.spy(Email.class);
|
||||
doNothing().when(spy).send();
|
||||
|
||||
emailMockedStatic.when(() -> Email.getEmail(any())).thenReturn(spy);
|
||||
|
||||
// when patch for replace email
|
||||
getClient().perform(patch("/api/eperson/registrations/" + registrationData.getID())
|
||||
.param(TOKEN_QUERY_PARAM, token)
|
||||
.content(patchContent)
|
||||
.contentType(contentType))
|
||||
.andExpect(status().is2xxSuccessful());
|
||||
|
||||
verify(spy, times(1)).addRecipient(newMail);
|
||||
verify(spy).addArgument(
|
||||
ArgumentMatchers.contains(
|
||||
registrationData.getRegistrationType().getLink()
|
||||
)
|
||||
);
|
||||
verify(spy, times(1)).send();
|
||||
|
||||
registrationData = registrationDataService.findByEmail(context, newMail);
|
||||
|
||||
assertThat(registrationData, notNullValue());
|
||||
assertThat(registrationData.getToken(), not(emptyOrNullString()));
|
||||
|
||||
token = registrationData.getToken();
|
||||
newMail = "vins-01@fake.mail";
|
||||
patchContent = getPatchContent(
|
||||
List.of(new ReplaceOperation("/email", newMail))
|
||||
);
|
||||
|
||||
spy = Mockito.spy(Email.class);
|
||||
doNothing().when(spy).send();
|
||||
|
||||
emailMockedStatic.when(() -> Email.getEmail(any())).thenReturn(spy);
|
||||
|
||||
// when patch for replace email
|
||||
getClient().perform(patch("/api/eperson/registrations/" + registrationData.getID())
|
||||
.param(TOKEN_QUERY_PARAM, token)
|
||||
.content(patchContent)
|
||||
.contentType(contentType))
|
||||
.andExpect(status().is2xxSuccessful());
|
||||
|
||||
// then verification email sent
|
||||
verify(spy, times(1)).addRecipient(newMail);
|
||||
verify(spy).addArgument(
|
||||
ArgumentMatchers.contains(
|
||||
registrationData.getRegistrationType().getLink()
|
||||
)
|
||||
);
|
||||
verify(spy, times(1)).send();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenRegistrationDataWithEmail_whenPatchForExistingEPersonEmail_thenReviewAccountLinkSent()
|
||||
throws Exception {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
RegistrationRest registrationRest = new RegistrationRest();
|
||||
registrationRest.setEmail(eperson.getEmail());
|
||||
registrationRest.setNetId("0000-0000-0000-0000");
|
||||
|
||||
// given RegistrationData with email
|
||||
getClient().perform(post("/api/eperson/registrations")
|
||||
.param(TYPE_QUERY_PARAM, TYPE_REGISTER)
|
||||
.content(mapper.writeValueAsBytes(registrationRest))
|
||||
.contentType(contentType))
|
||||
.andExpect(status().isCreated());
|
||||
|
||||
RegistrationData registrationData =
|
||||
registrationDataService.findByEmail(context, registrationRest.getEmail());
|
||||
|
||||
assertThat(registrationData, notNullValue());
|
||||
assertThat(registrationData.getToken(), not(emptyOrNullString()));
|
||||
|
||||
context.turnOffAuthorisationSystem();
|
||||
final EPerson vins =
|
||||
EPersonBuilder.createEPerson(context)
|
||||
.withEmail("vins-01@fake.mail")
|
||||
.withNameInMetadata("Vincenzo", "Mecca")
|
||||
.withOrcid("0101-0101-0101-0101")
|
||||
.build();
|
||||
context.restoreAuthSystemState();
|
||||
|
||||
String token = registrationData.getToken();
|
||||
String vinsEmail = vins.getEmail();
|
||||
String patchContent = getPatchContent(
|
||||
List.of(new ReplaceOperation("/email", vins.getEmail()))
|
||||
);
|
||||
|
||||
Email spy = Mockito.spy(Email.class);
|
||||
doNothing().when(spy).send();
|
||||
|
||||
emailMockedStatic.when(() -> Email.getEmail(any())).thenReturn(spy);
|
||||
|
||||
// when patch for replace email
|
||||
getClient().perform(patch("/api/eperson/registrations/" + registrationData.getID())
|
||||
.param(TOKEN_QUERY_PARAM, token)
|
||||
.content(patchContent)
|
||||
.contentType(contentType))
|
||||
.andExpect(status().is2xxSuccessful());
|
||||
|
||||
// then verification email sent
|
||||
verify(spy, times(1)).addRecipient(vinsEmail);
|
||||
verify(spy).addArgument(
|
||||
ArgumentMatchers.contains(
|
||||
RegistrationTypeEnum.VALIDATION_ORCID.getLink()
|
||||
)
|
||||
);
|
||||
verify(spy, times(1)).send();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenRegistrationDataWithoutEmail_whenPatchForExistingAccount_thenReviewAccountSent() throws Exception {
|
||||
RegistrationData registrationData =
|
||||
createNewRegistrationData("0000-1111-2222-3333", RegistrationTypeEnum.ORCID);
|
||||
|
||||
assertThat(registrationData, notNullValue());
|
||||
assertThat(registrationData.getToken(), not(emptyOrNullString()));
|
||||
|
||||
context.turnOffAuthorisationSystem();
|
||||
final EPerson vins =
|
||||
EPersonBuilder.createEPerson(context)
|
||||
.withEmail("vins-01@fake.mail")
|
||||
.withNameInMetadata("Vincenzo", "Mecca")
|
||||
.withOrcid("0101-0101-0101-0101")
|
||||
.build();
|
||||
context.commit();
|
||||
context.restoreAuthSystemState();
|
||||
|
||||
String token = registrationData.getToken();
|
||||
String vinsEmail = vins.getEmail();
|
||||
String patchContent = getPatchContent(
|
||||
List.of(new AddOperation("/email", vins.getEmail()))
|
||||
);
|
||||
|
||||
Email spy = Mockito.spy(Email.class);
|
||||
doNothing().when(spy).send();
|
||||
|
||||
emailMockedStatic.when(() -> Email.getEmail(any())).thenReturn(spy);
|
||||
|
||||
// when patch for replace email
|
||||
getClient().perform(patch("/api/eperson/registrations/" + registrationData.getID())
|
||||
.param(TOKEN_QUERY_PARAM, token)
|
||||
.content(patchContent)
|
||||
.contentType(contentType))
|
||||
.andExpect(status().is2xxSuccessful());
|
||||
|
||||
// then verification email sent
|
||||
verify(spy, times(1)).addRecipient(vinsEmail);
|
||||
verify(spy).addArgument(
|
||||
ArgumentMatchers.contains(
|
||||
RegistrationTypeEnum.VALIDATION_ORCID.getLink()
|
||||
)
|
||||
);
|
||||
verify(spy, times(1)).send();
|
||||
}
|
||||
|
||||
|
||||
private RegistrationData createNewRegistrationData(
|
||||
String netId, RegistrationTypeEnum type
|
||||
) throws SQLException, AuthorizeException {
|
||||
context.turnOffAuthorisationSystem();
|
||||
RegistrationData registrationData =
|
||||
registrationDataService.create(context, netId, type);
|
||||
context.commit();
|
||||
context.restoreAuthSystemState();
|
||||
return registrationData;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1747,14 +1747,15 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra
|
||||
context.turnOffAuthorisationSystem();
|
||||
|
||||
EPerson ePerson = EPersonBuilder.createEPerson(context)
|
||||
.withCanLogin(true)
|
||||
.withOrcid("0000-1111-2222-3333")
|
||||
.withOrcidScope("/read")
|
||||
.withOrcidScope("/write")
|
||||
.withEmail("test@email.it")
|
||||
.withPassword(password)
|
||||
.withNameInMetadata("Test", "User")
|
||||
.build();
|
||||
.withCanLogin(true)
|
||||
.withOrcid("0000-1111-2222-3333")
|
||||
.withNetId("0000-1111-2222-3333")
|
||||
.withOrcidScope("/read")
|
||||
.withOrcidScope("/write")
|
||||
.withEmail("test@email.it")
|
||||
.withPassword(password)
|
||||
.withNameInMetadata("Test", "User")
|
||||
.build();
|
||||
|
||||
OrcidTokenBuilder.create(context, ePerson, "3de2e370-8aa9-4bbe-8d7e-f5b1577bdad4").build();
|
||||
|
||||
@@ -1774,7 +1775,9 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra
|
||||
.andExpect(status().isForbidden());
|
||||
|
||||
profile = context.reloadEntity(profile);
|
||||
ePerson = context.reloadEntity(ePerson);
|
||||
|
||||
assertThat(ePerson.getNetid(), notNullValue());
|
||||
assertThat(getMetadataValues(profile, "person.identifier.orcid"), not(empty()));
|
||||
assertThat(getMetadataValues(profile, "dspace.orcid.scope"), not(empty()));
|
||||
assertThat(getMetadataValues(profile, "dspace.orcid.authenticated"), not(empty()));
|
||||
@@ -1789,14 +1792,15 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra
|
||||
context.turnOffAuthorisationSystem();
|
||||
|
||||
EPerson ePerson = EPersonBuilder.createEPerson(context)
|
||||
.withCanLogin(true)
|
||||
.withOrcid("0000-1111-2222-3333")
|
||||
.withOrcidScope("/read")
|
||||
.withOrcidScope("/write")
|
||||
.withEmail("test@email.it")
|
||||
.withPassword(password)
|
||||
.withNameInMetadata("Test", "User")
|
||||
.build();
|
||||
.withCanLogin(true)
|
||||
.withOrcid("0000-1111-2222-3333")
|
||||
.withNetId("0000-1111-2222-3333")
|
||||
.withOrcidScope("/read")
|
||||
.withOrcidScope("/write")
|
||||
.withEmail("test@email.it")
|
||||
.withPassword(password)
|
||||
.withNameInMetadata("Test", "User")
|
||||
.build();
|
||||
|
||||
OrcidTokenBuilder.create(context, ePerson, "3de2e370-8aa9-4bbe-8d7e-f5b1577bdad4").build();
|
||||
|
||||
@@ -1816,7 +1820,9 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra
|
||||
.andExpect(status().isForbidden());
|
||||
|
||||
profile = context.reloadEntity(profile);
|
||||
ePerson = context.reloadEntity(ePerson);
|
||||
|
||||
assertThat(ePerson.getNetid(), notNullValue());
|
||||
assertThat(getMetadataValues(profile, "person.identifier.orcid"), not(empty()));
|
||||
assertThat(getMetadataValues(profile, "dspace.orcid.scope"), not(empty()));
|
||||
assertThat(getMetadataValues(profile, "dspace.orcid.authenticated"), not(empty()));
|
||||
@@ -1831,14 +1837,15 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra
|
||||
context.turnOffAuthorisationSystem();
|
||||
|
||||
EPerson ePerson = EPersonBuilder.createEPerson(context)
|
||||
.withCanLogin(true)
|
||||
.withOrcid("0000-1111-2222-3333")
|
||||
.withOrcidScope("/read")
|
||||
.withOrcidScope("/write")
|
||||
.withEmail("test@email.it")
|
||||
.withPassword(password)
|
||||
.withNameInMetadata("Test", "User")
|
||||
.build();
|
||||
.withCanLogin(true)
|
||||
.withOrcid("0000-1111-2222-3333")
|
||||
.withNetId("0000-1111-2222-3333")
|
||||
.withOrcidScope("/read")
|
||||
.withOrcidScope("/write")
|
||||
.withEmail("test@email.it")
|
||||
.withPassword(password)
|
||||
.withNameInMetadata("Test", "User")
|
||||
.build();
|
||||
|
||||
OrcidTokenBuilder.create(context, ePerson, "3de2e370-8aa9-4bbe-8d7e-f5b1577bdad4").build();
|
||||
|
||||
@@ -1865,7 +1872,9 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra
|
||||
.andExpect(status().isForbidden());
|
||||
|
||||
profile = context.reloadEntity(profile);
|
||||
ePerson = context.reloadEntity(ePerson);
|
||||
|
||||
assertThat(ePerson.getNetid(), notNullValue());
|
||||
assertThat(getMetadataValues(profile, "person.identifier.orcid"), not(empty()));
|
||||
assertThat(getMetadataValues(profile, "dspace.orcid.scope"), not(empty()));
|
||||
assertThat(getMetadataValues(profile, "dspace.orcid.authenticated"), not(empty()));
|
||||
@@ -1968,7 +1977,9 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra
|
||||
verifyNoMoreInteractions(orcidClientMock);
|
||||
|
||||
profile = context.reloadEntity(profile);
|
||||
eperson = context.reloadEntity(eperson);
|
||||
|
||||
assertThat(eperson.getNetid(), nullValue());
|
||||
assertThat(getMetadataValues(profile, "person.identifier.orcid"), empty());
|
||||
assertThat(getMetadataValues(profile, "dspace.orcid.scope"), empty());
|
||||
assertThat(getMetadataValues(profile, "dspace.orcid.authenticated"), empty());
|
||||
@@ -2058,7 +2069,9 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra
|
||||
.andExpect(status().isForbidden());
|
||||
|
||||
profile = context.reloadEntity(profile);
|
||||
eperson = context.reloadEntity(eperson);
|
||||
|
||||
assertThat(eperson.getNetid(), nullValue());
|
||||
assertThat(getMetadataValues(profile, "person.identifier.orcid"), not(empty()));
|
||||
assertThat(getMetadataValues(profile, "dspace.orcid.scope"), not(empty()));
|
||||
assertThat(getMetadataValues(profile, "dspace.orcid.authenticated"), not(empty()));
|
||||
@@ -2073,14 +2086,15 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra
|
||||
context.turnOffAuthorisationSystem();
|
||||
|
||||
EPerson ePerson = EPersonBuilder.createEPerson(context)
|
||||
.withCanLogin(true)
|
||||
.withOrcid("0000-1111-2222-3333")
|
||||
.withOrcidScope("/read")
|
||||
.withOrcidScope("/write")
|
||||
.withEmail("test@email.it")
|
||||
.withPassword(password)
|
||||
.withNameInMetadata("Test", "User")
|
||||
.build();
|
||||
.withCanLogin(true)
|
||||
.withOrcid("0000-1111-2222-3333")
|
||||
.withNetId("0000-1111-2222-3333")
|
||||
.withOrcidScope("/read")
|
||||
.withOrcidScope("/write")
|
||||
.withEmail("test@email.it")
|
||||
.withPassword(password)
|
||||
.withNameInMetadata("Test", "User")
|
||||
.build();
|
||||
|
||||
OrcidTokenBuilder.create(context, ePerson, "3de2e370-8aa9-4bbe-8d7e-f5b1577bdad4").build();
|
||||
|
||||
@@ -2100,7 +2114,9 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra
|
||||
.andExpect(status().isForbidden());
|
||||
|
||||
profile = context.reloadEntity(profile);
|
||||
ePerson = context.reloadEntity(ePerson);
|
||||
|
||||
assertThat(ePerson.getNetid(), notNullValue());
|
||||
assertThat(getMetadataValues(profile, "person.identifier.orcid"), not(empty()));
|
||||
assertThat(getMetadataValues(profile, "dspace.orcid.scope"), not(empty()));
|
||||
assertThat(getMetadataValues(profile, "dspace.orcid.authenticated"), not(empty()));
|
||||
@@ -2115,14 +2131,15 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra
|
||||
context.turnOffAuthorisationSystem();
|
||||
|
||||
EPerson ePerson = EPersonBuilder.createEPerson(context)
|
||||
.withCanLogin(true)
|
||||
.withOrcid("0000-1111-2222-3333")
|
||||
.withOrcidScope("/read")
|
||||
.withOrcidScope("/write")
|
||||
.withEmail("test@email.it")
|
||||
.withPassword(password)
|
||||
.withNameInMetadata("Test", "User")
|
||||
.build();
|
||||
.withCanLogin(true)
|
||||
.withOrcid("0000-1111-2222-3333")
|
||||
.withNetId("0000-1111-2222-3333")
|
||||
.withOrcidScope("/read")
|
||||
.withOrcidScope("/write")
|
||||
.withEmail("test@email.it")
|
||||
.withPassword(password)
|
||||
.withNameInMetadata("Test", "User")
|
||||
.build();
|
||||
|
||||
OrcidTokenBuilder.create(context, ePerson, "3de2e370-8aa9-4bbe-8d7e-f5b1577bdad4").build();
|
||||
|
||||
@@ -2142,7 +2159,9 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra
|
||||
.andExpect(status().isForbidden());
|
||||
|
||||
profile = context.reloadEntity(profile);
|
||||
ePerson = context.reloadEntity(ePerson);
|
||||
|
||||
assertThat(ePerson.getNetid(), notNullValue());
|
||||
assertThat(getMetadataValues(profile, "person.identifier.orcid"), not(empty()));
|
||||
assertThat(getMetadataValues(profile, "dspace.orcid.scope"), not(empty()));
|
||||
assertThat(getMetadataValues(profile, "dspace.orcid.authenticated"), not(empty()));
|
||||
@@ -2194,7 +2213,9 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra
|
||||
verifyNoMoreInteractions(orcidClientMock);
|
||||
|
||||
profile = context.reloadEntity(profile);
|
||||
eperson = context.reloadEntity(eperson);
|
||||
|
||||
assertThat(eperson.getNetid(), nullValue());
|
||||
assertThat(getMetadataValues(profile, "person.identifier.orcid"), empty());
|
||||
assertThat(getMetadataValues(profile, "dspace.orcid.scope"), empty());
|
||||
assertThat(getMetadataValues(profile, "dspace.orcid.authenticated"), empty());
|
||||
@@ -2209,14 +2230,15 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra
|
||||
context.turnOffAuthorisationSystem();
|
||||
|
||||
EPerson ePerson = EPersonBuilder.createEPerson(context)
|
||||
.withCanLogin(true)
|
||||
.withOrcid("0000-1111-2222-3333")
|
||||
.withOrcidScope("/read")
|
||||
.withOrcidScope("/write")
|
||||
.withEmail("test@email.it")
|
||||
.withPassword(password)
|
||||
.withNameInMetadata("Test", "User")
|
||||
.build();
|
||||
.withCanLogin(true)
|
||||
.withOrcid("0000-1111-2222-3333")
|
||||
.withNetId("0000-1111-2222-3333")
|
||||
.withOrcidScope("/read")
|
||||
.withOrcidScope("/write")
|
||||
.withEmail("test@email.it")
|
||||
.withPassword(password)
|
||||
.withNameInMetadata("Test", "User")
|
||||
.build();
|
||||
|
||||
OrcidTokenBuilder.create(context, ePerson, "3de2e370-8aa9-4bbe-8d7e-f5b1577bdad4").build();
|
||||
|
||||
@@ -2236,7 +2258,9 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra
|
||||
.andExpect(status().isForbidden());
|
||||
|
||||
profile = context.reloadEntity(profile);
|
||||
ePerson = context.reloadEntity(ePerson);
|
||||
|
||||
assertThat(ePerson.getNetid(), notNullValue());
|
||||
assertThat(getMetadataValues(profile, "person.identifier.orcid"), not(empty()));
|
||||
assertThat(getMetadataValues(profile, "dspace.orcid.scope"), not(empty()));
|
||||
assertThat(getMetadataValues(profile, "dspace.orcid.authenticated"), not(empty()));
|
||||
@@ -2287,7 +2311,9 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra
|
||||
verifyNoMoreInteractions(orcidClientMock);
|
||||
|
||||
profile = context.reloadEntity(profile);
|
||||
eperson = context.reloadEntity(eperson);
|
||||
|
||||
assertThat(eperson.getNetid(), nullValue());
|
||||
assertThat(getMetadataValues(profile, "person.identifier.orcid"), empty());
|
||||
assertThat(getMetadataValues(profile, "dspace.orcid.scope"), empty());
|
||||
assertThat(getMetadataValues(profile, "dspace.orcid.authenticated"), empty());
|
||||
@@ -2340,7 +2366,9 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra
|
||||
verifyNoMoreInteractions(orcidClientMock);
|
||||
|
||||
profile = context.reloadEntity(profile);
|
||||
eperson = context.reloadEntity(eperson);
|
||||
|
||||
assertThat(eperson.getNetid(), nullValue());
|
||||
assertThat(getMetadataValues(profile, "person.identifier.orcid"), empty());
|
||||
assertThat(getMetadataValues(profile, "dspace.orcid.scope"), empty());
|
||||
assertThat(getMetadataValues(profile, "dspace.orcid.authenticated"), empty());
|
||||
@@ -2355,14 +2383,15 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra
|
||||
context.turnOffAuthorisationSystem();
|
||||
|
||||
EPerson ePerson = EPersonBuilder.createEPerson(context)
|
||||
.withCanLogin(true)
|
||||
.withOrcid("0000-1111-2222-3333")
|
||||
.withOrcidScope("/read")
|
||||
.withOrcidScope("/write")
|
||||
.withEmail("test@email.it")
|
||||
.withPassword(password)
|
||||
.withNameInMetadata("Test", "User")
|
||||
.build();
|
||||
.withCanLogin(true)
|
||||
.withOrcid("0000-1111-2222-3333")
|
||||
.withNetId("0000-1111-2222-3333")
|
||||
.withOrcidScope("/read")
|
||||
.withOrcidScope("/write")
|
||||
.withEmail("test@email.it")
|
||||
.withPassword(password)
|
||||
.withNameInMetadata("Test", "User")
|
||||
.build();
|
||||
|
||||
OrcidTokenBuilder.create(context, ePerson, "3de2e370-8aa9-4bbe-8d7e-f5b1577bdad4").build();
|
||||
|
||||
@@ -2382,7 +2411,9 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra
|
||||
.andExpect(status().isForbidden());
|
||||
|
||||
profile = context.reloadEntity(profile);
|
||||
ePerson = context.reloadEntity(ePerson);
|
||||
|
||||
assertThat(ePerson.getNetid(), notNullValue());
|
||||
assertThat(getMetadataValues(profile, "person.identifier.orcid"), not(empty()));
|
||||
assertThat(getMetadataValues(profile, "dspace.orcid.scope"), not(empty()));
|
||||
assertThat(getMetadataValues(profile, "dspace.orcid.authenticated"), not(empty()));
|
||||
|
@@ -1637,6 +1637,43 @@ google.recaptcha.site-verify = https://www.google.com/recaptcha/api/siteverify
|
||||
# checkbox - The "I'm not a robot" Checkbox requires the user to click a checkbox indicating the user is not a robot.
|
||||
#google.recaptcha.mode =
|
||||
|
||||
#------------------------------------------------------------------#
|
||||
#---------------REGISTRATION DATA CONFIGURATION--------------------#
|
||||
#------------------------------------------------------------------#
|
||||
|
||||
# Configuration for the duration of the token depending on the type
|
||||
# the format used should be compatible with the standard DURATION format (ISO-8601),
|
||||
# but without the prefix `PT`:
|
||||
#
|
||||
# - PT1H -> 1H // hours
|
||||
# - PT1M -> 1M // minutes
|
||||
# - PT1S -> 1S // seconds
|
||||
#
|
||||
# reference: https://www.digi.com/resources/documentation/digidocs/90001488-13/reference/r_iso_8601_duration_format.htm
|
||||
#
|
||||
# Sets the token expiration to complete the login with orcid to be 1H
|
||||
eperson.registration-data.token.orcid.expiration = 1H
|
||||
# Sets the token expiration for the email validation sent with orcid login to be 1H
|
||||
eperson.registration-data.token.validation_orcid.expiration = 1H
|
||||
# Sets the token expiration for the forgot token type to be 24H
|
||||
eperson.registration-data.token.forgot.expiration = 24H
|
||||
# Sets the token expiration for the register token type to be 24H
|
||||
eperson.registration-data.token.register.expiration = 24H
|
||||
# Sets the token expiration for the invitation token type to be 24H
|
||||
eperson.registration-data.token.invitation.expiration = 24H
|
||||
# Sets the token expiration for the change_password token type to be 1H
|
||||
eperson.registration-data.token.change_password.expiration = 1H
|
||||
|
||||
# Configuration that enables the schedulable tasks related to the registration, as of now the class schedules a cleanup
|
||||
# of the registationdata table. This action will remove all the expired token from that table.
|
||||
# Just take a look to org.dspace.app.scheduler.eperson.RegistrationDataScheduler for a deeper understanding.
|
||||
# The property `enabled` should be setted to true to enable it.
|
||||
eperson.registration-data.scheduler.enabled = true
|
||||
# Configuration for the task that deletes expired registrations.
|
||||
# Its value should be compatible with the cron format.
|
||||
# By default it's scheduled to be run every 15 minutes.
|
||||
eperson.registration-data.scheduler.expired-registration-data.cron = 0 0/15 * * * ?
|
||||
|
||||
#------------------------------------------------------------------#
|
||||
#-------------------MODULE CONFIGURATIONS--------------------------#
|
||||
#------------------------------------------------------------------#
|
||||
|
22
dspace/config/emails/orcid
Normal file
22
dspace/config/emails/orcid
Normal file
@@ -0,0 +1,22 @@
|
||||
## E-mail sent to DSpace users when they try to register with an ORCID account
|
||||
##
|
||||
## Parameters: {0} is expanded to a special registration URL
|
||||
##
|
||||
## See org.dspace.core.Email for information on the format of this file.
|
||||
##
|
||||
#set($subject = "${config.get('dspace.name')} Account Registration")
|
||||
#set($phone = ${config.get('mail.message.helpdesk.telephone')})
|
||||
To complete registration for a DSpace account, please click the link
|
||||
below:
|
||||
|
||||
${params[0]}
|
||||
|
||||
If you need assistance with your account, please email
|
||||
|
||||
${config.get("mail.helpdesk")}
|
||||
#if( $phone )
|
||||
|
||||
or call us at ${phone}.
|
||||
#end
|
||||
|
||||
The ${config.get("dspace.name")} Team
|
22
dspace/config/emails/validation_orcid
Normal file
22
dspace/config/emails/validation_orcid
Normal file
@@ -0,0 +1,22 @@
|
||||
## E-mail sent to DSpace users when they confirm the orcid email address for the account
|
||||
##
|
||||
## Parameters: {0} is expanded to a special registration URL
|
||||
##
|
||||
## See org.dspace.core.Email for information on the format of this file.
|
||||
##
|
||||
#set($subject = "${config.get('dspace.name')} Account Registration")
|
||||
#set($phone = ${config.get('mail.message.helpdesk.telephone')})
|
||||
To confirm your email and create the needed account, please click the link
|
||||
below:
|
||||
|
||||
${params[0]}
|
||||
|
||||
If you need assistance with your account, please email
|
||||
|
||||
${config.get("mail.helpdesk")}
|
||||
#if( $phone )
|
||||
|
||||
or call us at ${phone}.
|
||||
#end
|
||||
|
||||
The ${config.get("dspace.name")} Team
|
@@ -73,6 +73,7 @@
|
||||
<mapping class="org.dspace.eperson.Group"/>
|
||||
<mapping class="org.dspace.eperson.Group2GroupCache"/>
|
||||
<mapping class="org.dspace.eperson.RegistrationData"/>
|
||||
<mapping class="org.dspace.eperson.RegistrationDataMetadata"/>
|
||||
<mapping class="org.dspace.eperson.Subscription"/>
|
||||
<mapping class="org.dspace.eperson.SubscriptionParameter"/>
|
||||
<mapping class="org.dspace.handle.Handle"/>
|
||||
|
@@ -40,6 +40,7 @@
|
||||
<bean class="org.dspace.eperson.dao.impl.Group2GroupCacheDAOImpl"/>
|
||||
<bean class="org.dspace.eperson.dao.impl.GroupDAOImpl"/>
|
||||
<bean class="org.dspace.eperson.dao.impl.RegistrationDataDAOImpl"/>
|
||||
<bean class="org.dspace.eperson.dao.impl.RegistrationDataMetadataDAOImpl"/>
|
||||
<bean class="org.dspace.eperson.dao.impl.SubscriptionDAOImpl"/>
|
||||
<bean class="org.dspace.eperson.dao.impl.SubscriptionParameterDAOImpl"/>
|
||||
|
||||
|
@@ -107,6 +107,7 @@
|
||||
<bean class="org.dspace.eperson.EPersonServiceImpl"/>
|
||||
<bean class="org.dspace.eperson.GroupServiceImpl"/>
|
||||
<bean class="org.dspace.eperson.RegistrationDataServiceImpl"/>
|
||||
<bean class="org.dspace.eperson.RegistrationDataMetadataServiceImpl"/>
|
||||
<bean class="org.dspace.eperson.SubscribeServiceImpl"/>
|
||||
<bean class="org.dspace.eperson.CaptchaServiceImpl"/>
|
||||
<bean class="org.dspace.event.EventServiceImpl"/>
|
||||
|
Reference in New Issue
Block a user