Merge branch 'main' into CST-7754

This commit is contained in:
corrado lombardi
2023-02-03 18:49:57 +01:00
21 changed files with 1789 additions and 63 deletions

View File

@@ -806,10 +806,11 @@
<scope>test</scope>
</dependency>
<dependency>
<dependency>
<groupId>org.apache.bcel</groupId>
<artifactId>bcel</artifactId>
<version>6.4.0</version>
<version>6.6.0</version>
<scope>test</scope>
</dependency>
<!-- required for openaire api integration -->

View File

@@ -8,8 +8,8 @@
package org.dspace.browse;
import java.util.List;
import java.util.UUID;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
/**
@@ -140,21 +140,21 @@ public interface BrowseDAO {
public void setAscending(boolean ascending);
/**
* Get the database ID of the container object. The container object will be a
* Get the container object. The container object will be a
* Community or a Collection.
*
* @return the database id of the container, or -1 if none is set
* @return the container, or null if none is set
*/
public UUID getContainerID();
public DSpaceObject getContainer();
/**
* Set the database id of the container object. This should be the id of a
* Community or Collection. This will constrain the results of the browse
* to only items or values within items that appear in the given container.
* Set the container object. This should be a Community or Collection.
* This will constrain the results of the browse to only items or values within items that appear in the given
* container and add the related configuration default filters.
*
* @param containerID community/collection internal ID (UUID)
* @param container community/collection
*/
public void setContainerID(UUID containerID);
public void setContainer(DSpaceObject container);
/**
* get the name of the field in which to look for the container id. This is

View File

@@ -141,12 +141,12 @@ public class BrowseEngine {
Collection col = (Collection) scope.getBrowseContainer();
dao.setContainerTable("collection2item");
dao.setContainerIDField("collection_id");
dao.setContainerID(col.getID());
dao.setContainer(col);
} else if (scope.inCommunity()) {
Community com = (Community) scope.getBrowseContainer();
dao.setContainerTable("communities2item");
dao.setContainerIDField("community_id");
dao.setContainerID(com.getID());
dao.setContainer(com);
}
}
@@ -247,12 +247,12 @@ public class BrowseEngine {
Collection col = (Collection) scope.getBrowseContainer();
dao.setContainerTable("collection2item");
dao.setContainerIDField("collection_id");
dao.setContainerID(col.getID());
dao.setContainer(col);
} else if (scope.inCommunity()) {
Community com = (Community) scope.getBrowseContainer();
dao.setContainerTable("communities2item");
dao.setContainerIDField("community_id");
dao.setContainerID(com.getID());
dao.setContainer(com);
}
}
@@ -413,12 +413,12 @@ public class BrowseEngine {
Collection col = (Collection) scope.getBrowseContainer();
dao.setContainerTable("collection2item");
dao.setContainerIDField("collection_id");
dao.setContainerID(col.getID());
dao.setContainer(col);
} else if (scope.inCommunity()) {
Community com = (Community) scope.getBrowseContainer();
dao.setContainerTable("communities2item");
dao.setContainerIDField("community_id");
dao.setContainerID(com.getID());
dao.setContainer(com);
}
}

View File

@@ -13,13 +13,13 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger;
import org.apache.solr.client.solrj.util.ClientUtils;
import org.dspace.authorize.factory.AuthorizeServiceFactory;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.core.Context;
import org.dspace.discovery.DiscoverFacetField;
@@ -31,6 +31,8 @@ import org.dspace.discovery.DiscoverResult.SearchDocument;
import org.dspace.discovery.IndexableObject;
import org.dspace.discovery.SearchService;
import org.dspace.discovery.SearchServiceException;
import org.dspace.discovery.SearchUtils;
import org.dspace.discovery.configuration.DiscoveryConfiguration;
import org.dspace.discovery.configuration.DiscoveryConfigurationParameters;
import org.dspace.discovery.indexobject.IndexableItem;
import org.dspace.services.factory.DSpaceServicesFactory;
@@ -124,9 +126,9 @@ public class SolrBrowseDAO implements BrowseDAO {
private String containerIDField = null;
/**
* the database id of the container we are constraining to
* the container we are constraining to
*/
private UUID containerID = null;
private DSpaceObject container = null;
/**
* the column that we are sorting results by
@@ -176,6 +178,7 @@ public class SolrBrowseDAO implements BrowseDAO {
if (sResponse == null) {
DiscoverQuery query = new DiscoverQuery();
addLocationScopeFilter(query);
addDefaultFilterQueries(query);
addStatusFilter(query);
if (distinct) {
DiscoverFacetField dff;
@@ -240,15 +243,20 @@ public class SolrBrowseDAO implements BrowseDAO {
}
private void addLocationScopeFilter(DiscoverQuery query) {
if (containerID != null) {
if (container != null) {
if (containerIDField.startsWith("collection")) {
query.addFilterQueries("location.coll:" + containerID);
query.addFilterQueries("location.coll:" + container.getID());
} else if (containerIDField.startsWith("community")) {
query.addFilterQueries("location.comm:" + containerID);
query.addFilterQueries("location.comm:" + container.getID());
}
}
}
private void addDefaultFilterQueries(DiscoverQuery query) {
DiscoveryConfiguration discoveryConfiguration = SearchUtils.getDiscoveryConfiguration(container);
discoveryConfiguration.getDefaultFilterQueries().forEach(query::addFilterQueries);
}
@Override
public int doCountQuery() throws BrowseException {
DiscoverResult resp = getSolrResponse();
@@ -337,6 +345,7 @@ public class SolrBrowseDAO implements BrowseDAO {
throws BrowseException {
DiscoverQuery query = new DiscoverQuery();
addLocationScopeFilter(query);
addDefaultFilterQueries(query);
addStatusFilter(query);
query.setMaxResults(0);
query.addFilterQueries("search.resourcetype:" + IndexableItem.TYPE);
@@ -398,8 +407,8 @@ public class SolrBrowseDAO implements BrowseDAO {
* @see org.dspace.browse.BrowseDAO#getContainerID()
*/
@Override
public UUID getContainerID() {
return containerID;
public DSpaceObject getContainer() {
return container;
}
/*
@@ -561,8 +570,8 @@ public class SolrBrowseDAO implements BrowseDAO {
* @see org.dspace.browse.BrowseDAO#setContainerID(int)
*/
@Override
public void setContainerID(UUID containerID) {
this.containerID = containerID;
public void setContainer(DSpaceObject container) {
this.container = container;
}

View File

@@ -51,8 +51,14 @@ import org.dspace.content.virtual.VirtualMetadataPopulator;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.core.LogHelper;
import org.dspace.discovery.DiscoverQuery;
import org.dspace.discovery.DiscoverResult;
import org.dspace.discovery.SearchService;
import org.dspace.discovery.SearchServiceException;
import org.dspace.discovery.indexobject.IndexableItem;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group;
import org.dspace.eperson.service.GroupService;
import org.dspace.event.Event;
import org.dspace.harvest.HarvestedItem;
import org.dspace.harvest.service.HarvestedItemService;
@@ -93,6 +99,8 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl<Item> implements It
@Autowired(required = true)
protected CommunityService communityService;
@Autowired(required = true)
protected GroupService groupService;
@Autowired(required = true)
protected AuthorizeService authorizeService;
@Autowired(required = true)
protected BundleService bundleService;
@@ -105,6 +113,8 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl<Item> implements It
@Autowired(required = true)
protected InstallItemService installItemService;
@Autowired(required = true)
protected SearchService searchService;
@Autowired(required = true)
protected ResourcePolicyService resourcePolicyService;
@Autowired(required = true)
protected CollectionService collectionService;
@@ -1065,6 +1075,53 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl<Item> implements It
return collectionService.canEditBoolean(context, item.getOwningCollection(), false);
}
/**
* Finds all Indexed Items where the current user has edit rights. If the user is an Admin,
* this is all Indexed Items. Otherwise, it includes those Items where
* an indexed "edit" policy lists either the eperson or one of the eperson's groups
*
* @param context DSpace context
* @param discoverQuery
* @return discovery search result objects
* @throws SQLException if something goes wrong
* @throws SearchServiceException if search error
*/
private DiscoverResult retrieveItemsWithEdit(Context context, DiscoverQuery discoverQuery)
throws SQLException, SearchServiceException {
EPerson currentUser = context.getCurrentUser();
if (!authorizeService.isAdmin(context)) {
String userId = currentUser != null ? "e" + currentUser.getID().toString() : "e";
Stream<String> groupIds = groupService.allMemberGroupsSet(context, currentUser).stream()
.map(group -> "g" + group.getID());
String query = Stream.concat(Stream.of(userId), groupIds)
.collect(Collectors.joining(" OR ", "edit:(", ")"));
discoverQuery.addFilterQueries(query);
}
return searchService.search(context, discoverQuery);
}
@Override
public List<Item> findItemsWithEdit(Context context, int offset, int limit)
throws SQLException, SearchServiceException {
DiscoverQuery discoverQuery = new DiscoverQuery();
discoverQuery.setDSpaceObjectFilter(IndexableItem.TYPE);
discoverQuery.setStart(offset);
discoverQuery.setMaxResults(limit);
DiscoverResult resp = retrieveItemsWithEdit(context, discoverQuery);
return resp.getIndexableObjects().stream()
.map(solrItems -> ((IndexableItem) solrItems).getIndexedObject())
.collect(Collectors.toList());
}
@Override
public int countItemsWithEdit(Context context) throws SQLException, SearchServiceException {
DiscoverQuery discoverQuery = new DiscoverQuery();
discoverQuery.setMaxResults(0);
discoverQuery.setDSpaceObjectFilter(IndexableItem.TYPE);
DiscoverResult resp = retrieveItemsWithEdit(context, discoverQuery);
return (int) resp.getTotalSearchResults();
}
/**
* Check if the item is an inprogress submission
*

View File

@@ -28,6 +28,7 @@ import org.dspace.content.MetadataValue;
import org.dspace.content.Thumbnail;
import org.dspace.content.WorkspaceItem;
import org.dspace.core.Context;
import org.dspace.discovery.SearchServiceException;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group;
@@ -768,6 +769,27 @@ public interface ItemService
*/
int countWithdrawnItems(Context context) throws SQLException;
/**
* finds all items for which the current user has editing rights
* @param context DSpace context object
* @param offset page offset
* @param limit page size limit
* @return list of items for which the current user has editing rights
* @throws SQLException
* @throws SearchServiceException
*/
public List<Item> findItemsWithEdit(Context context, int offset, int limit)
throws SQLException, SearchServiceException;
/**
* counts all items for which the current user has editing rights
* @param context DSpace context object
* @return list of items for which the current user has editing rights
* @throws SQLException
* @throws SearchServiceException
*/
public int countItemsWithEdit(Context context) throws SQLException, SearchServiceException;
/**
* Check if the supplied item is an inprogress submission
*

View File

@@ -0,0 +1,119 @@
/**
* 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.discovery;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import org.dspace.authorize.ResourcePolicy;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.content.DSpaceObject;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.core.Context;
/**
* Util methods used by indexing.
*
* @author Koen Pauwels (koen.pauwels at atmire dot com)
*/
public class IndexingUtils {
private IndexingUtils() {
}
/**
* Retrieve all ancestor communities of a given community, with the first one being the given community and the
* last one being the root.
* <p>
*
* @param context DSpace context object
* @param community Community for which we search the ancestors
* @return A list of ancestor communities.
* @throws SQLException if database error
*/
static List<Community> getAncestorCommunities(Context context, Community community) throws SQLException {
ArrayList<Community> communities = new ArrayList<>();
while (community != null) {
communities.add(community);
community = (Community) ContentServiceFactory.getInstance().getDSpaceObjectService(community)
.getParentObject(context, community);
}
return communities;
}
/**
* Retrieve the ids of all groups that have ADMIN rights to the given community, either directly
* (through direct resource policy) or indirectly (through a policy on an ancestor community).
*
* @param context DSpace context object
* @param community Community for which we search the admin group IDs
* @return A list of admin group IDs
* @throws SQLException if database error
*/
static List<UUID> findTransitiveAdminGroupIds(Context context, Community community) throws SQLException {
return getAncestorCommunities(context, community).stream()
.filter(parent -> parent.getAdministrators() != null)
.map(parent -> parent.getAdministrators().getID())
.collect(Collectors.toList());
}
/**
* Retrieve the ids of all groups that have ADMIN rights to the given collection, either directly
* (through direct resource policy) or indirectly (through a policy on its community, or one of
* its ancestor communities).
*
* @param context DSpace context object
* @param collection Collection for which we search the admin group IDs
* @return A list of admin group IDs
* @throws SQLException if database error
*/
static List<UUID> findTransitiveAdminGroupIds(Context context, Collection collection) throws SQLException {
List<UUID> ids = new ArrayList<>();
if (collection.getAdministrators() != null) {
ids.add(collection.getAdministrators().getID());
}
for (Community community : collection.getCommunities()) {
for (UUID id : findTransitiveAdminGroupIds(context, community)) {
ids.add(id);
}
}
return ids;
}
/**
* Retrieve group and eperson IDs for all groups and eperson who have _any_ of the given authorizations
* on the given DSpaceObject. The resulting IDs are prefixed with "e" in the case of an eperson ID, and "g" in the
* case of a group ID.
*
* @param authService The authentication service
* @param context DSpace context object
* @param obj DSpaceObject for which we search the admin group IDs
* @return A stream of admin group IDs as Strings, prefixed with either "e" or "g", depending on whether it is a
* group or eperson ID.
* @throws SQLException if database error
*/
static List<String> findDirectlyAuthorizedGroupAndEPersonPrefixedIds(
AuthorizeService authService, Context context, DSpaceObject obj, int[] authorizations)
throws SQLException {
ArrayList<String> prefixedIds = new ArrayList<>();
for (int auth : authorizations) {
for (ResourcePolicy policy : authService.getPoliciesActionFilter(context, obj, auth)) {
String prefixedId = policy.getGroup() == null
? "e" + policy.getEPerson().getID()
: "g" + policy.getGroup().getID();
prefixedIds.add(prefixedId);
context.uncacheEntity(policy);
}
}
return prefixedIds;
}
}

View File

@@ -7,16 +7,17 @@
*/
package org.dspace.discovery;
import static org.dspace.discovery.IndexingUtils.findDirectlyAuthorizedGroupAndEPersonPrefixedIds;
import static org.dspace.discovery.IndexingUtils.findTransitiveAdminGroupIds;
import java.sql.SQLException;
import java.util.List;
import java.util.UUID;
import org.apache.logging.log4j.Logger;
import org.apache.solr.common.SolrInputDocument;
import org.dspace.authorize.ResourcePolicy;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.core.LogHelper;
@@ -42,29 +43,21 @@ public class SolrServiceIndexCollectionSubmittersPlugin implements SolrServiceIn
Collection col = ((IndexableCollection) idxObj).getIndexedObject();
if (col != null) {
try {
String fieldValue = null;
Community parent = (Community) ContentServiceFactory.getInstance().getDSpaceObjectService(col)
.getParentObject(context, col);
while (parent != null) {
if (parent.getAdministrators() != null) {
fieldValue = "g" + parent.getAdministrators().getID();
document.addField("submit", fieldValue);
}
parent = (Community) ContentServiceFactory.getInstance().getDSpaceObjectService(parent)
.getParentObject(context, parent);
// Index groups with ADMIN rights on the Collection, on
// Communities containing those Collections, and recursively on any Community containing such a
// Community.
// TODO: Strictly speaking we should also check for epersons who received admin rights directly,
// without being part of the admin group. Finding them may be a lot slower though.
for (UUID unprefixedId : findTransitiveAdminGroupIds(context, col)) {
document.addField("submit", "g" + unprefixedId);
}
List<ResourcePolicy> policies = authorizeService.getPoliciesActionFilter(context,col,Constants.ADD);
policies.addAll(authorizeService.getPoliciesActionFilter(context, col, Constants.ADMIN));
for (ResourcePolicy resourcePolicy : policies) {
if (resourcePolicy.getGroup() != null) {
fieldValue = "g" + resourcePolicy.getGroup().getID();
} else {
fieldValue = "e" + resourcePolicy.getEPerson().getID();
}
document.addField("submit", fieldValue);
context.uncacheEntity(resourcePolicy);
// Index groups and epersons with ADD or ADMIN rights on the Collection.
List<String> prefixedIds = findDirectlyAuthorizedGroupAndEPersonPrefixedIds(
authorizeService, context, col, new int[] {Constants.ADD, Constants.ADMIN}
);
for (String prefixedId : prefixedIds) {
document.addField("submit", prefixedId);
}
} catch (SQLException e) {
log.error(LogHelper.getHeader(context, "Error while indexing resource policies",
@@ -73,5 +66,4 @@ public class SolrServiceIndexCollectionSubmittersPlugin implements SolrServiceIn
}
}
}
}

View File

@@ -0,0 +1,71 @@
/**
* 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.discovery;
import static org.dspace.discovery.IndexingUtils.findDirectlyAuthorizedGroupAndEPersonPrefixedIds;
import static org.dspace.discovery.IndexingUtils.findTransitiveAdminGroupIds;
import java.sql.SQLException;
import java.util.List;
import java.util.UUID;
import org.apache.logging.log4j.Logger;
import org.apache.solr.common.SolrInputDocument;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.content.Collection;
import org.dspace.content.Item;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.core.LogHelper;
import org.dspace.discovery.indexobject.IndexableItem;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Indexes policies that yield write access to items.
*
* @author Koen Pauwels at atmire.com
*/
public class SolrServiceIndexItemEditorsPlugin implements SolrServiceIndexPlugin {
private static final Logger log = org.apache.logging.log4j.LogManager
.getLogger(SolrServiceIndexItemEditorsPlugin.class);
@Autowired(required = true)
protected AuthorizeService authorizeService;
@Override
public void additionalIndex(Context context, IndexableObject idxObj, SolrInputDocument document) {
if (idxObj instanceof IndexableItem) {
Item item = ((IndexableItem) idxObj).getIndexedObject();
if (item != null) {
try {
// Index groups with ADMIN rights on Collections containing the Item, on
// Communities containing those Collections, and recursively on any Community containing ssuch a
// Community.
// TODO: Strictly speaking we should also check for epersons who received admin rights directly,
// without being part of the admin group. Finding them may be a lot slower though.
for (Collection collection : item.getCollections()) {
for (UUID unprefixedId : findTransitiveAdminGroupIds(context, collection)) {
document.addField("edit", "g" + unprefixedId);
}
}
// Index groups and epersons with WRITE or direct ADMIN rights on the Item.
List<String> prefixedIds = findDirectlyAuthorizedGroupAndEPersonPrefixedIds(
authorizeService, context, item, new int[] {Constants.WRITE, Constants.ADMIN}
);
for (String prefixedId : prefixedIds) {
document.addField("edit", prefixedId);
}
} catch (SQLException e) {
log.error(LogHelper.getHeader(context, "Error while indexing resource policies",
"Item: (id " + item.getID() + " name " + item.getName() + ")" ));
}
}
}
}
}

View File

@@ -20,12 +20,15 @@ import java.util.stream.Collectors;
import org.apache.logging.log4j.Logger;
import org.dspace.AbstractIntegrationTestWithDatabase;
import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.ResourcePolicy;
import org.dspace.builder.CollectionBuilder;
import org.dspace.builder.CommunityBuilder;
import org.dspace.builder.EntityTypeBuilder;
import org.dspace.builder.GroupBuilder;
import org.dspace.builder.ItemBuilder;
import org.dspace.builder.RelationshipBuilder;
import org.dspace.builder.RelationshipTypeBuilder;
import org.dspace.builder.ResourcePolicyBuilder;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.content.EntityType;
@@ -35,6 +38,8 @@ import org.dspace.content.Relationship;
import org.dspace.content.RelationshipType;
import org.dspace.content.WorkspaceItem;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.core.Constants;
import org.dspace.eperson.Group;
import org.dspace.versioning.Version;
import org.dspace.versioning.factory.VersionServiceFactory;
import org.dspace.versioning.service.VersioningService;
@@ -473,6 +478,82 @@ public class ItemServiceTest extends AbstractIntegrationTestWithDatabase {
context.restoreAuthSystemState();
}
@Test
public void testFindItemsWithEditNoRights() throws Exception {
context.setCurrentUser(eperson);
List<Item> result = itemService.findItemsWithEdit(context, 0, 10);
int count = itemService.countItemsWithEdit(context);
assertThat(result.size(), equalTo(0));
assertThat(count, equalTo(0));
}
@Test
public void testFindAndCountItemsWithEditEPerson() throws Exception {
ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context)
.withUser(eperson)
.withDspaceObject(item)
.withAction(Constants.WRITE)
.build();
context.setCurrentUser(eperson);
List<Item> result = itemService.findItemsWithEdit(context, 0, 10);
int count = itemService.countItemsWithEdit(context);
assertThat(result.size(), equalTo(1));
assertThat(count, equalTo(1));
}
@Test
public void testFindAndCountItemsWithAdminEPerson() throws Exception {
ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context)
.withUser(eperson)
.withDspaceObject(item)
.withAction(Constants.ADMIN)
.build();
context.setCurrentUser(eperson);
List<Item> result = itemService.findItemsWithEdit(context, 0, 10);
int count = itemService.countItemsWithEdit(context);
assertThat(result.size(), equalTo(1));
assertThat(count, equalTo(1));
}
@Test
public void testFindAndCountItemsWithEditGroup() throws Exception {
context.turnOffAuthorisationSystem();
Group group = GroupBuilder.createGroup(context)
.addMember(eperson)
.build();
context.restoreAuthSystemState();
ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context)
.withGroup(group)
.withDspaceObject(item)
.withAction(Constants.WRITE)
.build();
context.setCurrentUser(eperson);
List<Item> result = itemService.findItemsWithEdit(context, 0, 10);
int count = itemService.countItemsWithEdit(context);
assertThat(result.size(), equalTo(1));
assertThat(count, equalTo(1));
}
@Test
public void testFindAndCountItemsWithAdminGroup() throws Exception {
context.turnOffAuthorisationSystem();
Group group = GroupBuilder.createGroup(context)
.addMember(eperson)
.build();
context.restoreAuthSystemState();
ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context)
.withGroup(group)
.withDspaceObject(item)
.withAction(Constants.ADMIN)
.build();
context.setCurrentUser(eperson);
List<Item> result = itemService.findItemsWithEdit(context, 0, 10);
int count = itemService.countItemsWithEdit(context);
assertThat(result.size(), equalTo(1));
assertThat(count, equalTo(1));
}
private void assertMetadataValue(String authorQualifier, String contributorElement, String dcSchema, String value,
String authority, int place, MetadataValue metadataValue) {
assertThat(metadataValue.getValue(), equalTo(value));

View File

@@ -13,6 +13,7 @@ import org.dspace.app.rest.model.BaseObjectRest;
import org.dspace.app.rest.model.RestAddressableModel;
import org.dspace.app.rest.model.SiteRest;
import org.dspace.core.Context;
import org.dspace.discovery.SearchServiceException;
import org.springframework.core.annotation.AnnotationUtils;
/**
@@ -33,7 +34,7 @@ public interface AuthorizationFeature {
* wide feature
* @return true if the user associated with the context has access to the feature for the specified object
*/
boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException;
boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException, SearchServiceException;
/**
* Return the name of the feature

View File

@@ -13,6 +13,7 @@ import java.util.List;
import org.dspace.app.rest.model.BaseObjectRest;
import org.dspace.app.rest.model.SiteRest;
import org.dspace.core.Context;
import org.dspace.discovery.SearchServiceException;
/**
* This service provides access to the Authorization Features and check if the feature is allowed or not in a specific
@@ -34,7 +35,8 @@ public interface AuthorizationFeatureService {
* feature pass the {@link SiteRest} object
* @return true if the user associated with the context has access to the feature
*/
boolean isAuthorized(Context context, AuthorizationFeature feature, BaseObjectRest object) throws SQLException;
boolean isAuthorized(Context context, AuthorizationFeature feature, BaseObjectRest object)
throws SQLException, SearchServiceException;
/**
* Get all the authorization features defined in the system

View File

@@ -18,6 +18,7 @@ import org.dspace.app.rest.authorization.AuthorizationFeatureService;
import org.dspace.app.rest.model.BaseObjectRest;
import org.dspace.app.rest.utils.Utils;
import org.dspace.core.Context;
import org.dspace.discovery.SearchServiceException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -38,7 +39,7 @@ public class AuthorizationFeatureServiceImpl implements AuthorizationFeatureServ
@Override
public boolean isAuthorized(Context context, AuthorizationFeature feature, BaseObjectRest object)
throws SQLException {
throws SQLException, SearchServiceException {
if (object == null) {
// the authorization interface require that the object is not null
return false;

View File

@@ -0,0 +1,58 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.authorization.impl;
import java.sql.SQLException;
import org.dspace.app.rest.authorization.AuthorizationFeature;
import org.dspace.app.rest.authorization.AuthorizationFeatureDocumentation;
import org.dspace.app.rest.model.BaseObjectRest;
import org.dspace.app.rest.model.ItemRest;
import org.dspace.app.rest.model.SiteRest;
import org.dspace.app.rest.utils.Utils;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.content.Item;
import org.dspace.content.service.ItemService;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.discovery.SearchServiceException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
@AuthorizationFeatureDocumentation(name = EditItemFeature.NAME,
description = "It can be used to verify if a user has rights to edit any item.")
public class EditItemFeature implements AuthorizationFeature {
public static final String NAME = "canEditItem";
@Autowired
AuthorizeService authService;
@Autowired
ItemService itemService;
@Autowired
Utils utils;
@Override
public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException, SearchServiceException {
if (object instanceof SiteRest) {
return itemService.countItemsWithEdit(context) > 0;
} else if (object instanceof ItemRest) {
Item item = (Item) utils.getDSpaceAPIObjectFromRest(context, object);
return authService.authorizeActionBoolean(context, item, Constants.WRITE);
}
return false;
}
@Override
public String[] getSupportedTypes() {
return new String[] {
ItemRest.CATEGORY + "." + ItemRest.NAME,
SiteRest.CATEGORY + "." + SiteRest.NAME
};
}
}

View File

@@ -0,0 +1,62 @@
/**
* 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.authorization.impl;
import java.sql.SQLException;
import org.dspace.app.rest.authorization.AuthorizationFeature;
import org.dspace.app.rest.authorization.AuthorizationFeatureDocumentation;
import org.dspace.app.rest.model.BaseObjectRest;
import org.dspace.app.rest.model.CollectionRest;
import org.dspace.app.rest.model.SiteRest;
import org.dspace.app.rest.utils.Utils;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.content.Collection;
import org.dspace.content.service.CollectionService;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.discovery.SearchServiceException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
@AuthorizationFeatureDocumentation(name = SubmitFeature.NAME,
description = "It can be used to verify if a user has rights to submit anything.")
public class SubmitFeature implements AuthorizationFeature {
public static final String NAME = "canSubmit";
@Autowired
AuthorizeService authService;
@Autowired
CollectionService collectionService;
@Autowired
Utils utils;
@Override
public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException, SearchServiceException {
if (object instanceof SiteRest) {
// Check whether the user has permission to add to any collection
return collectionService.countCollectionsWithSubmit("", context, null) > 0;
} else if (object instanceof CollectionRest) {
// Check whether the user has permission to add to the given collection
Collection collection = (Collection) utils.getDSpaceAPIObjectFromRest(context, object);
return authService.authorizeActionBoolean(context, collection, Constants.ADD);
}
return false;
}
@Override
public String[] getSupportedTypes() {
return new String[] {
CollectionRest.CATEGORY + "." + CollectionRest.NAME,
SiteRest.CATEGORY + "." + SiteRest.NAME
};
}
}

View File

@@ -31,6 +31,7 @@ import org.dspace.app.rest.model.BaseObjectRest;
import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.core.Context;
import org.dspace.discovery.SearchServiceException;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.service.EPersonService;
import org.slf4j.Logger;
@@ -124,7 +125,7 @@ public class AuthorizationRestRepository extends DSpaceRestRepository<Authorizat
// restore the real current user
context.restoreContextUser();
}
} catch (SQLException e) {
} catch (SQLException | SearchServiceException e) {
throw new RuntimeException(e.getMessage(), e);
}
@@ -151,7 +152,7 @@ public class AuthorizationRestRepository extends DSpaceRestRepository<Authorizat
@SearchRestMethod(name = "object")
public Page<AuthorizationRest> findByObject(@Parameter(value = "uri", required = true) String uri,
@Parameter(value = "eperson") UUID epersonUuid, @Parameter(value = "feature") String featureName,
Pageable pageable) throws AuthorizeException, SQLException {
Pageable pageable) throws AuthorizeException, SQLException, SearchServiceException {
Context context = obtainContext();
@@ -234,7 +235,7 @@ public class AuthorizationRestRepository extends DSpaceRestRepository<Authorizat
Context context,
EPerson user,
String uri,
String featureName) throws SQLException {
String featureName) throws SQLException, SearchServiceException {
BaseObjectRest restObject = utils.getBaseObjectRestFromUri(context, uri);
return authorizationsForObject(context, user, featureName, restObject);
@@ -244,7 +245,7 @@ public class AuthorizationRestRepository extends DSpaceRestRepository<Authorizat
Context context,
EPerson user, String featureName,
BaseObjectRest obj)
throws SQLException {
throws SQLException, SearchServiceException {
if (obj == null) {
return new ArrayList<>();
@@ -269,7 +270,7 @@ public class AuthorizationRestRepository extends DSpaceRestRepository<Authorizat
private List<Authorization> findByObjectAndFeature(
Context context, EPerson user, BaseObjectRest obj, String featureName
) throws SQLException {
) throws SQLException, SearchServiceException {
AuthorizationFeature feature = authorizationFeatureService.find(featureName);

View File

@@ -176,6 +176,24 @@ public class BrowsesResourceControllerIT extends AbstractControllerIntegrationTe
.withSubject("AnotherTest").withSubject("TestingForMore")
.withSubject("ExtraEntry")
.build();
Item withdrawnItem1 = ItemBuilder.createItem(context, col2)
.withTitle("Withdrawn item 1")
.withIssueDate("2016-02-13")
.withAuthor("Smith, Maria").withAuthor("Doe, Jane")
.withSubject("AnotherTest").withSubject("TestingForMore")
.withSubject("ExtraEntry").withSubject("WithdrawnEntry")
.withdrawn()
.build();
Item privateItem1 = ItemBuilder.createItem(context, col2)
.withTitle("Private item 1")
.withIssueDate("2016-02-13")
.withAuthor("Smith, Maria").withAuthor("Doe, Jane")
.withSubject("AnotherTest").withSubject("TestingForMore")
.withSubject("ExtraEntry").withSubject("PrivateEntry")
.makeUnDiscoverable()
.build();
context.restoreAuthSystemState();
@@ -369,6 +387,23 @@ public class BrowsesResourceControllerIT extends AbstractControllerIntegrationTe
.withSubject("AnotherTest")
.build();
Item withdrawnItem1 = ItemBuilder.createItem(context, col2)
.withTitle("Withdrawn item 1")
.withIssueDate("2016-02-13")
.withAuthor("Smith, Maria").withAuthor("Doe, Jane")
.withSubject("AnotherTest").withSubject("TestingForMore")
.withSubject("ExtraEntry").withSubject("WithdrawnEntry")
.withdrawn()
.build();
Item privateItem1 = ItemBuilder.createItem(context, col2)
.withTitle("Private item 1")
.withIssueDate("2016-02-13")
.withAuthor("Smith, Maria").withAuthor("Doe, Jane")
.withSubject("AnotherTest").withSubject("TestingForMore")
.withSubject("ExtraEntry").withSubject("PrivateEntry")
.makeUnDiscoverable()
.build();
context.restoreAuthSystemState();
//** WHEN **
@@ -407,6 +442,276 @@ public class BrowsesResourceControllerIT extends AbstractControllerIntegrationTe
ItemMatcher.matchItemWithTitleAndDateIssued(publicItem1, "zPublic item more", "2017-10-17")
)));
//** WHEN **
//An anonymous user browses the items that correspond with the PrivateEntry subject query
getClient().perform(get("/api/discover/browses/subject/items")
.param("filterValue", "PrivateEntry"))
//** THEN **
//The status has to be 200
.andExpect(status().isOk())
//We expect the content type to be "application/hal+json;charset=UTF-8"
.andExpect(content().contentType(contentType))
//We expect there to be no elements because the item is private
.andExpect(jsonPath("$.page.totalElements", is(0)))
.andExpect(jsonPath("$.page.size", is(20)));
//** WHEN **
//An anonymous user browses the items that correspond with the WithdrawnEntry subject query
getClient().perform(get("/api/discover/browses/subject/items")
.param("filterValue", "WithdrawnEntry"))
//** THEN **
//The status has to be 200
.andExpect(status().isOk())
//We expect the content type to be "application/hal+json;charset=UTF-8"
.andExpect(content().contentType(contentType))
//We expect there to be no elements because the item is withdrawn
.andExpect(jsonPath("$.page.totalElements", is(0)))
.andExpect(jsonPath("$.page.size", is(20)));
}
@Test
public void findBrowseBySubjectItemsWithScope() throws Exception {
context.turnOffAuthorisationSystem();
//** GIVEN **
//1. A community-collection structure with one parent community with sub-community and two collections.
parentCommunity = CommunityBuilder.createCommunity(context)
.withName("Parent Community")
.build();
Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity)
.withName("Sub Community")
.build();
Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build();
Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build();
//2. Two public items with the same subject and another public item that contains that same subject, but also
// another one
// All of the items are readable by an Anonymous user
Item publicItem1 = ItemBuilder.createItem(context, col1)
.withTitle("zPublic item more")
.withIssueDate("2017-10-17")
.withAuthor("Smith, Donald").withAuthor("Doe, John")
.withSubject("ExtraEntry").withSubject("AnotherTest")
.build();
Item publicItem2 = ItemBuilder.createItem(context, col2)
.withTitle("Public item 2")
.withIssueDate("2016-02-13")
.withAuthor("Smith, Maria").withAuthor("Doe, Jane")
.withSubject("AnotherTest")
.build();
Item publicItem3 = ItemBuilder.createItem(context, col2)
.withTitle("Public item 3")
.withIssueDate("2016-02-14")
.withAuthor("Smith, Maria").withAuthor("Doe, Jane")
.withSubject("AnotherTest")
.build();
Item withdrawnItem1 = ItemBuilder.createItem(context, col2)
.withTitle("Withdrawn item 1")
.withIssueDate("2016-02-13")
.withAuthor("Smith, Maria").withAuthor("Doe, Jane")
.withSubject("AnotherTest").withSubject("TestingForMore")
.withSubject("ExtraEntry").withSubject("WithdrawnEntry")
.withdrawn()
.build();
Item privateItem1 = ItemBuilder.createItem(context, col2)
.withTitle("Private item 1")
.withIssueDate("2016-02-13")
.withAuthor("Smith, Maria").withAuthor("Doe, Jane")
.withSubject("AnotherTest").withSubject("TestingForMore")
.withSubject("ExtraEntry").withSubject("PrivateEntry")
.makeUnDiscoverable()
.build();
context.restoreAuthSystemState();
//** WHEN **
//An anonymous user browses the items that correspond with the ExtraEntry subject query
getClient().perform(get("/api/discover/browses/subject/items")
.param("scope", String.valueOf(col2.getID()))
.param("filterValue", "ExtraEntry"))
//** THEN **
//The status has to be 200
.andExpect(status().isOk())
//We expect the content type to be "application/hal+json;charset=UTF-8"
.andExpect(content().contentType(contentType))
//We expect there to be no elements in collection 2
.andExpect(jsonPath("$.page.totalElements", is(0)))
.andExpect(jsonPath("$.page.size", is(20)));
//** WHEN **
//An anonymous user browses the items that correspond with the AnotherTest subject query
getClient().perform(get("/api/discover/browses/subject/items")
.param("scope", String.valueOf(col2.getID()))
.param("filterValue", "AnotherTest"))
//** THEN **
//The status has to be 200
.andExpect(status().isOk())
//We expect the content type to be "application/hal+json;charset=UTF-8"
.andExpect(content().contentType(contentType))
//We expect there to be only two elements, the ones that we've added with the requested subject
// in collection 2
.andExpect(jsonPath("$.page.totalElements", is(2)))
.andExpect(jsonPath("$.page.size", is(20)))
//Verify that the title of the public and embargoed items are present and sorted descending
.andExpect(jsonPath("$._embedded.items", contains(
ItemMatcher.matchItemWithTitleAndDateIssued(publicItem2, "Public item 2", "2016-02-13"),
ItemMatcher.matchItemWithTitleAndDateIssued(publicItem3, "Public item 3", "2016-02-14")
)));
//** WHEN **
//An anonymous user browses the items that correspond with the PrivateEntry subject query
getClient().perform(get("/api/discover/browses/subject/items")
.param("scope", String.valueOf(col2.getID()))
.param("filterValue", "PrivateEntry"))
//** THEN **
//The status has to be 200
.andExpect(status().isOk())
//We expect the content type to be "application/hal+json;charset=UTF-8"
.andExpect(content().contentType(contentType))
//We expect there to be no elements because the item is private
.andExpect(jsonPath("$.page.totalElements", is(0)))
.andExpect(jsonPath("$.page.size", is(20)));
//** WHEN **
//An anonymous user browses the items that correspond with the WithdrawnEntry subject query
getClient().perform(get("/api/discover/browses/subject/items")
.param("scope", String.valueOf(col2.getID()))
.param("filterValue", "WithdrawnEntry"))
//** THEN **
//The status has to be 200
.andExpect(status().isOk())
//We expect the content type to be "application/hal+json;charset=UTF-8"
.andExpect(content().contentType(contentType))
//We expect there to be no elements because the item is withdrawn
.andExpect(jsonPath("$.page.totalElements", is(0)))
.andExpect(jsonPath("$.page.size", is(20)));
}
@Test
public void findBrowseBySubjectItemsWithScopeAsAdmin() throws Exception {
context.turnOffAuthorisationSystem();
//** GIVEN **
//1. A community-collection structure with one parent community with sub-community and two collections.
parentCommunity = CommunityBuilder.createCommunity(context)
.withName("Parent Community")
.build();
Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity)
.withName("Sub Community")
.build();
Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build();
Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build();
//2. Two public items with the same subject and another public item that contains that same subject, but also
// another one
// All of the items are readable by an Anonymous user
Item publicItem1 = ItemBuilder.createItem(context, col1)
.withTitle("zPublic item more")
.withIssueDate("2017-10-17")
.withAuthor("Smith, Donald").withAuthor("Doe, John")
.withSubject("ExtraEntry").withSubject("AnotherTest")
.build();
Item publicItem2 = ItemBuilder.createItem(context, col2)
.withTitle("Public item 2")
.withIssueDate("2016-02-13")
.withAuthor("Smith, Maria").withAuthor("Doe, Jane")
.withSubject("AnotherTest")
.build();
Item publicItem3 = ItemBuilder.createItem(context, col2)
.withTitle("Public item 3")
.withIssueDate("2016-02-14")
.withAuthor("Smith, Maria").withAuthor("Doe, Jane")
.withSubject("AnotherTest")
.build();
Item withdrawnItem1 = ItemBuilder.createItem(context, col2)
.withTitle("Withdrawn item 1")
.withIssueDate("2016-02-13")
.withAuthor("Smith, Maria").withAuthor("Doe, Jane")
.withSubject("AnotherTest").withSubject("TestingForMore")
.withSubject("ExtraEntry").withSubject("WithdrawnEntry")
.withdrawn()
.build();
Item privateItem1 = ItemBuilder.createItem(context, col2)
.withTitle("Private item 1")
.withIssueDate("2016-02-13")
.withAuthor("Smith, Maria").withAuthor("Doe, Jane")
.withSubject("AnotherTest").withSubject("TestingForMore")
.withSubject("ExtraEntry").withSubject("PrivateEntry")
.makeUnDiscoverable()
.build();
context.restoreAuthSystemState();
String adminToken = getAuthToken(admin.getEmail(), password);
//** WHEN **
//An admin user browses the items that correspond with the ExtraEntry subject query
getClient(adminToken).perform(get("/api/discover/browses/subject/items")
.param("scope", String.valueOf(col2.getID()))
.param("filterValue", "ExtraEntry"))
//** THEN **
//The status has to be 200
.andExpect(status().isOk())
//We expect the content type to be "application/hal+json;charset=UTF-8"
.andExpect(content().contentType(contentType))
//We expect there to be no elements in collection 2
.andExpect(jsonPath("$.page.totalElements", is(0)))
.andExpect(jsonPath("$.page.size", is(20)));
//** WHEN **
//An admin user browses the items that correspond with the AnotherTest subject query
getClient(adminToken).perform(get("/api/discover/browses/subject/items")
.param("scope", String.valueOf(col2.getID()))
.param("filterValue", "AnotherTest"))
//** THEN **
//The status has to be 200
.andExpect(status().isOk())
//We expect the content type to be "application/hal+json;charset=UTF-8"
.andExpect(content().contentType(contentType))
//We expect there to be only two elements, the ones that we've added with the requested subject
// in collection 2
.andExpect(jsonPath("$.page.totalElements", is(2)))
.andExpect(jsonPath("$.page.size", is(20)))
//Verify that the title of the public and embargoed items are present and sorted descending
.andExpect(jsonPath("$._embedded.items", contains(
ItemMatcher.matchItemWithTitleAndDateIssued(publicItem2, "Public item 2", "2016-02-13"),
ItemMatcher.matchItemWithTitleAndDateIssued(publicItem3, "Public item 3", "2016-02-14")
)));
//** WHEN **
//An admin user browses the items that correspond with the PrivateEntry subject query
getClient(adminToken).perform(get("/api/discover/browses/subject/items")
.param("scope", String.valueOf(col2.getID()))
.param("filterValue", "PrivateEntry"))
//** THEN **
//The status has to be 200
.andExpect(status().isOk())
//We expect the content type to be "application/hal+json;charset=UTF-8"
.andExpect(content().contentType(contentType))
//We expect there to be no elements because the item is private
.andExpect(jsonPath("$.page.totalElements", is(0)))
.andExpect(jsonPath("$.page.size", is(20)));
//** WHEN **
//An admin user browses the items that correspond with the WithdrawnEntry subject query
getClient(adminToken).perform(get("/api/discover/browses/subject/items")
.param("scope", String.valueOf(col2.getID()))
.param("filterValue", "WithdrawnEntry"))
//** THEN **
//The status has to be 200
.andExpect(status().isOk())
//We expect the content type to be "application/hal+json;charset=UTF-8"
.andExpect(content().contentType(contentType))
//We expect there to be no elements because the item is withdrawn
.andExpect(jsonPath("$.page.totalElements", is(0)))
.andExpect(jsonPath("$.page.size", is(20)));
}
@Test
@@ -501,6 +806,174 @@ public class BrowsesResourceControllerIT extends AbstractControllerIntegrationTe
.andExpect(jsonPath("$._embedded.items[*].metadata", Matchers.allOf(
not(matchMetadata("dc.title", "This is a private item")),
not(matchMetadata("dc.title", "Internal publication")))));
String adminToken = getAuthToken(admin.getEmail(), password);
//** WHEN **
//An anonymous user browses the items in the Browse by item endpoint
//sorted descending by tile
getClient(adminToken).perform(get("/api/discover/browses/title/items")
.param("sort", "title,desc"))
//** THEN **
//The status has to be 200 OK
.andExpect(status().isOk())
//We expect the content type to be "application/hal+json;charset=UTF-8"
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$.page.size", is(20)))
.andExpect(jsonPath("$.page.totalElements", is(4)))
.andExpect(jsonPath("$.page.totalPages", is(1)))
.andExpect(jsonPath("$.page.number", is(0)))
.andExpect(jsonPath("$._embedded.items",
contains(ItemMatcher.matchItemWithTitleAndDateIssued(publicItem2,
"Public item 2",
"2016-02-13"),
ItemMatcher.matchItemWithTitleAndDateIssued(publicItem1,
"Public item 1",
"2017-10-17"),
ItemMatcher.matchItemWithTitleAndDateIssued(internalItem,
"Internal publication",
"2016-09-19"),
ItemMatcher.matchItemWithTitleAndDateIssued(embargoedItem,
"An embargoed publication",
"2017-08-10")
)))
//The private and internal items must not be present
.andExpect(jsonPath("$._embedded.items[*].metadata", Matchers.allOf(
not(matchMetadata("dc.title", "This is a private item")),
not(matchMetadata("dc.title", "Internal publication")))));
}
@Test
public void findBrowseByTitleItemsWithScope() throws Exception {
context.turnOffAuthorisationSystem();
//** GIVEN **
//1. A community-collection structure with one parent community with sub-community and two collections.
parentCommunity = CommunityBuilder.createCommunity(context)
.withName("Parent Community")
.build();
Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity)
.withName("Sub Community")
.build();
Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build();
Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build();
//2. Two public items that are readable by Anonymous
Item publicItem1 = ItemBuilder.createItem(context, col1)
.withTitle("Public item 1")
.withIssueDate("2017-10-17")
.withAuthor("Smith, Donald").withAuthor("Doe, John")
.withSubject("Java").withSubject("Unit Testing")
.build();
Item publicItem2 = ItemBuilder.createItem(context, col2)
.withTitle("Public item 2")
.withIssueDate("2016-02-13")
.withAuthor("Smith, Maria").withAuthor("Doe, Jane")
.withSubject("Angular").withSubject("Unit Testing")
.build();
//3. An item that has been made private
Item privateItem = ItemBuilder.createItem(context, col2)
.withTitle("This is a private item")
.withIssueDate("2015-03-12")
.withAuthor("Duck, Donald")
.withSubject("Cartoons").withSubject("Ducks")
.makeUnDiscoverable()
.build();
//4. An item with an item-level embargo
Item embargoedItem = ItemBuilder.createItem(context, col2)
.withTitle("An embargoed publication")
.withIssueDate("2017-08-10")
.withAuthor("Mouse, Mickey")
.withSubject("Cartoons").withSubject("Mice")
.withEmbargoPeriod("12 months")
.build();
//5. An item that is only readable for an internal groups
Group internalGroup = GroupBuilder.createGroup(context)
.withName("Internal Group")
.build();
Item internalItem = ItemBuilder.createItem(context, col2)
.withTitle("Internal publication")
.withIssueDate("2016-09-19")
.withAuthor("Doe, John")
.withSubject("Unknown")
.withReaderGroup(internalGroup)
.build();
context.restoreAuthSystemState();
//** WHEN **
//An anonymous user browses the items in the Browse by item endpoint
//sorted descending by tile
getClient().perform(get("/api/discover/browses/title/items")
.param("scope", String.valueOf(col2.getID()))
.param("sort", "title,desc"))
//** THEN **
//The status has to be 200 OK
.andExpect(status().isOk())
//We expect the content type to be "application/hal+json;charset=UTF-8"
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$.page.size", is(20)))
.andExpect(jsonPath("$.page.totalElements", is(1)))
.andExpect(jsonPath("$.page.totalPages", is(1)))
.andExpect(jsonPath("$.page.number", is(0)))
.andExpect(jsonPath("$._embedded.items",
contains(ItemMatcher.matchItemWithTitleAndDateIssued(publicItem2,
"Public item 2",
"2016-02-13"))))
//The private and internal items must not be present
.andExpect(jsonPath("$._embedded.items[*].metadata", Matchers.allOf(
not(matchMetadata("dc.title", "This is a private item")),
not(matchMetadata("dc.title", "Internal publication")))));
String adminToken = getAuthToken(admin.getEmail(), password);
//** WHEN **
//An admin user browses the items in the Browse by item endpoint
//sorted descending by tile
getClient(adminToken).perform(get("/api/discover/browses/title/items")
.param("scope", String.valueOf(col2.getID()))
.param("sort", "title,desc"))
//** THEN **
//The status has to be 200 OK
.andExpect(status().isOk())
//We expect the content type to be "application/hal+json;charset=UTF-8"
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$.page.size", is(20)))
.andExpect(jsonPath("$.page.totalElements", is(3)))
.andExpect(jsonPath("$.page.totalPages", is(1)))
.andExpect(jsonPath("$.page.number", is(0)))
.andExpect(jsonPath("$._embedded.items", contains(
ItemMatcher.matchItemWithTitleAndDateIssued(publicItem2,
"Public item 2",
"2016-02-13"),
ItemMatcher.matchItemWithTitleAndDateIssued(internalItem,
"Internal publication",
"2016-09-19"),
ItemMatcher.matchItemWithTitleAndDateIssued(embargoedItem,
"An embargoed publication",
"2017-08-10")
)))
//The private and internal items must not be present
.andExpect(jsonPath("$._embedded.items[*].metadata", Matchers.allOf(
not(matchMetadata("dc.title", "This is a private item"))
)));
}
@Test
@@ -623,6 +1096,18 @@ public class BrowsesResourceControllerIT extends AbstractControllerIntegrationTe
.withIssueDate("2016-01-12")
.build();
Item withdrawnItem1 = ItemBuilder.createItem(context, col2)
.withTitle("Withdrawn item 1")
.withIssueDate("2016-02-13")
.withdrawn()
.build();
Item privateItem1 = ItemBuilder.createItem(context, col2)
.withTitle("Private item 1")
.makeUnDiscoverable()
.build();
context.restoreAuthSystemState();
//** WHEN **
@@ -682,6 +1167,159 @@ public class BrowsesResourceControllerIT extends AbstractControllerIntegrationTe
ItemMatcher.matchItemWithTitleAndDateIssued(item7,
"Item 7", "2016-01-12")
)));
String adminToken = getAuthToken(admin.getEmail(), password);
//The next page gives us the last two items
getClient(adminToken).perform(get("/api/discover/browses/dateissued/items")
.param("sort", "title,asc")
.param("size", "5")
.param("page", "1"))
//The status has to be 200 OK
.andExpect(status().isOk())
//We expect the content type to be "application/hal+json;charset=UTF-8"
.andExpect(content().contentType(contentType))
//We expect only the first five items to be present
.andExpect(jsonPath("$.page.size", is(5)))
.andExpect(jsonPath("$.page.totalElements", is(7)))
.andExpect(jsonPath("$.page.totalPages", is(2)))
.andExpect(jsonPath("$.page.number", is(1)))
//Verify that the title and date of the items match and that they are sorted ascending
.andExpect(jsonPath("$._embedded.items",
contains(ItemMatcher.matchItemWithTitleAndDateIssued(item6,
"Item 6", "2016-01-13"),
ItemMatcher.matchItemWithTitleAndDateIssued(item7,
"Item 7", "2016-01-12")
)));
}
@Test
public void testPaginationBrowseByDateIssuedItemsWithScope() throws Exception {
context.turnOffAuthorisationSystem();
//** GIVEN **
//1. A community-collection structure with one parent community with sub-community and two collections.
parentCommunity = CommunityBuilder.createCommunity(context)
.withName("Parent Community")
.build();
Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity)
.withName("Sub Community")
.build();
Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build();
Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build();
//2. 7 public items that are readable by Anonymous
Item item1 = ItemBuilder.createItem(context, col1)
.withTitle("Item 1")
.withIssueDate("2017-10-17")
.build();
Item item2 = ItemBuilder.createItem(context, col2)
.withTitle("Item 2")
.withIssueDate("2016-02-13")
.build();
Item item3 = ItemBuilder.createItem(context, col1)
.withTitle("Item 3")
.withIssueDate("2016-02-12")
.build();
Item item4 = ItemBuilder.createItem(context, col2)
.withTitle("Item 4")
.withIssueDate("2016-02-11")
.build();
Item item5 = ItemBuilder.createItem(context, col1)
.withTitle("Item 5")
.withIssueDate("2016-02-10")
.build();
Item item6 = ItemBuilder.createItem(context, col2)
.withTitle("Item 6")
.withIssueDate("2016-01-13")
.build();
Item item7 = ItemBuilder.createItem(context, col1)
.withTitle("Item 7")
.withIssueDate("2016-01-12")
.build();
Item withdrawnItem1 = ItemBuilder.createItem(context, col2)
.withTitle("Withdrawn item 1")
.withIssueDate("2016-02-13")
.withdrawn()
.build();
Item privateItem1 = ItemBuilder.createItem(context, col2)
.withTitle("Private item 1")
.makeUnDiscoverable()
.build();
context.restoreAuthSystemState();
//** WHEN **
//An anonymous user browses the items in the Browse by date issued endpoint
//sorted ascending by tile with a page size of 5
getClient().perform(get("/api/discover/browses/dateissued/items")
.param("scope", String.valueOf(col2.getID()))
.param("sort", "title,asc")
.param("size", "5"))
//** THEN **
//The status has to be 200 OK
.andExpect(status().isOk())
//We expect the content type to be "application/hal+json;charset=UTF-8"
.andExpect(content().contentType(contentType))
//We expect only the first five items to be present
.andExpect(jsonPath("$.page.size", is(5)))
.andExpect(jsonPath("$.page.totalElements", is(3)))
.andExpect(jsonPath("$.page.totalPages", is(1)))
.andExpect(jsonPath("$.page.number", is(0)))
//Verify that the title and date of the items match and that they are sorted ascending
.andExpect(jsonPath("$._embedded.items",
contains(
ItemMatcher.matchItemWithTitleAndDateIssued(item2,
"Item 2", "2016-02-13"),
ItemMatcher.matchItemWithTitleAndDateIssued(item4,
"Item 4", "2016-02-11"),
ItemMatcher.matchItemWithTitleAndDateIssued(item6,
"Item 6", "2016-01-13")
)));
String adminToken = getAuthToken(admin.getEmail(), password);
getClient(adminToken).perform(get("/api/discover/browses/dateissued/items")
.param("scope", String.valueOf(col2.getID()))
.param("sort", "title,asc")
.param("size", "5"))
//** THEN **
//The status has to be 200 OK
.andExpect(status().isOk())
//We expect the content type to be "application/hal+json;charset=UTF-8"
.andExpect(content().contentType(contentType))
//We expect only the first five items to be present
.andExpect(jsonPath("$.page.size", is(5)))
.andExpect(jsonPath("$.page.totalElements", is(3)))
.andExpect(jsonPath("$.page.totalPages", is(1)))
.andExpect(jsonPath("$.page.number", is(0)))
//Verify that the title and date of the items match and that they are sorted ascending
.andExpect(jsonPath("$._embedded.items",
contains(
ItemMatcher.matchItemWithTitleAndDateIssued(item2,
"Item 2", "2016-02-13"),
ItemMatcher.matchItemWithTitleAndDateIssued(item4,
"Item 4", "2016-02-11"),
ItemMatcher.matchItemWithTitleAndDateIssued(item6,
"Item 6", "2016-01-13")
)));
}
@Test

View File

@@ -0,0 +1,279 @@
/**
* 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.authorization;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.concurrent.Callable;
import org.dspace.app.rest.authorization.impl.EditItemFeature;
import org.dspace.app.rest.converter.ItemConverter;
import org.dspace.app.rest.converter.SiteConverter;
import org.dspace.app.rest.model.ItemRest;
import org.dspace.app.rest.model.SiteRest;
import org.dspace.app.rest.projection.DefaultProjection;
import org.dspace.app.rest.test.AbstractControllerIntegrationTest;
import org.dspace.app.rest.utils.Utils;
import org.dspace.authorize.ResourcePolicy;
import org.dspace.builder.CollectionBuilder;
import org.dspace.builder.CommunityBuilder;
import org.dspace.builder.GroupBuilder;
import org.dspace.builder.ItemBuilder;
import org.dspace.builder.ResourcePolicyBuilder;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.content.Item;
import org.dspace.content.Site;
import org.dspace.content.service.SiteService;
import org.dspace.core.Constants;
import org.dspace.eperson.Group;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.web.servlet.ResultActions;
public class EditItemFeatureIT extends AbstractControllerIntegrationTest {
@Autowired
private AuthorizationFeatureService authorizationFeatureService;
@Autowired
private SiteService siteService;
@Autowired
private ItemConverter itemConverter;
@Autowired
private SiteConverter siteConverter;
@Autowired
private Utils utils;
private Group group;
private String siteUri;
private String epersonToken;
private AuthorizationFeature editItemFeature;
private Community communityA;
private Collection collectionA1;
private Collection collectionA2;
private Item itemA1X;
private Item itemA2X;
@Override
@Before
public void setUp() throws Exception {
super.setUp();
withSuppressedAuthorization(() -> {
communityA = CommunityBuilder.createCommunity(context).withName("Community A").build();
collectionA1 = CollectionBuilder.createCollection(context, communityA).withName("Collection A1").build();
collectionA2 = CollectionBuilder.createCollection(context, communityA).withName("Collection A2").build();
itemA1X = ItemBuilder.createItem(context, collectionA1).withTitle("Item A1X").build();
itemA2X = ItemBuilder.createItem(context, collectionA2).withTitle("Item A2X").build();
group = GroupBuilder.createGroup(context)
.withName("group")
.addMember(eperson)
.build();
return null;
});
editItemFeature = authorizationFeatureService.find(EditItemFeature.NAME);
Site site = siteService.findSite(context);
SiteRest siteRest = siteConverter.convert(site, DefaultProjection.DEFAULT);
siteUri = utils.linkToSingleResource(siteRest, "self").getHref();
}
@Test
public void testNoRights() throws Exception {
expectZeroResults(requestSitewideEditItemFeature());
}
@Test
public void testDirectEPersonWritePolicy() throws Exception {
ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context)
.withUser(eperson)
.withDspaceObject(itemA1X)
.withAction(Constants.WRITE)
.build();
expectSomeResults(requestSitewideEditItemFeature());
expectSomeResults(requestEditItemFeature(itemA1X));
expectZeroResults(requestEditItemFeature(itemA2X));
}
@Test
public void testDirectGroupWritePolicy() throws Exception {
ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context)
.withGroup(group)
.withDspaceObject(itemA1X)
.withAction(Constants.WRITE)
.build();
expectSomeResults(requestSitewideEditItemFeature());
expectSomeResults(requestEditItemFeature(itemA1X));
expectZeroResults(requestEditItemFeature(itemA2X));
}
@Test
public void testDirectEPersonAdminPolicy() throws Exception {
ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context)
.withUser(eperson)
.withDspaceObject(itemA1X)
.withAction(Constants.ADMIN)
.build();
expectSomeResults(requestSitewideEditItemFeature());
expectSomeResults(requestEditItemFeature(itemA1X));
expectZeroResults(requestEditItemFeature(itemA2X));
}
@Test
public void testDirectGroupAdminPolicy() throws Exception {
ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context)
.withGroup(group)
.withDspaceObject(itemA1X)
.withAction(Constants.ADMIN)
.build();
expectSomeResults(requestSitewideEditItemFeature());
expectSomeResults(requestEditItemFeature(itemA1X));
expectZeroResults(requestEditItemFeature(itemA2X));
}
@Test
public void testNonemptyCollectionAdmin() throws Exception {
Item item = withSuppressedAuthorization(() -> {
Collection col = CollectionBuilder
.createCollection(context, communityA)
.withName("nonempty collection")
.withAdminGroup(eperson)
.build();
return ItemBuilder
.createItem(context, col)
.withTitle("item in nonempty collection")
.build();
});
expectSomeResults(requestSitewideEditItemFeature());
expectSomeResults(requestEditItemFeature(item));
expectZeroResults(requestEditItemFeature(itemA1X));
expectZeroResults(requestEditItemFeature(itemA2X));
}
@Test
public void testEmptyCollectionAdmin() throws Exception {
withSuppressedAuthorization(() -> {
Collection col = CollectionBuilder
.createCollection(context, communityA)
.withName("nonempty collection")
.withAdminGroup(eperson)
.build();
return null;
});
expectZeroResults(requestSitewideEditItemFeature());
}
@Test
public void testCommunityWithEmptyCollectionAdmin() throws Exception {
withSuppressedAuthorization(() -> {
Community comm = CommunityBuilder
.createCommunity(context)
.withName("This community contains a collection")
.withAdminGroup(eperson)
.build();
Collection coll = CollectionBuilder
.createCollection(context, comm)
.withName("This collection contains no items")
.build();
return null;
});
expectZeroResults(requestSitewideEditItemFeature());
}
@Test
public void testCommunityWithNonemptyCollectionAdmin() throws Exception {
Item item = withSuppressedAuthorization(() -> {
Community comm = CommunityBuilder
.createCommunity(context)
.withName("This community contains a collection")
.withAdminGroup(eperson)
.build();
Collection coll = CollectionBuilder
.createCollection(context, comm)
.withName("This collection contains an item")
.build();
return ItemBuilder
.createItem(context, coll)
.withTitle("This is an item")
.build();
});
expectSomeResults(requestSitewideEditItemFeature());
expectSomeResults(requestEditItemFeature(item));
}
@Test
public void testNestedCommunitiesWithNonemptyCollectionAdmin() throws Exception {
Item item = withSuppressedAuthorization(() -> {
Community parent = CommunityBuilder
.createCommunity(context)
.withName("parent community")
.withAdminGroup(eperson)
.build();
Community child = CommunityBuilder
.createSubCommunity(context, parent)
.withName("child community")
.withAdminGroup(eperson)
.build();
Collection coll = CollectionBuilder
.createCollection(context, child)
.withName("This collection contains an item")
.build();
return ItemBuilder
.createItem(context, coll)
.withTitle("This is an item")
.build();
});
expectSomeResults(requestSitewideEditItemFeature());
expectSomeResults(requestEditItemFeature(item));
}
private ResultActions requestSitewideEditItemFeature() throws Exception {
return requestEditItemFeature(siteUri);
}
private ResultActions requestEditItemFeature(Item item) throws Exception {
return requestEditItemFeature(getItemUri(item));
}
private ResultActions requestEditItemFeature(String uri) throws Exception {
epersonToken = getAuthToken(eperson.getEmail(), password);
return getClient(epersonToken).perform(get("/api/authz/authorizations/search/object?")
.param("uri", uri)
.param("feature", editItemFeature.getName())
.param("embed", "feature"));
}
private ResultActions expectSomeResults(ResultActions actions) throws Exception {
return actions
.andExpect(status().isOk())
.andExpect(jsonPath("$.page.totalElements", greaterThan(0)));
}
private ResultActions expectZeroResults(ResultActions actions) throws Exception {
return actions
.andExpect(status().isOk())
.andExpect(jsonPath("$.page.totalElements", is(0)));
}
private <T> T withSuppressedAuthorization(Callable<T> fn) throws Exception {
context.turnOffAuthorisationSystem();
T result = fn.call();
context.restoreAuthSystemState();
return result;
}
private String getItemUri(Item item) {
ItemRest itemRest = itemConverter.convert(item, DefaultProjection.DEFAULT);
return utils.linkToSingleResource(itemRest, "self").getHref();
}
}

View File

@@ -0,0 +1,328 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.authorization;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.concurrent.Callable;
import org.dspace.app.rest.authorization.impl.SubmitFeature;
import org.dspace.app.rest.converter.CollectionConverter;
import org.dspace.app.rest.converter.SiteConverter;
import org.dspace.app.rest.model.CollectionRest;
import org.dspace.app.rest.model.SiteRest;
import org.dspace.app.rest.projection.DefaultProjection;
import org.dspace.app.rest.test.AbstractControllerIntegrationTest;
import org.dspace.app.rest.utils.Utils;
import org.dspace.authorize.ResourcePolicy;
import org.dspace.builder.CollectionBuilder;
import org.dspace.builder.CommunityBuilder;
import org.dspace.builder.GroupBuilder;
import org.dspace.builder.ResourcePolicyBuilder;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.content.Site;
import org.dspace.content.service.SiteService;
import org.dspace.core.Constants;
import org.dspace.eperson.Group;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.web.servlet.ResultActions;
public class SubmitFeatureIT extends AbstractControllerIntegrationTest {
@Autowired
private AuthorizationFeatureService authorizationFeatureService;
@Autowired
private SiteService siteService;
@Autowired
private CollectionConverter collectionConverter;
@Autowired
private SiteConverter siteConverter;
@Autowired
private Utils utils;
private Group group;
private String siteUri;
private String epersonToken;
private AuthorizationFeature submitFeature;
private Community communityA;
private Collection collectionA1;
private Collection collectionA2;
@Override
@Before
public void setUp() throws Exception {
super.setUp();
withSuppressedAuthorization(() -> {
communityA = CommunityBuilder.createCommunity(context).withName("Community A").build();
collectionA1 = CollectionBuilder.createCollection(context, communityA).withName("Collection A1").build();
collectionA2 = CollectionBuilder.createCollection(context, communityA).withName("Collection A2").build();
group = GroupBuilder.createGroup(context)
.withName("group")
.addMember(eperson)
.build();
return null;
});
submitFeature = authorizationFeatureService.find(SubmitFeature.NAME);
Site site = siteService.findSite(context);
SiteRest siteRest = siteConverter.convert(site, DefaultProjection.DEFAULT);
siteUri = utils.linkToSingleResource(siteRest, "self").getHref();
}
@Test
public void testNoRights() throws Exception {
expectZeroResults(requestSitewideSubmitFeature());
}
@Test
public void testDirectEPersonAddPolicy() throws Exception {
ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context)
.withUser(eperson)
.withDspaceObject(collectionA1)
.withAction(Constants.ADD)
.build();
expectSomeResults(requestSitewideSubmitFeature());
}
@Test
public void testDirectGroupAddPolicy() throws Exception {
ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context)
.withGroup(group)
.withDspaceObject(collectionA1)
.withAction(Constants.ADD)
.build();
expectSomeResults(requestSitewideSubmitFeature());
}
@Test
public void testDirectEPersonAdminPolicy() throws Exception {
ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context)
.withUser(eperson)
.withDspaceObject(collectionA1)
.withAction(Constants.ADMIN)
.build();
expectSomeResults(requestSitewideSubmitFeature());
}
@Test
public void testDirectGroupAdminPolicy() throws Exception {
ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context)
.withGroup(group)
.withDspaceObject(collectionA1)
.withAction(Constants.ADMIN)
.build();
expectSomeResults(requestSitewideSubmitFeature());
}
@Test
public void testCollectionAdmin() throws Exception {
withSuppressedAuthorization(() -> {
Collection col = CollectionBuilder
.createCollection(context, communityA)
.withName("this is another test collection")
.withAdminGroup(eperson)
.build();
return null;
});
expectSomeResults(requestSitewideSubmitFeature());
}
@Test
public void testCommunityWithoutCollectionsAdmin() throws Exception {
withSuppressedAuthorization(() -> {
Community comm = CommunityBuilder
.createCommunity(context)
.withName("This community contains no collections")
.withAdminGroup(eperson)
.build();
return null;
});
expectZeroResults(requestSitewideSubmitFeature());
}
@Test
public void testCommunityWithCollectionsAdmin() throws Exception {
withSuppressedAuthorization(() -> {
Community comm = CommunityBuilder
.createCommunity(context)
.withName("This community contains a collection")
.withAdminGroup(eperson)
.build();
Collection coll = CollectionBuilder
.createCollection(context, comm)
.withName("Contained collection")
.build();
return null;
});
expectSomeResults(requestSitewideSubmitFeature());
}
@Test
public void testCommunityWithSubCommunityWithCollectionsAdmin() throws Exception {
withSuppressedAuthorization(() -> {
Community parent = CommunityBuilder
.createCommunity(context)
.withName("This community contains no collections")
.withAdminGroup(eperson)
.build();
Community child = CommunityBuilder
.createSubCommunity(context, parent)
.withName("This community contains a collection")
.build();
Collection coll = CollectionBuilder
.createCollection(context, child)
.withName("Contained collection")
.build();
return null;
});
expectSomeResults(requestSitewideSubmitFeature());
}
@Test
public void testNoRightsOnCollection() throws Exception {
expectZeroResults(requestSubmitFeature(getCollectionUri(collectionA1)));
expectZeroResults(requestSubmitFeature(getCollectionUri(collectionA2)));
}
@Test
public void testDirectEPersonAddPolicyOnCollection() throws Exception {
ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context)
.withUser(eperson)
.withDspaceObject(collectionA1)
.withAction(Constants.ADD)
.build();
expectSomeResults(requestSubmitFeature(getCollectionUri(collectionA1)));
expectZeroResults(requestSubmitFeature(getCollectionUri(collectionA2)));
}
@Test
public void testDirectGroupAddPolicyOnCollection() throws Exception {
ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context)
.withGroup(group)
.withDspaceObject(collectionA1)
.withAction(Constants.ADD)
.build();
expectSomeResults(requestSubmitFeature(getCollectionUri(collectionA1)));
expectZeroResults(requestSubmitFeature(getCollectionUri(collectionA2)));
}
@Test
public void testDirectEPersonAdminPolicyOnCollection() throws Exception {
ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context)
.withUser(eperson)
.withDspaceObject(collectionA1)
.withAction(Constants.ADMIN)
.build();
expectSomeResults(requestSubmitFeature(getCollectionUri(collectionA1)));
expectZeroResults(requestSubmitFeature(getCollectionUri(collectionA2)));
}
@Test
public void testDirectGroupAdminPolicyOnCollection() throws Exception {
ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context)
.withGroup(group)
.withDspaceObject(collectionA1)
.withAction(Constants.ADMIN)
.build();
expectSomeResults(requestSubmitFeature(getCollectionUri(collectionA1)));
expectZeroResults(requestSubmitFeature(getCollectionUri(collectionA2)));
}
@Test
public void testCollectionAdminOnCollection() throws Exception {
Collection col = withSuppressedAuthorization(() -> {
return CollectionBuilder
.createCollection(context, communityA)
.withName("this is another test collection")
.withAdminGroup(eperson)
.build();
});
expectSomeResults(requestSubmitFeature(getCollectionUri(col)));
expectZeroResults(requestSubmitFeature(getCollectionUri(collectionA1)));
}
@Test
public void testCommunityWithCollectionsAdminOnCollection() throws Exception {
Collection coll = withSuppressedAuthorization(() -> {
Community comm = CommunityBuilder
.createCommunity(context)
.withName("This community contains a collection")
.withAdminGroup(eperson)
.build();
return CollectionBuilder
.createCollection(context, comm)
.withName("Contained collection")
.build();
});
expectSomeResults(requestSubmitFeature(getCollectionUri(coll)));
}
@Test
public void testCommunityWithSubCommunityWithCollectionsAdminOnCollection() throws Exception {
Collection coll = withSuppressedAuthorization(() -> {
Community parent = CommunityBuilder
.createCommunity(context)
.withName("This community contains no collections")
.withAdminGroup(eperson)
.build();
Community child = CommunityBuilder
.createSubCommunity(context, parent)
.withName("This community contains a collection")
.build();
return CollectionBuilder
.createCollection(context, child)
.withName("Contained collection")
.build();
});
expectSomeResults(requestSubmitFeature(getCollectionUri(coll)));
}
private ResultActions requestSitewideSubmitFeature() throws Exception {
return requestSubmitFeature(siteUri);
}
private ResultActions requestSubmitFeature(String uri) throws Exception {
epersonToken = getAuthToken(eperson.getEmail(), password);
return getClient(epersonToken).perform(get("/api/authz/authorizations/search/object?")
.param("uri", uri)
.param("feature", submitFeature.getName())
.param("embed", "feature"));
}
private ResultActions expectSomeResults(ResultActions actions) throws Exception {
return actions
.andExpect(status().isOk())
.andExpect(jsonPath("$.page.totalElements", greaterThan(0)));
}
private ResultActions expectZeroResults(ResultActions actions) throws Exception {
return actions
.andExpect(status().isOk())
.andExpect(jsonPath("$.page.totalElements", is(0)));
}
private <T> T withSuppressedAuthorization(Callable<T> fn) throws Exception {
context.turnOffAuthorisationSystem();
T result = fn.call();
context.restoreAuthSystemState();
return result;
}
private String getCollectionUri(Collection collection) {
CollectionRest collectionRest = collectionConverter.convert(collection, DefaultProjection.DEFAULT);
return utils.linkToSingleResource(collectionRest, "self").getHref();
}
}

View File

@@ -31,6 +31,7 @@
<bean id="solrServicePrivateItemPlugin" class="org.dspace.discovery.SolrServicePrivateItemPlugin" scope="prototype"/>
<bean id="SolrServiceParentObjectIndexingPlugin" class="org.dspace.discovery.SolrServiceParentObjectIndexingPlugin" scope="prototype"/>
<bean id="SolrServiceIndexCollectionSubmittersPlugin" class="org.dspace.discovery.SolrServiceIndexCollectionSubmittersPlugin" scope="prototype"/>
<bean id="SolrServiceIndexItemEditorsPlugin" class="org.dspace.discovery.SolrServiceIndexItemEditorsPlugin" scope="prototype"/>
<alias name="solrServiceResourceIndexPlugin" alias="org.dspace.discovery.SolrServiceResourceRestrictionPlugin"/>

View File

@@ -264,6 +264,9 @@
<!-- used to track which group(s) have submit permissions -->
<field name="submit" type="string" indexed="true" stored="true" omitNorms="true" multiValued="true" docValues="true" />
<!-- used to track which eperson(s) have edit permissions on items -->
<field name="edit" type="string" indexed="true" stored="true" omitNorms="true" multiValued="true" docValues="true" />
<!-- used to track entity type of collections -->
<field name="search.entitytype" type="string" indexed="true" stored="true" required="false" />