diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java index fb148b8c4eb9..c48bf14c58ac 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java @@ -1018,21 +1018,27 @@ public Object getDSpaceAPIObjectFromRest(Context context, BaseObjectRest restObj */ public BaseObjectRest getBaseObjectRestFromUri(Context context, String uri) throws SQLException { String dspaceUrl = configurationService.getProperty("dspace.server.url"); + String dspaceSSRUrl = configurationService.getProperty("dspace.server.ssr.url", ""); // Convert strings to URL objects. // Do this early to check that inputs are well-formed. URL dspaceUrlObject; + URL dspaceUrlSSRObject = null; URL requestUrlObject; try { dspaceUrlObject = new URL(dspaceUrl); requestUrlObject = new URL(uri); + if (StringUtils.isNoneBlank(dspaceSSRUrl)) { + dspaceUrlSSRObject = new URL(dspaceSSRUrl); + } } catch (MalformedURLException ex) { throw new IllegalArgumentException( String.format("Configuration '%s' or request '%s' is malformed", dspaceUrl, uri)); } // Check whether the URI could be valid. - if (!urlIsPrefixOf(dspaceUrl, uri)) { + if (!urlIsPrefixOf(dspaceUrl, uri) && (StringUtils.isBlank(dspaceSSRUrl) || + !urlIsPrefixOf(dspaceSSRUrl, uri))) { throw new IllegalArgumentException("the supplied uri is not ours: " + uri); } @@ -1042,10 +1048,16 @@ public BaseObjectRest getBaseObjectRestFromUri(Context context, String uri) thro String[] requestPath = StringUtils.split(requestUrlObject.getPath(), '/'); String[] uriParts = Arrays.copyOfRange(requestPath, dspacePathLength, requestPath.length); + String[] uriSSRParts = new String[0]; + if (StringUtils.isNoneBlank(dspaceSSRUrl) && !Objects.isNull(dspaceUrlSSRObject)) { + int dspaceSSRPathLength = StringUtils.split(dspaceUrlSSRObject.getPath(), '/').length; + uriSSRParts = Arrays.copyOfRange(requestPath, dspaceSSRPathLength, + requestPath.length); + } if ("api".equalsIgnoreCase(uriParts[0])) { uriParts = Arrays.copyOfRange(uriParts, 1, uriParts.length); } - if (uriParts.length != 3) { + if (uriParts.length != 3 && uriSSRParts.length != 3) { throw new IllegalArgumentException("the supplied uri lacks required path elements: " + uri); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationRestRepositoryIT.java index 6a51649b5bce..107636052955 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationRestRepositoryIT.java @@ -884,6 +884,133 @@ public void findByObjectBadRequestTest() throws Exception { .andExpect(status().isBadRequest()); } + @Test + /** + * Verify that the findByObject return the 400 Bad Request response for invalid or missing URI (required parameter) + * + * @throws Exception + */ + public void findByObjectBadRequestSSRTest() throws Exception { + Site site = siteService.findSite(context); + SiteRest siteRest = siteConverter.convert(site, DefaultProjection.DEFAULT); + String siteUri = "http://ssr.example.com/api/core/sites/" + siteRest.getId(); + String[] invalidUris = new String[] { + "invalid-uri", + "", + "http://localhost/api/wrongcategory/wrongmodel/1", + "http://localhost/api/core/sites/this-is-not-an-uuid" + }; + + // disarm the alwaysThrowExceptionFeature + configurationService.setProperty("org.dspace.app.rest.authorization.AlwaysThrowExceptionFeature.turnoff", true); + configurationService.setProperty("dspace.server.ssr.url", "http://ssr.example.com/api"); + + String adminToken = getAuthToken(admin.getEmail(), password); + String epersonToken = getAuthToken(eperson.getEmail(), password); + + // verify that it works for administrator users + getClient(adminToken).perform(get("/api/authz/authorizations/search/object") + .param("uri", siteUri)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.authorizations", Matchers.hasSize(greaterThanOrEqualTo(1)))) + .andExpect(jsonPath("$._embedded.authorizations", Matchers.everyItem( + Matchers.anyOf( + JsonPathMatchers.hasJsonPath("$.type", is("authorization")), + JsonPathMatchers.hasJsonPath("$._embedded.feature", + Matchers.allOf( + is(alwaysTrue.getName()) + )), + JsonPathMatchers.hasJsonPath("$._embedded.feature", + Matchers.not(Matchers.anyOf( + is(alwaysFalse.getName()), + is(alwaysException.getName()), + is(trueForTestUsers.getName()), + is(trueForAdmins.getName()) + ) + )), + JsonPathMatchers.hasJsonPath("$._embedded.feature.resourcetypes", + Matchers.hasItem(is("authorization"))), + JsonPathMatchers.hasJsonPath("$.id", + Matchers.anyOf( + Matchers.startsWith(eperson.getID().toString()), + Matchers.endsWith(siteRest.getUniqueType() + "_" + siteRest.getId())))) + ) + ) + ) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/authz/authorizations/search/object"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(1))); + + // verify that it works for authenticated users + getClient(epersonToken).perform(get("/api/authz/authorizations/search/object") + .param("uri", siteUri)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.authorizations", Matchers.hasSize(greaterThanOrEqualTo(1)))) + .andExpect(jsonPath("$._embedded.authorizations", Matchers.everyItem( + Matchers.anyOf( + JsonPathMatchers.hasJsonPath("$.type", is("authorization")), + JsonPathMatchers.hasJsonPath("$._embedded.feature", + Matchers.allOf( + is(alwaysTrue.getName()) + )), + JsonPathMatchers.hasJsonPath("$._embedded.feature", + Matchers.not(Matchers.anyOf( + is(alwaysFalse.getName()), + is(alwaysException.getName()), + is(trueForTestUsers.getName()), + is(trueForAdmins.getName()) + ) + )), + JsonPathMatchers.hasJsonPath("$._embedded.feature.resourcetypes", + Matchers.hasItem(is("authorization"))), + JsonPathMatchers.hasJsonPath("$.id", + Matchers.anyOf( + Matchers.startsWith(eperson.getID().toString()), + Matchers.endsWith(siteRest.getUniqueType() + "_" + siteRest.getId())))) + ) + ) + ) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/authz/authorizations/search/object"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(1))); + + // verify that it works for anonymous users + getClient(epersonToken).perform(get("/api/authz/authorizations/search/object") + .param("uri", siteUri)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.authorizations", Matchers.hasSize(greaterThanOrEqualTo(1)))) + .andExpect(jsonPath("$._embedded.authorizations", Matchers.everyItem( + Matchers.anyOf( + JsonPathMatchers.hasJsonPath("$.type", is("authorization")), + JsonPathMatchers.hasJsonPath("$._embedded.feature", + Matchers.allOf( + is(alwaysTrue.getName()) + )), + JsonPathMatchers.hasJsonPath("$._embedded.feature", + Matchers.not(Matchers.anyOf( + is(alwaysFalse.getName()), + is(alwaysException.getName()), + is(trueForTestUsers.getName()), + is(trueForAdmins.getName()) + ) + )), + JsonPathMatchers.hasJsonPath("$._embedded.feature.resourcetypes", + Matchers.hasItem(is("authorization"))), + JsonPathMatchers.hasJsonPath("$.id", + Matchers.anyOf( + Matchers.startsWith(eperson.getID().toString()), + Matchers.endsWith(siteRest.getUniqueType() + "_" + siteRest.getId())))) + ) + ) + ) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/authz/authorizations/search/object"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(1))); + } + @Test /** * Verify that the findByObject return the 401 Unauthorized response when an eperson is involved diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index a5ad81ead6c0..ba51e040e7a5 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -28,6 +28,10 @@ csvexport.dir = ${dspace.dir}/exports # and is usually "synced" with the "rest" section in the DSpace User Interface's config.*.yml. # It corresponds to the URL that you would type into your browser to access the REST API. dspace.server.url = http://localhost:8080/server +# Additional URL of DSpace backend which could be used by DSpace frontend during SSR execution. +# May require a port number if not using standard ports (80 or 443) +# DO NOT end it with '/'. +dspace.server.ssr.url = # Public URL of DSpace frontend (Angular UI). May require a port number if not using standard ports (80 or 443) # DO NOT end it with '/'. diff --git a/dspace/config/local.cfg.EXAMPLE b/dspace/config/local.cfg.EXAMPLE index ba46f939dbd0..4cdf4a04eeb4 100644 --- a/dspace/config/local.cfg.EXAMPLE +++ b/dspace/config/local.cfg.EXAMPLE @@ -40,6 +40,11 @@ dspace.dir=/dspace # It corresponds to the URL that you would type into your browser to access the REST API. dspace.server.url = http://localhost:8080/server +# Additional URL of DSpace backend which could be used by DSpace frontend during SSR execution. +# May require a port number if not using standard ports (80 or 443) +# DO NOT end it with '/'. +dspace.server.ssr.url = + # Public URL of DSpace frontend (Angular UI). May require a port number if not using standard ports (80 or 443) # DO NOT end it with '/'. # This is used by the backend to provide links in emails, RSS feeds, Sitemaps, etc.