diff --git a/dspace-api/src/test/data/dspaceFolder/config/local.cfg b/dspace-api/src/test/data/dspaceFolder/config/local.cfg index 5f32bd0919..d205df407b 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/local.cfg +++ b/dspace-api/src/test/data/dspaceFolder/config/local.cfg @@ -129,3 +129,6 @@ configuration.exposed.array.value = public_value_1, public_value_2 # Test config for the authentication ip functionality authentication-ip.Staff = 5.5.5.5 authentication-ip.Student = 6.6.6.6 + +# Test config for the usage statistics authorization +usage-statistics.authorization.admin.usage = true diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/ViewUsageStatisticsFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/ViewUsageStatisticsFeature.java new file mode 100644 index 0000000000..a71bc04cf9 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/ViewUsageStatisticsFeature.java @@ -0,0 +1,76 @@ +/** + * 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.CommunityRest; +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.DSpaceObject; +import org.dspace.core.Context; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * The view statistics feature. It can be used to verify if statistics can be viewed. + * + * In case DSpace is configured to only show statistics to administrators, authorization is granted if the current user + * is the object's admin. Otherwise, authorization is granted if the current user can view the object. + */ +@Component +@AuthorizationFeatureDocumentation(name = ViewUsageStatisticsFeature.NAME, + description = "It can be used to verify if statistics can be viewed") +public class ViewUsageStatisticsFeature implements AuthorizationFeature { + + public final static String NAME = "canViewUsageStatistics"; + + @Autowired + private ConfigurationService configurationService; + + @Autowired + private AuthorizeService authorizeService; + + @Autowired + private Utils utils; + + @Override + public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException { + if (object instanceof SiteRest + || object instanceof CommunityRest + || object instanceof CollectionRest + || object instanceof ItemRest) { + + if (configurationService.getBooleanProperty("usage-statistics.authorization.admin.usage", true)) { + return authorizeService.isAdmin(context, + (DSpaceObject)utils.getDSpaceAPIObjectFromRest(context, object)); + } else { + return authorizeService.authorizeActionBoolean(context, + (DSpaceObject)utils.getDSpaceAPIObjectFromRest(context, object), org.dspace.core.Constants.READ); + } + } + return false; + } + + @Override + public String[] getSupportedTypes() { + return new String[]{ + SiteRest.CATEGORY + "." + SiteRest.NAME, + CommunityRest.CATEGORY + "." + CommunityRest.NAME, + CollectionRest.CATEGORY + "." + CollectionRest.NAME, + ItemRest.CATEGORY + "." + ItemRest.NAME + }; + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ViewUsageStatisticsFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ViewUsageStatisticsFeatureIT.java new file mode 100644 index 0000000000..75cb71b55f --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ViewUsageStatisticsFeatureIT.java @@ -0,0 +1,315 @@ +/** + * 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.io.InputStream; + +import org.apache.commons.codec.CharEncoding; +import org.apache.commons.io.IOUtils; +import org.dspace.app.rest.converter.BitstreamConverter; +import org.dspace.app.rest.converter.CollectionConverter; +import org.dspace.app.rest.converter.CommunityConverter; +import org.dspace.app.rest.converter.ItemConverter; +import org.dspace.app.rest.converter.SiteConverter; +import org.dspace.app.rest.model.BitstreamRest; +import org.dspace.app.rest.model.CollectionRest; +import org.dspace.app.rest.model.CommunityRest; +import org.dspace.app.rest.model.ItemRest; +import org.dspace.app.rest.model.SiteRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.app.rest.utils.Utils; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.builder.BitstreamBuilder; +import org.dspace.builder.BundleBuilder; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; +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.services.ConfigurationService; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Test for the canViewUsageStatistics authorization feature + */ +public class ViewUsageStatisticsFeatureIT extends AbstractControllerIntegrationTest { + + @Autowired + private Utils utils; + + @Autowired + private ConfigurationService configurationService; + + @Autowired + private AuthorizeService authorizeService; + + @Autowired + private SiteConverter siteConverter; + + @Autowired + private CommunityConverter communityConverter; + + @Autowired + private CollectionConverter collectionConverter; + + @Autowired + private ItemConverter itemConverter; + + @Autowired + private BitstreamConverter bitstreamConverter; + + @Autowired + SiteService siteService; + + private Site site; + private SiteRest siteRest; + private Community communityA; + private CommunityRest communityARest; + private Collection collectionA; + private CollectionRest collectionARest; + private Item itemA; + private ItemRest itemARest; + private Bitstream bitstreamA; + private BitstreamRest bitstreamARest; + private Bundle bundleA; + private String adminToken; + private String epersonToken; + + final String feature = "canViewUsageStatistics"; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + context.turnOffAuthorisationSystem(); + + site = siteService.findSite(context); + communityA = CommunityBuilder.createCommunity(context) + .withName("communityA") + .build(); + collectionA = CollectionBuilder.createCollection(context, communityA) + .withName("collectionA") + .build(); + itemA = ItemBuilder.createItem(context, collectionA) + .withTitle("itemA") + .build(); + bundleA = BundleBuilder.createBundle(context, itemA) + .withName("ORIGINAL") + .build(); + String bitstreamContent = "Dummy content"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstreamA = BitstreamBuilder.createBitstream(context, bundleA, is) + .withName("bistreamA") + .build(); + } + + context.restoreAuthSystemState(); + + siteRest = siteConverter.convert(site, Projection.DEFAULT); + communityARest = communityConverter.convert(communityA, Projection.DEFAULT); + collectionARest = collectionConverter.convert(collectionA, Projection.DEFAULT); + itemARest = itemConverter.convert(itemA, Projection.DEFAULT); + bitstreamARest = bitstreamConverter.convert(bitstreamA, Projection.DEFAULT); + + adminToken = getAuthToken(admin.getEmail(), password); + epersonToken = getAuthToken(eperson.getEmail(), password); + } + + @Test + public void adminBitstreamTestNotFound() throws Exception { + getClient(adminToken).perform( + get("/api/authz/authorizations/search/object") + .param("embed", "feature") + .param("feature", feature) + .param("uri", utils.linkToSingleResource(bitstreamARest, "self").getHref())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))) + .andExpect(jsonPath("$._embedded").doesNotExist()); + } + + @Test + public void adminItemAdminRequiredSuccess() throws Exception { + getClient(adminToken).perform( + get("/api/authz/authorizations/search/object") + .param("embed", "feature") + .param("feature", feature) + .param("uri", utils.linkToSingleResource(itemARest, "self").getHref())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", greaterThan(0))) + .andExpect(jsonPath("$._embedded").exists()); + } + + @Test + public void adminCollectionAdminRequiredSuccess() throws Exception { + getClient(adminToken).perform( + get("/api/authz/authorizations/search/object") + .param("embed", "feature") + .param("feature", feature) + .param("uri", utils.linkToSingleResource(collectionARest, "self").getHref())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", greaterThan(0))) + .andExpect(jsonPath("$._embedded").exists()); + } + + @Test + public void adminCommunityAdminRequiredSuccess() throws Exception { + getClient(adminToken).perform( + get("/api/authz/authorizations/search/object") + .param("embed", "feature") + .param("feature", feature) + .param("uri", utils.linkToSingleResource(communityARest, "self").getHref())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", greaterThan(0))) + .andExpect(jsonPath("$._embedded").exists()); + } + + @Test + public void adminSiteAdminRequiredSuccess() throws Exception { + getClient(adminToken).perform( + get("/api/authz/authorizations/search/object") + .param("embed", "feature") + .param("feature", feature) + .param("uri", utils.linkToSingleResource(siteRest, "self").getHref())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", greaterThan(0))) + .andExpect(jsonPath("$._embedded").exists()); + } + + @Test + public void ePersonItemAdminRequiredNotFound() throws Exception { + getClient(epersonToken).perform( + get("/api/authz/authorizations/search/object") + .param("embed", "feature") + .param("feature", feature) + .param("uri", utils.linkToSingleResource(itemARest, "self").getHref())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))) + .andExpect(jsonPath("$._embedded").doesNotExist()); + } + + @Test + public void ePersonCollectionAdminRequiredNotFound() throws Exception { + getClient(epersonToken).perform( + get("/api/authz/authorizations/search/object") + .param("embed", "feature") + .param("feature", feature) + .param("uri", utils.linkToSingleResource(collectionARest, "self").getHref())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))) + .andExpect(jsonPath("$._embedded").doesNotExist()); + } + + @Test + public void ePersonCommunityAdminRequiredNotFound() throws Exception { + getClient(epersonToken).perform( + get("/api/authz/authorizations/search/object") + .param("embed", "feature") + .param("feature", feature) + .param("uri", utils.linkToSingleResource(communityARest, "self").getHref())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))) + .andExpect(jsonPath("$._embedded").doesNotExist()); + } + + @Test + public void ePersonSiteAdminRequiredNotFound() throws Exception { + getClient(epersonToken).perform( + get("/api/authz/authorizations/search/object") + .param("embed", "feature") + .param("feature", feature) + .param("uri", utils.linkToSingleResource(siteRest, "self").getHref())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))) + .andExpect(jsonPath("$._embedded").doesNotExist()); + } + + @Test + public void ePersonItemAdminNotRequiredSuccess() throws Exception { + configurationService.setProperty("usage-statistics.authorization.admin.usage", false); + + getClient(epersonToken).perform( + get("/api/authz/authorizations/search/object") + .param("embed", "feature") + .param("feature", feature) + .param("uri", utils.linkToSingleResource(itemARest, "self").getHref())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", greaterThan(0))) + .andExpect(jsonPath("$._embedded").exists()); + } + + @Test + public void ePersonCollectionAdminNotRequiredSuccess() throws Exception { + configurationService.setProperty("usage-statistics.authorization.admin.usage", false); + + getClient(epersonToken).perform( + get("/api/authz/authorizations/search/object") + .param("embed", "feature") + .param("feature", feature) + .param("uri", utils.linkToSingleResource(collectionARest, "self").getHref())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", greaterThan(0))) + .andExpect(jsonPath("$._embedded").exists()); + } + + @Test + public void ePersonCommunityAdminNotRequiredSuccess() throws Exception { + configurationService.setProperty("usage-statistics.authorization.admin.usage", false); + + getClient(epersonToken).perform( + get("/api/authz/authorizations/search/object") + .param("embed", "feature") + .param("feature", feature) + .param("uri", utils.linkToSingleResource(communityARest, "self").getHref())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", greaterThan(0))) + .andExpect(jsonPath("$._embedded").exists()); + } + + @Test + public void ePersonSiteAdminNotRequiredSuccess() throws Exception { + configurationService.setProperty("usage-statistics.authorization.admin.usage", false); + + getClient(epersonToken).perform( + get("/api/authz/authorizations/search/object") + .param("embed", "feature") + .param("feature", feature) + .param("uri", utils.linkToSingleResource(siteRest, "self").getHref())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", greaterThan(0))) + .andExpect(jsonPath("$._embedded").exists()); + } + + @Test + public void ePersonPrivateItemAdminNotRequiredNotFound() throws Exception { + configurationService.setProperty("usage-statistics.authorization.admin.usage", false); + authorizeService.removeAllPolicies(context, itemA); + + getClient(epersonToken).perform( + get("/api/authz/authorizations/search/object") + .param("embed", "feature") + .param("feature", feature) + .param("uri", utils.linkToSingleResource(itemARest, "self").getHref())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))) + .andExpect(jsonPath("$._embedded").doesNotExist()); + } +}