[CST-5306] Improvements on ResearcherProfileRestRepository

This commit is contained in:
Luca Giamminonni
2022-04-22 14:32:20 +02:00
parent 626775347b
commit ee6cf3d60c
20 changed files with 284 additions and 227 deletions

View File

@@ -8,32 +8,26 @@
package org.dspace.app.exception;
/**
* This class provides an exception to be used when a conflict on a resource
* occurs.
* This class provides an exception to be used when trying to save a resource
* that already exists.
*
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
*
*/
public class ResourceConflictException extends RuntimeException {
public class ResourceAlreadyExistsException extends RuntimeException {
private static final long serialVersionUID = 1L;
private final Object resource;
/**
* Create a ResourceConflictException with a message and the conflicting
* resource.
* Create a ResourceAlreadyExistsException with a message and the already
* existing resource.
*
* @param message the error message
* @param resource the resource that caused the conflict
*/
public ResourceConflictException(String message, Object resource) {
public ResourceAlreadyExistsException(String message) {
super(message);
this.resource = resource;
}
public Object getResource() {
return resource;
}
}

View File

@@ -52,6 +52,11 @@ public class ResearcherProfile {
return dspaceObjectOwner.getValue();
}
/**
* A profile is considered visible if accessible by anonymous users. This method
* returns true if the given item has a READ policy related to ANONYMOUS group,
* false otherwise.
*/
public boolean isVisible() {
return item.getResourcePolicies().stream()
.filter(policy -> policy.getGroup() != null)

View File

@@ -9,7 +9,6 @@ package org.dspace.app.profile;
import static java.util.Optional.empty;
import static java.util.Optional.ofNullable;
import static org.apache.commons.lang3.ArrayUtils.contains;
import static org.dspace.content.authority.Choices.CF_ACCEPTED;
import static org.dspace.core.Constants.READ;
import static org.dspace.eperson.Group.ANONYMOUS;
@@ -24,7 +23,7 @@ import java.util.UUID;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.dspace.app.exception.ResourceConflictException;
import org.dspace.app.exception.ResourceAlreadyExistsException;
import org.dspace.app.profile.service.ResearcherProfileService;
import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.service.AuthorizeService;
@@ -105,8 +104,7 @@ public class ResearcherProfileServiceImpl implements ResearcherProfileService {
Item profileItem = findResearcherProfileItemById(context, ePerson.getID());
if (profileItem != null) {
ResearcherProfile profile = new ResearcherProfile(profileItem);
throw new ResourceConflictException("A profile is already linked to the provided User", profile);
throw new ResourceAlreadyExistsException("A profile is already linked to the provided User");
}
Collection collection = findProfileCollection(context)
@@ -136,7 +134,7 @@ public class ResearcherProfileServiceImpl implements ResearcherProfileService {
if (isHardDeleteEnabled()) {
deleteItem(context, profileItem);
} else {
removeDspaceObjectOwnerMetadata(context, profileItem);
removeOwnerMetadata(context, profileItem);
}
}
@@ -161,47 +159,63 @@ public class ResearcherProfileServiceImpl implements ResearcherProfileService {
}
@Override
public ResearcherProfile claim(final Context context, final EPerson ePerson, final URI uri)
public ResearcherProfile claim(Context context, EPerson ePerson, URI uri)
throws SQLException, AuthorizeException, SearchServiceException {
Item profileItem = findResearcherProfileItemById(context, ePerson.getID());
if (profileItem != null) {
ResearcherProfile profile = new ResearcherProfile(profileItem);
throw new ResourceConflictException("A profile is already linked to the provided User", profile);
throw new ResourceAlreadyExistsException("A profile is already linked to the provided User");
}
Item item = findItemByURI(context, uri)
.orElseThrow(() -> new IllegalArgumentException("No item found by URI " + uri));
if (!item.isArchived() || item.isWithdrawn() || notClaimableEntityType(item)) {
throw new IllegalArgumentException("Provided uri does not represent a valid Item to be claimed");
if (!item.isArchived() || item.isWithdrawn()) {
throw new IllegalArgumentException(
"Only archived items can be claimed to create a researcher profile. Item ID: " + item.getID());
}
if (!hasProfileType(item)) {
throw new IllegalArgumentException("The provided item has not a profile type. Item ID: " + item.getID());
}
String existingOwner = itemService.getMetadataFirstValue(item, "dspace", "object", "owner", Item.ANY);
if (StringUtils.isNotBlank(existingOwner)) {
throw new IllegalArgumentException("Item with provided uri has already an owner");
throw new IllegalArgumentException("Item with provided uri has already an owner - ID: " + existingOwner);
}
context.turnOffAuthorisationSystem();
itemService.addMetadata(context, item, "dspace", "object", "owner", null,
ePerson.getName(), ePerson.getID().toString(), CF_ACCEPTED);
context.restoreAuthSystemState();
return new ResearcherProfile(item);
}
@Override
public boolean hasProfileType(Item item) {
String profileType = getProfileType();
if (StringUtils.isBlank(profileType)) {
return false;
}
return profileType.equals(itemService.getEntityType(item));
}
@Override
public String getProfileType() {
return configurationService.getProperty("researcher-profile.entity-type", "Person");
}
private Optional<Item> findItemByURI(final Context context, final URI uri) throws SQLException {
String path = uri.getPath();
UUID uuid = UUIDUtils.fromString(path.substring(path.lastIndexOf("/") + 1));
return ofNullable(itemService.find(context, uuid));
}
private boolean notClaimableEntityType(final Item item) {
String entityType = itemService.getEntityType(item);
return !contains(configurationService.getArrayProperty("claimable.entityType"), entityType);
}
/**
* Search for an profile item owned by an eperson with the given id.
*/
private Item findResearcherProfileItemById(Context context, UUID id) throws SQLException, AuthorizeException {
String profileType = getProfileType();
@@ -218,6 +232,10 @@ public class ResearcherProfileServiceImpl implements ResearcherProfileService {
return null;
}
/**
* Returns a Profile collection based on a configuration or searching for a
* collection of researcher profile type.
*/
@SuppressWarnings("rawtypes")
private Optional<Collection> findProfileCollection(Context context) throws SQLException, SearchServiceException {
UUID uuid = UUIDUtils.fromString(configurationService.getProperty("researcher-profile.collection.uuid"));
@@ -246,6 +264,9 @@ public class ResearcherProfileServiceImpl implements ResearcherProfileService {
return ofNullable((Collection) indexableObjects.get(0).getIndexedObject());
}
/**
* Create a new profile item for the given ePerson in the provided collection.
*/
private Item createProfileItem(Context context, EPerson ePerson, Collection collection)
throws AuthorizeException, SQLException {
@@ -259,22 +280,34 @@ public class ResearcherProfileServiceImpl implements ResearcherProfileService {
item = installItemService.installItem(context, workspaceItem);
Group anonymous = groupService.findByName(context, ANONYMOUS);
authorizeService.removeGroupPolicies(context, item, anonymous);
if (isNewProfilePrivateByDefault()) {
Group anonymous = groupService.findByName(context, ANONYMOUS);
authorizeService.removeGroupPolicies(context, item, anonymous);
}
authorizeService.addPolicy(context, item, READ, ePerson);
return item;
return reloadItem(context, item);
}
private boolean isHardDeleteEnabled() {
return configurationService.getBooleanProperty("researcher-profile.hard-delete.enabled");
}
private void removeDspaceObjectOwnerMetadata(Context context, Item profileItem) throws SQLException {
private boolean isNewProfilePrivateByDefault() {
return configurationService.getBooleanProperty("researcher-profile.set-new-profile-private");
}
private void removeOwnerMetadata(Context context, Item profileItem) throws SQLException {
List<MetadataValue> metadata = itemService.getMetadata(profileItem, "dspace", "object", "owner", Item.ANY);
itemService.removeMetadataValues(context, profileItem, metadata);
}
private Item reloadItem(Context context, Item item) throws SQLException {
context.uncacheEntity(item);
return context.reloadEntity(item);
}
private void deleteItem(Context context, Item profileItem) throws SQLException, AuthorizeException {
try {
context.turnOffAuthorisationSystem();
@@ -286,8 +319,4 @@ public class ResearcherProfileServiceImpl implements ResearcherProfileService {
}
}
private String getProfileType() {
return configurationService.getProperty("researcher-profile.type", "Person");
}
}

View File

@@ -13,6 +13,7 @@ import java.util.UUID;
import org.dspace.app.profile.ResearcherProfile;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.Item;
import org.dspace.core.Context;
import org.dspace.discovery.SearchServiceException;
import org.dspace.eperson.EPerson;
@@ -52,22 +53,26 @@ public interface ResearcherProfileService {
throws AuthorizeException, SQLException, SearchServiceException;
/**
* Removes the association between the researcher profile and eperson related to
* the input uuid.
* Delete the profile with the given id. Based on the
* researcher-profile.hard-delete.enabled configuration, this method deletes the
* related item or removes the association between the researcher profile and
* eperson related to the input uuid.
*
* @param context the relevant DSpace Context.
* @param id the researcher profile id
* @param context the relevant DSpace Context.
* @param id the researcher profile id
* @throws AuthorizeException
* @throws SQLException
*/
public void deleteById(Context context, UUID id) throws SQLException, AuthorizeException;
/**
* Changes the visibility of the given profile using the given new visible value
* Changes the visibility of the given profile using the given new visible
* value.
*
* @param context the relevant DSpace Context.
* @param profile the researcher profile to update
* @param visible the visible value to set
* @param context the relevant DSpace Context.
* @param profile the researcher profile to update
* @param visible the visible value to set. If true the profile will
* be visible to all users.
* @throws SQLException
* @throws AuthorizeException
*/
@@ -76,11 +81,32 @@ public interface ResearcherProfileService {
/**
* Claims and links an eperson to an existing DSpaceObject
* @param context the relevant DSpace Context.
* @param ePerson the ePerson
* @param uri uri of existing DSpaceObject to be linked to the eperson
* @return
* @param context the relevant DSpace Context.
* @param ePerson the ePerson
* @param uri uri of existing Item to be linked to the
* eperson
* @return the created profile
* @throws IllegalArgumentException if the given uri is not related to an
* archived item or if the item cannot be
* claimed
*/
ResearcherProfile claim(Context context, EPerson ePerson, URI uri)
throws SQLException, AuthorizeException, SearchServiceException;
/**
* Check if the given item has an entity type compatible with that of the
* researcher profile. If the given item does not have an entity type, the check
* returns false.
*
* @param item the item to check
* @return the check result
*/
boolean hasProfileType(Item item);
/**
* Returns the profile entity type, if any.
*
* @return the profile type
*/
String getProfileType();
}

View File

@@ -12,7 +12,8 @@ import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import org.apache.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.factory.EPersonServiceFactory;
@@ -20,11 +21,14 @@ import org.dspace.eperson.service.EPersonService;
import org.dspace.util.UUIDUtils;
/**
* Implementation of {@link ChoiceAuthority} based on EPerson. Allows you to set
* the id of an eperson as authority.
*
* @author Mykhaylo Boychuk (4science.it)
*/
public class EPersonAuthority implements ChoiceAuthority {
private static final Logger log = Logger.getLogger(EPersonAuthority.class);
private static final Logger log = LogManager.getLogger(EPersonAuthority.class);
/**
* the name assigned to the specific instance by the PluginService, @see

View File

@@ -143,6 +143,4 @@ authentication-ip.Student = 6.6.6.6
# NOTE: 127.0.0.1 (localhost) is always a trusted IP, see ClientInfoServiceImpl#parseTrustedProxyRanges
useProxies = true
proxies.trusted.ipranges = 7.7.7.7
proxies.trusted.include_ui_ip = true
researcher-profile.type = Person
proxies.trusted.include_ui_ip = true

View File

@@ -23,7 +23,9 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* Checks if the given user can claim the given item.
* Checks if the given user can claim the given item. An item can be claimed
* only if the show claim is enabled for it (see
* {@link org.dspace.app.rest.authorization.impl.ShowClaimItemFeature}).
*
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
*

View File

@@ -11,7 +11,6 @@ import java.sql.SQLException;
import java.util.Objects;
import java.util.UUID;
import org.apache.commons.lang3.ArrayUtils;
import org.dspace.app.profile.service.ResearcherProfileService;
import org.dspace.app.rest.authorization.AuthorizationFeature;
import org.dspace.app.rest.authorization.AuthorizationFeatureDocumentation;
@@ -21,7 +20,6 @@ import org.dspace.authorize.AuthorizeException;
import org.dspace.content.Item;
import org.dspace.content.service.ItemService;
import org.dspace.core.Context;
import org.dspace.services.ConfigurationService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -45,15 +43,11 @@ public class ShowClaimItemFeature implements AuthorizationFeature {
private final ItemService itemService;
private final ResearcherProfileService researcherProfileService;
private final ConfigurationService configurationService;
@Autowired
public ShowClaimItemFeature(ItemService itemService,
ResearcherProfileService researcherProfileService,
ConfigurationService configurationService) {
public ShowClaimItemFeature(ItemService itemService, ResearcherProfileService researcherProfileService) {
this.itemService = itemService;
this.researcherProfileService = researcherProfileService;
this.configurationService = configurationService;
}
@Override
@@ -67,25 +61,18 @@ public class ShowClaimItemFeature implements AuthorizationFeature {
String id = ((ItemRest) object).getId();
Item item = itemService.find(context, UUID.fromString(id));
return claimableEntityType(item) && hasNotAlreadyAProfile(context);
return researcherProfileService.hasProfileType(item) && hasNotAlreadyAProfile(context);
}
private boolean hasNotAlreadyAProfile(Context context) {
try {
return researcherProfileService.findById(context, context.getCurrentUser().getID()) == null;
} catch (SQLException | AuthorizeException e) {
LOG.warn("Error while checking if eperson has a ResearcherProfileAssociated: {}",
e.getMessage(), e);
LOG.warn("Error while checking if eperson has a ResearcherProfileAssociated: {}", e.getMessage(), e);
return false;
}
}
private boolean claimableEntityType(Item item) {
String[] claimableEntityTypes = configurationService.getArrayProperty("claimable.entityType");
String entityType = itemService.getEntityType(item);
return ArrayUtils.contains(claimableEntityTypes, entityType);
}
@Override
public String[] getSupportedTypes() {
return new String[] {ItemRest.CATEGORY + "." + ItemRest.NAME};

View File

@@ -20,16 +20,12 @@ import javax.servlet.http.HttpServletResponse;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.app.exception.ResourceConflictException;
import org.dspace.app.rest.converter.ConverterService;
import org.dspace.app.rest.model.RestModel;
import org.dspace.app.exception.ResourceAlreadyExistsException;
import org.dspace.app.rest.utils.ContextUtil;
import org.dspace.app.rest.utils.Utils;
import org.dspace.authorize.AuthorizeException;
import org.dspace.core.Context;
import org.dspace.services.ConfigurationService;
import org.springframework.beans.TypeMismatchException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.repository.support.QueryMethodParameterConversionException;
import org.springframework.http.HttpHeaders;
@@ -72,12 +68,6 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH
@Inject
private ConfigurationService configurationService;
@Autowired
private ConverterService converterService;
@Autowired
private Utils utils;
@ExceptionHandler({AuthorizeException.class, RESTAuthorizationException.class, AccessDeniedException.class})
protected void handleAuthorizeException(HttpServletRequest request, HttpServletResponse response, Exception ex)
throws IOException {
@@ -177,10 +167,10 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH
HttpStatus.BAD_REQUEST.value());
}
@ExceptionHandler(ResourceConflictException.class)
protected ResponseEntity<? extends RestModel> resourceConflictException(ResourceConflictException ex) {
RestModel resource = converterService.toRest(ex.getResource(), utils.obtainProjection());
return new ResponseEntity<RestModel>(resource, HttpStatus.CONFLICT);
@ExceptionHandler(ResourceAlreadyExistsException.class)
protected void resourceConflictException(HttpServletRequest request, HttpServletResponse response,
ResourceAlreadyExistsException ex) throws IOException {
sendErrorResponse(request, response, null, ex.getMessage(), HttpStatus.UNPROCESSABLE_ENTITY.value());
}
@ExceptionHandler(MissingParameterException.class)

View File

@@ -8,6 +8,7 @@
package org.dspace.app.rest.login.impl;
import static org.apache.commons.collections4.IteratorUtils.toList;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.dspace.content.authority.Choices.CF_ACCEPTED;
import java.sql.SQLException;
@@ -53,15 +54,21 @@ public class ResearcherProfileAutomaticClaim implements PostLoggedInAction {
@Autowired
private EPersonService ePersonService;
/**
* The field of the eperson to search for.
*/
private final String ePersonField;
private final String profileFiled;
/**
* The field of the profile item to search.
*/
private final String profileField;
public ResearcherProfileAutomaticClaim(String ePersonField, String profileField) {
Assert.notNull(ePersonField, "An eperson field is required to perform automatic claim");
Assert.notNull(profileField, "An profile field is required to perform automatic claim");
this.ePersonField = ePersonField;
this.profileFiled = profileField;
this.profileField = profileField;
}
@Override
@@ -72,6 +79,10 @@ public class ResearcherProfileAutomaticClaim implements PostLoggedInAction {
return;
}
if (isBlank(researcherProfileService.getProfileType())) {
return;
}
try {
claimProfile(context, currentUser);
} catch (SQLException | AuthorizeException e) {
@@ -89,7 +100,7 @@ public class ResearcherProfileAutomaticClaim implements PostLoggedInAction {
return;
}
Item item = findClaimableItem(context, currentUser);
Item item = findClaimableProfile(context, currentUser);
if (item != null) {
itemService.addMetadata(context, item, "dspace", "object", "owner",
null, fullName, id.toString(), CF_ACCEPTED);
@@ -101,16 +112,16 @@ public class ResearcherProfileAutomaticClaim implements PostLoggedInAction {
return researcherProfileService.findById(context, context.getCurrentUser().getID()) != null;
}
private Item findClaimableItem(Context context, EPerson currentUser)
throws SQLException, AuthorizeException {
private Item findClaimableProfile(Context context, EPerson currentUser) throws SQLException, AuthorizeException {
String value = getValueToSearchFor(context, currentUser);
if (StringUtils.isEmpty(value)) {
return null;
}
List<Item> items = toList(itemService.findArchivedByMetadataField(context, profileFiled, value)).stream()
.filter(this::hasNotCrisOwner)
List<Item> items = toList(itemService.findArchivedByMetadataField(context, profileField, value)).stream()
.filter(this::hasNotOwner)
.filter(researcherProfileService::hasProfileType)
.collect(Collectors.toList());
return items.size() == 1 ? items.get(0) : null;
@@ -123,7 +134,7 @@ public class ResearcherProfileAutomaticClaim implements PostLoggedInAction {
return ePersonService.getMetadataFirstValue(currentUser, new MetadataFieldName(ePersonField), Item.ANY);
}
private boolean hasNotCrisOwner(Item item) {
private boolean hasNotOwner(Item item) {
return CollectionUtils.isEmpty(itemService.getMetadata(item, "dspace", "object", "owner", Item.ANY));
}

View File

@@ -52,7 +52,7 @@ public class ResearcherProfileEPersonLinkRepository extends AbstractDSpaceRestRe
* @param projection the projection object
* @return the ePerson rest representation
*/
@PreAuthorize("hasPermission(#id, 'PROFILE', 'READ')")
@PreAuthorize("hasPermission(#id, 'EPERSON', 'READ')")
public EPersonRest getEPerson(@Nullable HttpServletRequest request, UUID id,
@Nullable Pageable pageable, Projection projection) {

View File

@@ -46,9 +46,7 @@ import org.springframework.stereotype.Component;
*
*/
@Component(ResearcherProfileRest.CATEGORY + "." + ResearcherProfileRest.NAME)
@ConditionalOnProperty(
value = "researcher-profile.type"
)
@ConditionalOnProperty(value = "researcher-profile.entity-type")
public class ResearcherProfileRestRepository extends DSpaceRestRepository<ResearcherProfileRest, UUID> {
public static final String NO_VISIBILITY_CHANGE_MSG = "Refused to perform the Researcher Profile patch based "
@@ -136,7 +134,7 @@ public class ResearcherProfileRestRepository extends DSpaceRestRepository<Resear
}
@Override
@PreAuthorize("hasPermission(#id, 'PROFILE', 'WRITE')")
@PreAuthorize("hasPermission(#id, 'PROFILE', 'DELETE')")
protected void delete(Context context, UUID id) {
try {
researcherProfileService.deleteById(context, id);

View File

@@ -23,7 +23,7 @@ import org.springframework.stereotype.Component;
* Implementation for ResearcherProfile visibility patches.
*
* Example:
* <code> curl -X PATCH http://${dspace.server.url}/api/cris/profiles/<:id-eperson> -H "
* <code> curl -X PATCH http://${dspace.server.url}/api/eperson/profiles/<:id-eperson> -H "
* Content-Type: application/json" -d '[{ "op": "replace", "path": "
* /visible", "value": true]'
* </code>

View File

@@ -98,7 +98,6 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra
.build();
configurationService.setProperty("researcher-profile.collection.uuid", personCollection.getID().toString());
configurationService.setProperty("claimable.entityType", "Person");
context.setCurrentUser(user);
@@ -257,6 +256,23 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra
.andExpect(jsonPath("$.name", is(name)));
}
@Test
public void testCreateAndReturnWithPublicProfile() throws Exception {
configurationService.setProperty("researcher-profile.set-new-profile-private", false);
String id = user.getID().toString();
String authToken = getAuthToken(user.getEmail(), password);
getClient(authToken).perform(post("/api/eperson/profiles/")
.contentType(MediaType.APPLICATION_JSON_VALUE))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.id", is(id)))
.andExpect(jsonPath("$.visible", is(true)))
.andExpect(jsonPath("$.type", is("profile")))
.andExpect(jsonPath("$", matchLinks("http://localhost/api/eperson/profiles/" + id, "item", "eperson")));
}
/**
* Verify that an admin can call the createAndReturn endpoint to store a new
* researcher profile related to another user.
@@ -345,10 +361,7 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra
getClient(authToken).perform(post("/api/eperson/profiles/")
.contentType(MediaType.APPLICATION_JSON_VALUE))
.andExpect(status().isConflict())
.andExpect(jsonPath("$.id", is(id)))
.andExpect(jsonPath("$.visible", is(false)))
.andExpect(jsonPath("$.type", is("profile")));
.andExpect(status().isUnprocessableEntity());
}
@@ -762,6 +775,37 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra
}
@Test
public void testAutomaticProfileClaimByEmailWithRegularEntity() throws Exception {
String userToken = getAuthToken(user.getEmail(), password);
context.turnOffAuthorisationSystem();
Item itemToBeClaimed = ItemBuilder.createItem(context, personCollection)
.withPersonEmail(user.getEmail())
.build();
context.restoreAuthSystemState();
String id = user.getID().toString();
getClient(userToken).perform(get("/api/eperson/profiles/{id}", id))
.andExpect(status().isNotFound());
// the automatic claim is done after the user login
String newUserToken = getAuthToken(user.getEmail(), password);
getClient(newUserToken).perform(get("/api/eperson/profiles/{id}", id))
.andExpect(status().isOk());
// the profile item should be the same
String firstItemId = itemToBeClaimed.getID().toString();
String secondItemId = getItemIdByProfileId(newUserToken, id);
assertEquals("The item should be the same", firstItemId, secondItemId);
}
@Test
public void testNoAutomaticProfileClaimOccursIfManyClaimableItemsAreFound() throws Exception {
@@ -909,7 +953,7 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra
getClient(authToken).perform(post("/api/eperson/profiles/")
.contentType(TEXT_URI_LIST)
.content("http://localhost:8080/server/api/core/items/" + otherPerson.getID().toString()))
.andExpect(status().isConflict());
.andExpect(status().isUnprocessableEntity());
// other person trying to claim same profile
context.turnOffAuthorisationSystem();
@@ -1032,7 +1076,7 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra
getClient(authToken)
.perform(post("/api/eperson/profiles/").contentType(TEXT_URI_LIST)
.content("http://localhost:8080/server/api/core/items/" + id))
.andExpect(status().isConflict());
.andExpect(status().isUnprocessableEntity());
}
private String getItemIdByProfileId(String token, String id) throws Exception {

View File

@@ -24,7 +24,6 @@ import org.dspace.builder.CommunityBuilder;
import org.dspace.builder.ItemBuilder;
import org.dspace.content.Collection;
import org.dspace.content.Item;
import org.dspace.services.ConfigurationService;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
@@ -39,9 +38,6 @@ public class CanClaimItemFeatureIT extends AbstractControllerIntegrationTest {
private Item collectionAProfile;
private Item collectionBProfile;
@Autowired
private ConfigurationService configurationService;
@Autowired
private ItemConverter itemConverter;
@@ -73,8 +69,6 @@ public class CanClaimItemFeatureIT extends AbstractControllerIntegrationTest {
collectionAProfile = ItemBuilder.createItem(context, personCollection).build();
collectionBProfile = ItemBuilder.createItem(context, claimableCollectionB).build();
configurationService.addPropertyValue("claimable.entityType", "Person");
context.restoreAuthSystemState();
canClaimProfileFeature = authorizationFeatureService.find(CanClaimItemFeature.NAME);
@@ -173,22 +167,6 @@ public class CanClaimItemFeatureIT extends AbstractControllerIntegrationTest {
.andExpect(jsonPath("$.page.totalElements", equalTo(0)));
}
@Test
public void testWithoutClaimableEntities() throws Exception {
configurationService.setProperty("claimable.entityType", null);
getClient(getAuthToken(context.getCurrentUser().getEmail(), password))
.perform(get("/api/authz/authorizations/search/object")
.param("uri", uri(collectionAProfile))
.param("eperson", context.getCurrentUser().getID().toString())
.param("feature", canClaimProfileFeature.getName()))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded").doesNotExist())
.andExpect(jsonPath("$.page.totalElements", equalTo(0)));
}
private String uri(Item item) {
ItemRest itemRest = itemConverter.convert(item, Projection.DEFAULT);
String itemRestURI = utils.linkToSingleResource(itemRest, "self").getHref();

View File

@@ -24,7 +24,6 @@ import org.dspace.builder.CommunityBuilder;
import org.dspace.builder.ItemBuilder;
import org.dspace.content.Collection;
import org.dspace.content.Item;
import org.dspace.services.ConfigurationService;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
@@ -40,9 +39,6 @@ public class ShowClaimItemFeatureIT extends AbstractControllerIntegrationTest {
private Item collectionAProfile;
private Item collectionBProfile;
@Autowired
private ConfigurationService configurationService;
@Autowired
private ItemConverter itemConverter;
@@ -74,8 +70,6 @@ public class ShowClaimItemFeatureIT extends AbstractControllerIntegrationTest {
collectionAProfile = ItemBuilder.createItem(context, personCollection).build();
collectionBProfile = ItemBuilder.createItem(context, claimableCollectionB).build();
configurationService.addPropertyValue("claimable.entityType", "Person");
context.restoreAuthSystemState();
showClaimProfileFeature = authorizationFeatureService.find(ShowClaimItemFeature.NAME);
@@ -174,22 +168,6 @@ public class ShowClaimItemFeatureIT extends AbstractControllerIntegrationTest {
.andExpect(jsonPath("$.page.totalElements", equalTo(0)));
}
@Test
public void testWithoutClaimableEntities() throws Exception {
configurationService.setProperty("claimable.entityType", null);
getClient(getAuthToken(context.getCurrentUser().getEmail(), password))
.perform(get("/api/authz/authorizations/search/object")
.param("uri", uri(collectionAProfile))
.param("eperson", context.getCurrentUser().getID().toString())
.param("feature", showClaimProfileFeature.getName()))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded").doesNotExist())
.andExpect(jsonPath("$.page.totalElements", equalTo(0)));
}
private String uri(Item item) {
ItemRest itemRest = itemConverter.convert(item, Projection.DEFAULT);
String itemRestURI = utils.linkToSingleResource(itemRest, "self").getHref();

View File

@@ -1379,17 +1379,6 @@ sherpa.romeo.url = https://v2.sherpa.ac.uk/cgi/retrieve
# register for a new API key
sherpa.romeo.apikey =
##### Authority Control Settings #####
#plugin.named.org.dspace.content.authority.ChoiceAuthority = \
# org.dspace.content.authority.SampleAuthority = Sample, \
# org.dspace.content.authority.SHERPARoMEOPublisher = SRPublisher, \
# org.dspace.content.authority.SHERPARoMEOJournalTitle = SRJournalTitle, \
# org.dspace.content.authority.SolrAuthority = SolrAuthorAuthority
#Uncomment to enable ORCID authority control
#plugin.named.org.dspace.content.authority.ChoiceAuthority = \
# org.dspace.content.authority.SolrAuthority = SolrAuthorAuthority
# URL of ORCID API
# Defaults to using the Public API V3 (pub.orcid.org)
orcid.api.url = https://pub.orcid.org/v3.0
@@ -1404,69 +1393,6 @@ orcid.clientsecret =
#ORCID JWT Endpoint
orcid.oauth.url = https://orcid.org/oauth/token
## The DCInputAuthority plugin is automatically configured with every
## value-pairs element in input-forms.xml, namely:
## common_identifiers, common_types, common_iso_languages
##
## The DSpaceControlledVocabulary plugin is automatically configured
## with every *.xml file in [dspace]/config/controlled-vocabularies,
## and creates a plugin instance for each, using base filename as the name.
## eg: nsi, srsc.
## Each DSpaceControlledVocabulary plugin comes with three configuration options:
# vocabulary.plugin._plugin_.hierarchy.store = <true|false> # default: true
# vocabulary.plugin._plugin_.hierarchy.suggest = <true|false> # default: false
# vocabulary.plugin._plugin_.delimiter = "<string>" # default: "::"
##
## An example using "srsc" can be found later in this section
plugin.selfnamed.org.dspace.content.authority.ChoiceAuthority = \
org.dspace.content.authority.DCInputAuthority, \
org.dspace.content.authority.DSpaceControlledVocabulary
##
## This sets the default lowest confidence level at which a metadata value is included
## in an authority-controlled browse (and search) index. It is a symbolic
## keyword, one of the following values (listed in descending order):
## accepted
## uncertain
## ambiguous
## notfound
## failed
## rejected
## novalue
## unset
## See manual or org.dspace.content.authority.Choices source for descriptions.
authority.minconfidence = ambiguous
# Configuration settings for ORCID based authority control.
# Uncomment the lines below to enable configuration
#choices.plugin.dc.contributor.author = SolrAuthorAuthority
#choices.presentation.dc.contributor.author = authorLookup
#authority.controlled.dc.contributor.author = true
#authority.author.indexer.field.1=dc.contributor.author
##
## This sets the lowest confidence level at which a metadata value is included
## in an authority-controlled browse (and search) index. It is a symbolic
## keyword from the same set as for the default "authority.minconfidence"
#authority.minconfidence.dc.contributor.author = accepted
## demo: subject code autocomplete, using srsc as authority
## (DSpaceControlledVocabulary plugin must be enabled)
## Warning: when enabling this feature any controlled vocabulary configuration in the input-forms.xml for the metadata field will be overridden.
#vocabulary.plugin.srsc.hierarchy.store = true
#vocabulary.plugin.srsc.hierarchy.suggest = true
#vocabulary.plugin.srsc.delimiter = "::"
# publisher name lookup through SHERPA/RoMEO:
#choices.plugin.dc.publisher = SRPublisher
#choices.presentation.dc.publisher = suggest
## demo: journal title lookup, with ISSN as authority
#choices.plugin.dc.title.alternative = SRJournalTitle
#choices.presentation.dc.title.alternative = suggest
#authority.controlled.dc.title.alternative = true
##### Google Scholar Metadata Configuration #####
google-metadata.config = ${dspace.dir}/config/crosswalks/google-metadata.properties
google-metadata.enable = true
@@ -1632,6 +1558,7 @@ include = ${module_dir}/rest.cfg
include = ${module_dir}/iiif.cfg
include = ${module_dir}/solr-statistics.cfg
include = ${module_dir}/solrauthority.cfg
include = ${module_dir}/researcher-profile.cfg
include = ${module_dir}/spring.cfg
include = ${module_dir}/submission-curation.cfg
include = ${module_dir}/sword-client.cfg

View File

@@ -4,12 +4,83 @@
# These configs are used by the authority framework #
#---------------------------------------------------------------#
## The DCInputAuthority plugin is automatically configured with every
## value-pairs element in input-forms.xml, namely:
## common_identifiers, common_types, common_iso_languages
##
## The DSpaceControlledVocabulary plugin is automatically configured
## with every *.xml file in [dspace]/config/controlled-vocabularies,
## and creates a plugin instance for each, using base filename as the name.
## eg: nsi, srsc.
## Each DSpaceControlledVocabulary plugin comes with three configuration options:
# vocabulary.plugin._plugin_.hierarchy.store = <true|false> # default: true
# vocabulary.plugin._plugin_.hierarchy.suggest = <true|false> # default: false
# vocabulary.plugin._plugin_.delimiter = "<string>" # default: "::"
##
## An example using "srsc" can be found later in this section
plugin.selfnamed.org.dspace.content.authority.ChoiceAuthority = \
org.dspace.content.authority.DCInputAuthority, \
org.dspace.content.authority.DSpaceControlledVocabulary
##
## This sets the default lowest confidence level at which a metadata value is included
## in an authority-controlled browse (and search) index. It is a symbolic
## keyword, one of the following values (listed in descending order):
## accepted
## uncertain
## ambiguous
## notfound
## failed
## rejected
## novalue
## unset
## See manual or org.dspace.content.authority.Choices source for descriptions.
authority.minconfidence = ambiguous
# Configuration settings for ORCID based authority control.
# Uncomment the lines below to enable configuration
#choices.plugin.dc.contributor.author = SolrAuthorAuthority
#choices.presentation.dc.contributor.author = authorLookup
#authority.controlled.dc.contributor.author = true
#authority.author.indexer.field.1=dc.contributor.author
##
## This sets the lowest confidence level at which a metadata value is included
## in an authority-controlled browse (and search) index. It is a symbolic
## keyword from the same set as for the default "authority.minconfidence"
#authority.minconfidence.dc.contributor.author = accepted
## demo: subject code autocomplete, using srsc as authority
## (DSpaceControlledVocabulary plugin must be enabled)
## Warning: when enabling this feature any controlled vocabulary configuration in the input-forms.xml for the metadata field will be overridden.
#vocabulary.plugin.srsc.hierarchy.store = true
#vocabulary.plugin.srsc.hierarchy.suggest = true
#vocabulary.plugin.srsc.delimiter = "::"
# publisher name lookup through SHERPA/RoMEO:
#choices.plugin.dc.publisher = SRPublisher
#choices.presentation.dc.publisher = suggest
## demo: journal title lookup, with ISSN as authority
#choices.plugin.dc.title.alternative = SRJournalTitle
#choices.presentation.dc.title.alternative = suggest
#authority.controlled.dc.title.alternative = true
##### Authority Control Settings #####
#plugin.named.org.dspace.content.authority.ChoiceAuthority = \
# org.dspace.content.authority.SampleAuthority = Sample, \
# org.dspace.content.authority.SHERPARoMEOPublisher = SRPublisher, \
# org.dspace.content.authority.SHERPARoMEOJournalTitle = SRJournalTitle, \
# org.dspace.content.authority.SolrAuthority = SolrAuthorAuthority
#Uncomment to enable ORCID authority control
#plugin.named.org.dspace.content.authority.ChoiceAuthority = \
# org.dspace.content.authority.SolrAuthority = SolrAuthorAuthority
plugin.named.org.dspace.content.authority.ChoiceAuthority = \
org.dspace.content.authority.EPersonAuthority = EPersonAuthority
choices.plugin.dspace.object.owner = EPersonAuthority
choices.presentation.dspace.object.owner = suggest
authority.controlled.dspace.object.owner = true

View File

@@ -0,0 +1,15 @@
#---------------------------------------------------------------#
#------------------- PROFILE CONFIGURATIONS --------------------#
#---------------------------------------------------------------#
#the entity type of the researcher profile
researcher-profile.entity-type = Person
# the uuid of the collection where store the researcher profiles created from scratch
researcher-profile.collection.uuid =
# if true when the profile is deleted even the related item is deleted, if false only the link between profile and item is removed
researcher-profile.hard-delete.enabled = false
#true if the new profiles should be private (without anonymous read policy), false otherwise
researcher-profile.set-new-profile-private = true

View File

@@ -48,7 +48,7 @@
<schema>dspace</schema>
<element>object</element>
<qualifier>owner</qualifier>
<scope_note>Stores the reference to the eperson that own the item </scope_note>
<scope_note>Used to support researcher profiles</scope_note>
</dc-type>
</dspace-dc-types>