diff --git a/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicyServiceImpl.java b/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicyServiceImpl.java index 86998a2196..08a8a1463c 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicyServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicyServiceImpl.java @@ -19,6 +19,7 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.logging.log4j.Logger; import org.dspace.authorize.dao.ResourcePolicyDAO; +import org.dspace.authorize.service.AuthorizeService; import org.dspace.authorize.service.ResourcePolicyService; import org.dspace.content.DSpaceObject; import org.dspace.content.factory.ContentServiceFactory; @@ -51,6 +52,9 @@ public class ResourcePolicyServiceImpl implements ResourcePolicyService { @Autowired private GroupService groupService; + @Autowired + private AuthorizeService authorizeService; + protected ResourcePolicyServiceImpl() { } @@ -422,6 +426,6 @@ public class ResourcePolicyServiceImpl implements ResourcePolicyService { } else if (group != null && groupService.isMember(context, eperson, group)) { isMy = true; } - return isMy; + return isMy || authorizeService.isAdmin(context, eperson, resourcePolicy.getdSpaceObject()); } } diff --git a/dspace-api/src/main/java/org/dspace/core/Utils.java b/dspace-api/src/main/java/org/dspace/core/Utils.java index ea9ed57eca..a1294c3317 100644 --- a/dspace-api/src/main/java/org/dspace/core/Utils.java +++ b/dspace-api/src/main/java/org/dspace/core/Utils.java @@ -506,4 +506,5 @@ public final class Utils { ConfigurationService config = DSpaceServicesFactory.getInstance().getConfigurationService(); return StringSubstitutor.replace(string, config.getProperties()); } + } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResourcePolicyRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResourcePolicyRestRepository.java index a79a9fe4ea..c0341f15e3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResourcePolicyRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResourcePolicyRestRepository.java @@ -25,6 +25,7 @@ import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.ResourcePolicyRest; import org.dspace.app.rest.model.patch.Patch; import org.dspace.app.rest.repository.patch.ResourcePatch; +import org.dspace.app.rest.security.DSpacePermissionEvaluator; import org.dspace.app.rest.utils.DSpaceObjectUtils; import org.dspace.app.rest.utils.Utils; import org.dspace.authorize.AuthorizeException; @@ -44,6 +45,8 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.hateoas.Link; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; /** @@ -73,6 +76,9 @@ public class ResourcePolicyRestRepository extends DSpaceRestRepository resourcePatch; + @Autowired + private DSpacePermissionEvaluator permissionEvaluator; + @Autowired DiscoverableEndpointsService discoverableEndpointsService; @@ -222,14 +228,13 @@ public class ResourcePolicyRestRepository extends DSpaceRestRepository idRef = new AtomicReference(); + + try { + // submitter can't create policy + getClient(authSubmitterToken).perform(post("/api/authz/resourcepolicies") + .content(new ObjectMapper().writeValueAsBytes(resourcePolicyRest)) + .param("resource", bitstream.getID().toString()) + .param("eperson", submitter.getID().toString()) + .param("projections", "full") + .contentType(contentType)) + .andExpect(status().isForbidden()); + + // other collection admin can't create policy for other collection + getClient(authcolAdmin2Token).perform(post("/api/authz/resourcepolicies") + .content(new ObjectMapper().writeValueAsBytes(resourcePolicyRest)) + .param("resource", bitstream.getID().toString()) + .param("eperson", submitter.getID().toString()) + .param("projections", "full") + .contentType(contentType)) + .andExpect(status().isForbidden()); + + // create policy for submitter by collection admin + getClient(authcolAdminToken).perform(post("/api/authz/resourcepolicies") + .content(new ObjectMapper().writeValueAsBytes(resourcePolicyRest)) + .param("resource", bitstream.getID().toString()) + .param("eperson", submitter.getID().toString()) + .param("projections", "full") + .contentType(contentType)) + .andExpect(status().isCreated()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", ResourcePolicyMatcher.matchFullEmbeds())) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.name", is(resourcePolicyRest.getName())), + hasJsonPath("$.description", is(resourcePolicyRest.getDescription())), + hasJsonPath("$.policyType", is(resourcePolicyRest.getPolicyType())), + hasJsonPath("$.action", is(resourcePolicyRest.getAction())), + hasJsonPath("$.startDate", is(resourcePolicyRest.getStartDate())), + hasJsonPath("$.endDate", is(resourcePolicyRest.getEndDate())), + hasJsonPath("$.type", is(resourcePolicyRest.getType()))))) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + + // submitter can see own policy + getClient(authSubmitterToken).perform(get("/api/authz/resourcepolicies/" + idRef.get())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/authz/resourcepolicies/" + idRef.get()))); + + // collection admin can see that policy + getClient(authcolAdminToken).perform(get("/api/authz/resourcepolicies/" + idRef.get())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/authz/resourcepolicies/" + idRef.get()))); + } finally { + ResourcePolicyBuilder.delete(idRef.get()); + } + } + + @Test + public void createPolicyBySubCommunityAdminTest() throws Exception { + context.turnOffAuthorisationSystem(); + EPerson comAdmin = EPersonBuilder.createEPerson(context) + .withEmail("comAdmin@mail.test") + .withPassword(password) + .build(); + + EPerson comAdmin2 = EPersonBuilder.createEPerson(context) + .withEmail("comAdmin2@mail.test") + .withPassword(password) + .build(); + + EPerson submitter = EPersonBuilder.createEPerson(context) + .withEmail("colSubmitter@mail.test") + .withPassword(password) + .build(); + + Community community = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("My First Commynity") + .withAdminGroup(comAdmin) + .build(); + + Community community2 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("My Second Commynity") + .withAdminGroup(comAdmin2) + .build(); + + Collection collection = CollectionBuilder.createCollection(context, community) + .withName("My collection") + .withSubmitterGroup(submitter) + .withEntityType("Publication") + .build(); + + CollectionBuilder.createCollection(context, community2) + .withName("My Second Collection") + .withSubmitterGroup(submitter) + .withEntityType("Publication") + .build(); + + Item publication = ItemBuilder.createItem(context, collection) + .withTitle("Public item") + .build(); + + context.restoreAuthSystemState(); + + ResourcePolicyRest resourcePolicyRest = new ResourcePolicyRest(); + resourcePolicyRest.setPolicyType(ResourcePolicy.TYPE_CUSTOM); + resourcePolicyRest.setAction(Constants.actionText[Constants.WRITE]); + resourcePolicyRest.setName("Test for collection admin"); + + String authcomAdminToken = getAuthToken(comAdmin.getEmail(), password); + String authcomAdmin2Token = getAuthToken(comAdmin2.getEmail(), password); + String authSubmitterToken = getAuthToken(submitter.getEmail(), password); + AtomicReference idRef = new AtomicReference(); + + try { + // submitter can't create policy + getClient(authSubmitterToken).perform(post("/api/authz/resourcepolicies") + .content(new ObjectMapper().writeValueAsBytes(resourcePolicyRest)) + .param("resource", publication.getID().toString()) + .param("eperson", submitter.getID().toString()) + .param("projections", "full") + .contentType(contentType)) + .andExpect(status().isForbidden()); + + // other Community admin can't create policy for collections into other Community + getClient(authcomAdmin2Token).perform(post("/api/authz/resourcepolicies") + .content(new ObjectMapper().writeValueAsBytes(resourcePolicyRest)) + .param("resource", publication.getID().toString()) + .param("eperson", submitter.getID().toString()) + .param("projections", "full") + .contentType(contentType)) + .andExpect(status().isForbidden()); + + // create policy for submitter by Community admin + getClient(authcomAdminToken).perform(post("/api/authz/resourcepolicies") + .content(new ObjectMapper().writeValueAsBytes(resourcePolicyRest)) + .param("resource", publication.getID().toString()) + .param("eperson", submitter.getID().toString()) + .param("projections", "full") + .contentType(contentType)) + .andExpect(status().isCreated()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", ResourcePolicyMatcher.matchFullEmbeds())) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.name", is(resourcePolicyRest.getName())), + hasJsonPath("$.description", is(resourcePolicyRest.getDescription())), + hasJsonPath("$.policyType", is(resourcePolicyRest.getPolicyType())), + hasJsonPath("$.action", is(resourcePolicyRest.getAction())), + hasJsonPath("$.startDate", is(resourcePolicyRest.getStartDate())), + hasJsonPath("$.endDate", is(resourcePolicyRest.getEndDate())), + hasJsonPath("$.type", is(resourcePolicyRest.getType()))))) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + + // submitter can see own policy + getClient(authSubmitterToken).perform(get("/api/authz/resourcepolicies/" + idRef.get())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/authz/resourcepolicies/" + idRef.get()))); + + // community admin can see policies of own collections/items + getClient(authcomAdminToken).perform(get("/api/authz/resourcepolicies/" + idRef.get())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/authz/resourcepolicies/" + idRef.get()))); + + // Other community admin can't see policies of other community's collections/items + getClient(authcomAdmin2Token).perform(get("/api/authz/resourcepolicies/" + idRef.get())) + .andExpect(status().isForbidden()); + } finally { + ResourcePolicyBuilder.delete(idRef.get()); + } + } + + @Test + public void createPolicyByCommunityAdminTest() throws Exception { + context.turnOffAuthorisationSystem(); + EPerson rootComAdmin = EPersonBuilder.createEPerson(context) + .withEmail("rootComAdmin@mail.test") + .withPassword(password) + .build(); + + EPerson submitter = EPersonBuilder.createEPerson(context) + .withEmail("colSubmitter@mail.test") + .withPassword(password) + .build(); + + Community rootCommunity = CommunityBuilder.createCommunity(context) + .withName("Root Community") + .withAdminGroup(rootComAdmin) + .build(); + + Community community = CommunityBuilder.createSubCommunity(context, rootCommunity) + .withName("My First Commynity") + .build(); + + Community community2 = CommunityBuilder.createSubCommunity(context, rootCommunity) + .withName("My Second Commynity") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, community) + .withName("My collection") + .withSubmitterGroup(submitter) + .withEntityType("Publication") + .build(); + + CollectionBuilder.createCollection(context, community2) + .withName("My Second Collection") + .withSubmitterGroup(submitter) + .withEntityType("Publication") + .build(); + + Item publication = ItemBuilder.createItem(context, collection) + .withTitle("Public item") + .build(); + + Collection collection2 = CollectionBuilder.createCollection(context, community) + .withName("My Second Collection") + .withSubmitterGroup(submitter) + .withEntityType("Publication") + .build(); + + Item publication2 = ItemBuilder.createItem(context, collection2) + .withTitle("Item of second collection") + .build(); + + //Add a bitstream to a publication + Bitstream bitstream = null; + try (InputStream is = IOUtils.toInputStream("ThisIsSomeDummyText", CharEncoding.UTF_8)) { + bitstream = BitstreamBuilder.createBitstream(context, publication2, is) + .withName("Bitstream") + .withDescription("description") + .withMimeType("text/plain") + .build(); + } + + context.restoreAuthSystemState(); + + ResourcePolicyRest resourcePolicyRest = new ResourcePolicyRest(); + resourcePolicyRest.setPolicyType(ResourcePolicy.TYPE_CUSTOM); + resourcePolicyRest.setAction(Constants.actionText[Constants.WRITE]); + resourcePolicyRest.setName("Test for collection admin"); + + ResourcePolicyRest resourcePolicyRest2 = new ResourcePolicyRest(); + resourcePolicyRest2.setPolicyType(ResourcePolicy.TYPE_CUSTOM); + resourcePolicyRest2.setAction(Constants.actionText[Constants.WRITE]); + resourcePolicyRest2.setName("Test for root community admin"); + + String authSubmitterToken = getAuthToken(submitter.getEmail(), password); + String authRootAdminToken = getAuthToken(rootComAdmin.getEmail(), password); + + AtomicReference idRef = new AtomicReference(); + AtomicReference idRef2 = new AtomicReference(); + try { + // create policy for submitter by root Community admin + getClient(authRootAdminToken).perform(post("/api/authz/resourcepolicies") + .content(new ObjectMapper().writeValueAsBytes(resourcePolicyRest)) + .param("resource", publication.getID().toString()) + .param("eperson", submitter.getID().toString()) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", ResourcePolicyMatcher.matchFullEmbeds())) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.name", is(resourcePolicyRest.getName())), + hasJsonPath("$.description", is(resourcePolicyRest.getDescription())), + hasJsonPath("$.policyType", is(resourcePolicyRest.getPolicyType())), + hasJsonPath("$.action", is(resourcePolicyRest.getAction())), + hasJsonPath("$.startDate", is(resourcePolicyRest.getStartDate())), + hasJsonPath("$.endDate", is(resourcePolicyRest.getEndDate())), + hasJsonPath("$.type", is(resourcePolicyRest.getType()))))) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + + // create policy for submitter by root Community admin + getClient(authRootAdminToken).perform(post("/api/authz/resourcepolicies") + .content(new ObjectMapper().writeValueAsBytes(resourcePolicyRest)) + .param("resource", bitstream.getID().toString()) + .param("eperson", submitter.getID().toString()) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", ResourcePolicyMatcher.matchFullEmbeds())) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.name", is(resourcePolicyRest.getName())), + hasJsonPath("$.description", is(resourcePolicyRest.getDescription())), + hasJsonPath("$.policyType", is(resourcePolicyRest.getPolicyType())), + hasJsonPath("$.action", is(resourcePolicyRest.getAction())), + hasJsonPath("$.startDate", is(resourcePolicyRest.getStartDate())), + hasJsonPath("$.endDate", is(resourcePolicyRest.getEndDate())), + hasJsonPath("$.type", is(resourcePolicyRest.getType()))))) + .andDo(result -> idRef2.set(read(result.getResponse().getContentAsString(), "$.id"))); + + getClient(authSubmitterToken).perform(get("/api/authz/resourcepolicies/" + idRef.get())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/authz/resourcepolicies/" + idRef.get()))); + + getClient(authSubmitterToken).perform(get("/api/authz/resourcepolicies/" + idRef2.get())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/authz/resourcepolicies/" + idRef2.get()))); + } finally { + ResourcePolicyBuilder.delete(idRef.get()); + ResourcePolicyBuilder.delete(idRef2.get()); + } + } + @Test public void deleteOne() throws Exception { context.turnOffAuthorisationSystem(); @@ -1308,6 +1683,174 @@ public class ResourcePolicyRestRepositoryIT extends AbstractControllerIntegratio .andExpect(status().isNotFound()); } + @Test + public void deletePolicyByCollectionAdminTest() throws Exception { + context.turnOffAuthorisationSystem(); + EPerson colAdmin = EPersonBuilder.createEPerson(context) + .withEmail("colAdmin@mail.test") + .withPassword(password) + .build(); + + EPerson colAdmin2 = EPersonBuilder.createEPerson(context) + .withEmail("colAdmin2@mail.test") + .withPassword(password) + .build(); + + EPerson submitter = EPersonBuilder.createEPerson(context) + .withEmail("colSubmitter@mail.test") + .withPassword(password) + .build(); + + Community community = CommunityBuilder.createCommunity(context) + .withName("My top commynity") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, community) + .withName("My collection") + .withAdminGroup(colAdmin) + .withSubmitterGroup(submitter) + .withEntityType("Publication") + .build(); + + CollectionBuilder.createCollection(context, community) + .withName("My Second Collection") + .withAdminGroup(colAdmin2) + .withSubmitterGroup(submitter) + .withEntityType("Publication") + .build(); + + Item publication = ItemBuilder.createItem(context, collection) + .withTitle("Public item") + .build(); + + //Add a bitstream to a publication + Bitstream bitstream = null; + try (InputStream is = IOUtils.toInputStream("ThisIsSomeDummyText", CharEncoding.UTF_8)) { + bitstream = BitstreamBuilder.createBitstream(context, publication, is) + .withName("Bitstream") + .withDescription("description") + .withMimeType("text/plain") + .build(); + } + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + String authcolAdminToken = getAuthToken(colAdmin.getEmail(), password); + String authcolAdmin2Token = getAuthToken(colAdmin2.getEmail(), password); + String authSubmitterToken = getAuthToken(submitter.getEmail(), password); + + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context, submitter, null) + .withDspaceObject(bitstream) + .withAction(Constants.READ) + .withPolicyType(ResourcePolicy.TYPE_CUSTOM) + .build(); + + // submitter can't delete own policy + getClient(authSubmitterToken).perform(delete("/api/authz/resourcepolicies/" + rp.getID())) + .andExpect(status().isForbidden()); + + // check that policy wasn't deleted + getClient(adminToken).perform(get("/api/authz/resourcepolicies/" + rp.getID())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/authz/resourcepolicies/" + rp.getID()))); + + // other collection admin can't delete policy that belong to items of other collections + getClient(authcolAdmin2Token).perform(delete("/api/authz/resourcepolicies/" + rp.getID())) + .andExpect(status().isForbidden()); + + // check that policy wasn't deleted + getClient(adminToken).perform(get("/api/authz/resourcepolicies/" + rp.getID())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/authz/resourcepolicies/" + rp.getID()))); + + // delete policy for submitter by collection admin + getClient(authcolAdminToken).perform(delete("/api/authz/resourcepolicies/" + rp.getID())) + .andExpect(status().isNoContent()); + + getClient(adminToken).perform(get("/api/authz/resourcepolicies/" + rp.getID())) + .andExpect(status().isNotFound()); + } + + @Test + public void deletePolicyBySubCommunityAdminTest() throws Exception { + context.turnOffAuthorisationSystem(); + EPerson comAdmin = EPersonBuilder.createEPerson(context) + .withEmail("comAdmin@mail.test") + .withPassword(password) + .build(); + + EPerson comAdmin2 = EPersonBuilder.createEPerson(context) + .withEmail("comAdmin2@mail.test") + .withPassword(password) + .build(); + + EPerson submitter = EPersonBuilder.createEPerson(context) + .withEmail("colSubmitter@mail.test") + .withPassword(password) + .build(); + + Community community = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("My First Commynity") + .withAdminGroup(comAdmin) + .build(); + + Community community2 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("My Second Commynity") + .withAdminGroup(comAdmin2) + .build(); + + Collection collection = CollectionBuilder.createCollection(context, community) + .withName("My collection") + .withSubmitterGroup(submitter) + .withEntityType("Publication") + .build(); + + CollectionBuilder.createCollection(context, community2) + .withName("My Second Collection") + .withSubmitterGroup(submitter) + .withEntityType("Publication") + .build(); + + Item publication = ItemBuilder.createItem(context, collection) + .withTitle("Public item") + .build(); + + context.restoreAuthSystemState(); + + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context, submitter, null) + .withDspaceObject(publication) + .withAction(Constants.WRITE) + .withPolicyType(ResourcePolicy.TYPE_CUSTOM) + .build(); + + String adminToken = getAuthToken(admin.getEmail(), password); + String authcomAdminToken = getAuthToken(comAdmin.getEmail(), password); + String authcomAdmin2Token = getAuthToken(comAdmin2.getEmail(), password); + + // other Community admin can't delete policy of other Community + getClient(authcomAdmin2Token).perform(delete("/api/authz/resourcepolicies/" + rp.getID())) + .andExpect(status().isForbidden()); + + getClient(adminToken).perform(get("/api/authz/resourcepolicies/" + rp.getID())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/authz/resourcepolicies/" + rp.getID()))); + + // Community admin can delete policy + getClient(authcomAdminToken).perform(delete("/api/authz/resourcepolicies/" + rp.getID())) + .andExpect(status().isNoContent()); + + // submitter can see own policy + getClient(adminToken).perform(get("/api/authz/resourcepolicies/" + rp.getID())) + .andExpect(status().isNotFound()); + } + @Test public void patchReplaceStartDateTest() throws Exception { context.turnOffAuthorisationSystem();