diff --git a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java index 2ed925decd..6656028e7b 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -1072,7 +1072,7 @@ prevent the generation of resource policy entry values with null dspace_object a private boolean isNotAlreadyACustomRPOfThisTypeOnDSO(Context context, DSpaceObject dso) throws SQLException { List readRPs = resourcePolicyService.find(context, dso, Constants.READ); for (ResourcePolicy readRP : readRPs) { - if (readRP.getRpType().equals(ResourcePolicy.TYPE_CUSTOM)) { + if (readRP.getRpType() != null && readRP.getRpType().equals(ResourcePolicy.TYPE_CUSTOM)) { return false; } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java index ffaf6f2da7..c5fa04841c 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java @@ -43,12 +43,16 @@ import org.apache.commons.lang3.time.DateUtils; import org.dspace.app.rest.matcher.CollectionMatcher; import org.dspace.app.rest.matcher.ItemMatcher; import org.dspace.app.rest.matcher.MetadataMatcher; +import org.dspace.app.rest.matcher.ResourcePolicyMatcher; import org.dspace.app.rest.matcher.WorkspaceItemMatcher; import org.dspace.app.rest.model.patch.AddOperation; import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.model.patch.RemoveOperation; import org.dspace.app.rest.model.patch.ReplaceOperation; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.authorize.ResourcePolicy; +import org.dspace.authorize.factory.AuthorizeServiceFactory; +import org.dspace.authorize.service.ResourcePolicyService; import org.dspace.builder.BitstreamBuilder; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; @@ -67,9 +71,11 @@ import org.dspace.content.Item; import org.dspace.content.Relationship; import org.dspace.content.RelationshipType; import org.dspace.content.WorkspaceItem; +import org.dspace.core.Constants; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.eperson.factory.EPersonServiceFactory; +import org.dspace.eperson.service.GroupService; import org.dspace.services.ConfigurationService; import org.hamcrest.Matchers; import org.junit.Before; @@ -88,6 +94,10 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration @Autowired private ConfigurationService configurationService; + private GroupService groupService; + + private ResourcePolicyService resourcePolicyService; + private Group embargoedGroups; private Group embargoedGroup1; private Group embargoedGroup2; @@ -98,6 +108,8 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration public void setUp() throws Exception { super.setUp(); context.turnOffAuthorisationSystem(); + this.groupService = EPersonServiceFactory.getInstance().getGroupService(); + this.resourcePolicyService = AuthorizeServiceFactory.getInstance().getResourcePolicyService(); embargoedGroups = GroupBuilder.createGroup(context) .withName("Embargoed Groups") @@ -5299,4 +5311,232 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration } + @Test + public void patchUploadAddAdminRPInstallAndVerifyOnlyAdminCanView() throws Exception { + context.turnOffAuthorisationSystem(); + Community community = CommunityBuilder.createCommunity(context).withName("Com").build(); + Collection collection = CollectionBuilder.createCollection(context, community).withName("Col").build(); + InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); + WorkspaceItem wItem = WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withSubmitter(eperson) + .withTitle( + "Test Item patchUploadAddAdminRPInstallAndVerifyOnlyAdminCanView") + .withIssueDate("2019-03-06") + .withFulltext("upload2.pdf", "/local/path/simple-article.pdf", pdf) + .build(); + context.restoreAuthSystemState(); + + // auth + String epersonToken = getAuthToken(eperson.getEmail(), password); + String adminToken = getAuthToken(admin.getEmail(), password); + + // prepare patch body + Map accessCondition = new HashMap<>(); + accessCondition.put("name", "administrator"); + List ops = new ArrayList<>(); + ops.add(new AddOperation("/sections/upload/files/0/accessConditions/-", accessCondition)); + String patchBody = getPatchContent(ops); + + // submit patch and verify response + getClient(epersonToken) + .perform( + patch("/api/submission/workspaceitems/" + wItem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON) + ) + .andExpect(status().isOk()); + + Bitstream bitstream = wItem.getItem().getBundles().get(0).getBitstreams().get(0); + Group adminGroup = groupService.findByName(context, Group.ADMIN); + + // verify that bitstream of workspace item has this admin RP + getClient(adminToken).perform(get("/api/authz/resourcepolicies/search/resource") + .param("uuid", bitstream.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.resourcepolicies", Matchers.hasItems( + ResourcePolicyMatcher.matchResourcePolicyProperties(adminGroup, null, bitstream, + ResourcePolicy.TYPE_CUSTOM, Constants.READ, "administrator") + ))); + + // submit the workspaceitem to complete the deposit (as there is no workflow configured) + getClient(epersonToken) + .perform(post(BASE_REST_SERVER_URL + "/api/workflow/workflowitems") + .content("/api/submission/workspaceitems/" + wItem.getID()) + .contentType(textUriContentType)) + .andExpect(status().isCreated()); + + Group anonGroup = groupService.findByName(context, Group.ANONYMOUS); + + // verify that bitstream of workspace item still has this admin RP and no Anon READ inherited policy + getClient(adminToken).perform(get("/api/authz/resourcepolicies/search/resource") + .param("uuid", bitstream.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.resourcepolicies", Matchers.hasItems( + ResourcePolicyMatcher.matchResourcePolicyProperties(adminGroup, null, bitstream, + ResourcePolicy.TYPE_CUSTOM, Constants.READ, "administrator") + ))) + .andExpect(jsonPath("$._embedded.resourcepolicies", Matchers.not(Matchers.hasItems( + ResourcePolicyMatcher + .matchResourcePolicyProperties(anonGroup, null, bitstream, null, Constants.READ, + null) + )))); + + // Bitstream should NOT be accessible to anon or eperson user, only to admin + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content")) + .andExpect(status().isUnauthorized()); + getClient(epersonToken).perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content")) + .andExpect(status().isForbidden()); + getClient(adminToken).perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content")) + .andExpect(status().isOk()); + } + + @Test + public void patchUploadAddOpenAccessRPInstallAndVerifyAnonCanView() throws Exception { + context.turnOffAuthorisationSystem(); + Community community = CommunityBuilder.createCommunity(context).withName("Com").build(); + Collection collection = CollectionBuilder.createCollection(context, community).withName("Col").build(); + InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); + WorkspaceItem wItem = + WorkspaceItemBuilder.createWorkspaceItem(context, collection).withSubmitter(eperson) + .withTitle("Test Item patchUploadAddOpenAccessRPInstallAndVerifyOnlyAdminCanView") + .withIssueDate("2019-03-06") + .withFulltext("upload2.pdf", "/local/path/simple-article.pdf", pdf) + .build(); + context.restoreAuthSystemState(); + + // auth + String epersonToken = getAuthToken(eperson.getEmail(), password); + String adminToken = getAuthToken(admin.getEmail(), password); + + // prepare patch body + Map accessCondition = new HashMap<>(); + accessCondition.put("name", "openaccess"); + List ops = new ArrayList<>(); + ops.add(new AddOperation("/sections/upload/files/0/accessConditions/-", accessCondition)); + String patchBody = getPatchContent(ops); + + // submit patch and verify response + getClient(epersonToken) + .perform( + patch("/api/submission/workspaceitems/" + wItem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON) + ) + .andExpect(status().isOk()); + + Bitstream bitstream = wItem.getItem().getBundles().get(0).getBitstreams().get(0); + + // verify that bitstream of workspace item has this open access RP + getClient(adminToken).perform(get("/api/authz/resourcepolicies/search/resource") + .param("uuid", bitstream.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.resourcepolicies", Matchers.hasItems( + ResourcePolicyMatcher.matchResourcePolicyProperties(anonymousGroup, null, bitstream, + ResourcePolicy.TYPE_CUSTOM, Constants.READ, "openaccess") + ))); + + // submit the workspaceitem to complete the deposit (as there is no workflow configured) + getClient(epersonToken) + .perform(post(BASE_REST_SERVER_URL + "/api/workflow/workflowitems") + .content("/api/submission/workspaceitems/" + wItem.getID()) + .contentType(textUriContentType)) + .andExpect(status().isCreated()); + + // verify that bitstream of workspace item still has this open access RP + getClient(adminToken).perform(get("/api/authz/resourcepolicies/search/resource") + .param("uuid", bitstream.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.resourcepolicies", Matchers.hasItems( + ResourcePolicyMatcher.matchResourcePolicyProperties(anonymousGroup, null, bitstream, + ResourcePolicy.TYPE_CUSTOM, Constants.READ, "openaccess") + ))); + + // Bitstream should be accessible to anon + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content")) + .andExpect(status().isOk()); + } + + @Test + public void patchUploadAddAdminThenOpenAccessRPInstallAndVerifyAnonCanView() throws Exception { + context.turnOffAuthorisationSystem(); + Community community = CommunityBuilder.createCommunity(context).withName("Com").build(); + Collection collection = CollectionBuilder.createCollection(context, community).withName("Col").build(); + InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); + WorkspaceItem wItem = + WorkspaceItemBuilder.createWorkspaceItem(context, collection).withSubmitter(eperson) + .withTitle("Test Item patchUploadAddOpenAccessRPInstallAndVerifyOnlyAdminCanView") + .withIssueDate("2019-03-06") + .withFulltext("upload2.pdf", "/local/path/simple-article.pdf", pdf) + .build(); + context.restoreAuthSystemState(); + + // auth + String epersonToken = getAuthToken(eperson.getEmail(), password); + String adminToken = getAuthToken(admin.getEmail(), password); + + // prepare patch body + Map accessCondition = new HashMap<>(); + accessCondition.put("name", "administrator"); + List ops = new ArrayList<>(); + ops.add(new AddOperation("/sections/upload/files/0/accessConditions/-", accessCondition)); + String patchBody = getPatchContent(ops); + + // submit patch and verify response + getClient(epersonToken) + .perform( + patch("/api/submission/workspaceitems/" + wItem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON) + ) + .andExpect(status().isOk()); + + Bitstream bitstream = wItem.getItem().getBundles().get(0).getBitstreams().get(0); + Group adminGroup = groupService.findByName(context, Group.ADMIN); + + // verify that bitstream of workspace item has this admin RP + getClient(adminToken).perform(get("/api/authz/resourcepolicies/search/resource") + .param("uuid", bitstream.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.resourcepolicies", Matchers.hasItems( + ResourcePolicyMatcher.matchResourcePolicyProperties(adminGroup, null, bitstream, + ResourcePolicy.TYPE_CUSTOM, Constants.READ, "administrator") + ))); + + // prepare patch body + Map accessCondition2 = new HashMap<>(); + accessCondition2.put("name", "openaccess"); + List ops2 = new ArrayList<>(); + ops2.add(new AddOperation("/sections/upload/files/0/accessConditions/-", accessCondition2)); + String patchBody2 = getPatchContent(ops2); + + // submit patch and verify response + getClient(epersonToken) + .perform( + patch("/api/submission/workspaceitems/" + wItem.getID()) + .content(patchBody2) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON) + ) + .andExpect(status().isOk()); + + // verify that bitstream of workspace item has this open access RP + getClient(adminToken).perform(get("/api/authz/resourcepolicies/search/resource") + .param("uuid", bitstream.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.resourcepolicies", Matchers.hasItems( + ResourcePolicyMatcher.matchResourcePolicyProperties(anonymousGroup, null, bitstream, + ResourcePolicy.TYPE_CUSTOM, Constants.READ, "openaccess") + ))); + + // submit the workspaceitem to complete the deposit (as there is no workflow configured) + getClient(epersonToken) + .perform(post(BASE_REST_SERVER_URL + "/api/workflow/workflowitems") + .content("/api/submission/workspaceitems/" + wItem.getID()) + .contentType(textUriContentType)) + .andExpect(status().isCreated()); + + // Bitstream should be accessible to anon + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content")) + .andExpect(status().isOk()); + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ResourcePolicyMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ResourcePolicyMatcher.java index 88d247d091..b0423b5455 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ResourcePolicyMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ResourcePolicyMatcher.java @@ -14,8 +14,13 @@ import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; +import javax.annotation.Nullable; + import org.dspace.authorize.ResourcePolicy; +import org.dspace.content.DSpaceObject; import org.dspace.core.Constants; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Group; import org.hamcrest.Matcher; /** @@ -29,6 +34,27 @@ public class ResourcePolicyMatcher { private ResourcePolicyMatcher() { } + public static Matcher matchResourcePolicyProperties(@Nullable Group group, + @Nullable EPerson eperson, DSpaceObject dso, @Nullable String rpType, int action, @Nullable String name) { + return allOf( + hasJsonPath("$.name", is(name)), + hasJsonPath("$.action", is(Constants.actionText[action])), + rpType != null ? + hasJsonPath("$.policyType", is(rpType)) : + hasNoJsonPath("$.policyType"), + hasJsonPath("$.type", is("resourcepolicy")), + hasJsonPath("$._embedded.resource.id", is(dso.getID().toString())), + eperson != null ? + hasJsonPath("$._embedded.eperson.id", + is(eperson.getID().toString())) : + hasJsonPath("$._embedded.eperson", nullValue()), + group != null ? + hasJsonPath("$._embedded.group.id", + is(group.getID().toString())) : + hasJsonPath("$._embedded.group", nullValue()) + ); + } + public static Matcher matchResourcePolicy(ResourcePolicy resourcePolicy) { return allOf(hasJsonPath("$.id", is(resourcePolicy.getID())), hasJsonPath("$.name", is(resourcePolicy.getRpName())),