From dc628c96de2c01703a47edeece1ca002bbac0a71 Mon Sep 17 00:00:00 2001 From: milanmajchrak <90026355+milanmajchrak@users.noreply.github.com> Date: Wed, 15 Nov 2023 12:29:15 +0100 Subject: [PATCH 01/11] ufal/downloading-restricted-bitstreams-not-working-properly (#457) * The admin could download restricted bitstreams. * The user cannot download bitstream if he/she is not signed in. * Cannot obtain context with user. * The user is already fetched from the context. * Added docs. * Do not use endpoint for downloading the single file because it already exists - remove it. * Fixed AuthorizationRestControllerIT tests. --- .../AuthorizationBitstreamUtils.java | 24 ++- .../app/rest/AuthorizationRestController.java | 15 +- .../app/rest/MetadataBitstreamController.java | 146 +++--------------- .../rest/AuthorizationRestControllerIT.java | 53 +++++-- .../rest/MetadataBitstreamControllerIT.java | 36 +---- 5 files changed, 85 insertions(+), 189 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/authorize/AuthorizationBitstreamUtils.java b/dspace-api/src/main/java/org/dspace/authorize/AuthorizationBitstreamUtils.java index adada5ad7c28..1b98556ecad0 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/AuthorizationBitstreamUtils.java +++ b/dspace-api/src/main/java/org/dspace/authorize/AuthorizationBitstreamUtils.java @@ -18,7 +18,6 @@ import org.dspace.content.Bitstream; import org.dspace.content.Item; import org.dspace.content.clarin.ClarinLicense; -import org.dspace.content.clarin.ClarinLicenseLabel; import org.dspace.content.clarin.ClarinLicenseResourceMapping; import org.dspace.content.service.BitstreamService; import org.dspace.content.service.clarin.ClarinLicenseResourceMappingService; @@ -93,15 +92,15 @@ public boolean authorizeBitstream(Context context, Bitstream bitstream) throws S } /** - * If the bitstream has RES or ACA license and the user is Anonymous do not authorize that user. - * The user will be redirected to the login. + * Do not allow download for anonymous users. Allow it only if the bitstream has Clarin License and the license has + * confirmation = 3 (allow anonymous). + * * @param context DSpace context object * @param bitstreamID downloading Bitstream UUID * @return if the current user is authorized */ public boolean authorizeLicenseWithUser(Context context, UUID bitstreamID) throws SQLException { - // If the current user is null that means that the user is not signed in and cannot download the bitstream - // with RES or ACA license + // If the current user is null that means that the user is not signed if (Objects.nonNull(context.getCurrentUser())) { // User is signed return true; @@ -118,16 +117,13 @@ public boolean authorizeLicenseWithUser(Context context, UUID bitstreamID) throw // Bitstream should have only one type of the Clarin license, so we could get first record ClarinLicense clarinLicense = Objects.requireNonNull(clarinLicenseResourceMappings.get(0)).getLicense(); - // Get License Labels from clarin license and check if one of them is ACA or RES - List clarinLicenseLabels = clarinLicense.getLicenseLabels(); - for (ClarinLicenseLabel clarinLicenseLabel : clarinLicenseLabels) { - if (StringUtils.equals(clarinLicenseLabel.getLabel(), "RES") || - StringUtils.equals(clarinLicenseLabel.getLabel(), "ACA")) { - return false; - } + // 3 - Allow download for anonymous users, but with license confirmation + // 0 - License confirmation is not required + if (Objects.equals(clarinLicense.getConfirmation(), 3) || + Objects.equals(clarinLicense.getConfirmation(), 0)) { + return true; } - - return true; + return false; } private boolean userIsSubmitter(Context context, Bitstream bitstream, EPerson currentUser, UUID userID) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/AuthorizationRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/AuthorizationRestController.java index e91242bae75c..5b23d5cb893a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/AuthorizationRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/AuthorizationRestController.java @@ -27,6 +27,7 @@ import org.dspace.content.Bitstream; import org.dspace.content.service.BitstreamService; import org.dspace.content.service.clarin.ClarinLicenseResourceMappingService; +import org.dspace.core.Constants; import org.dspace.core.Context; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -84,28 +85,30 @@ public ResponseEntity authrn(@PathVariable String id, HttpServletResponse respon return null; } - // If the bitstream has RES or ACA license and the user is Anonymous return NotAuthorized exception + // Do not allow download for anonymous users. Allow it only if the bitstream has Clarin License and the license + // has confirmation = 3 (allow anonymous). if (!authorizationBitstreamUtils.authorizeLicenseWithUser(context, bitstream.getID())) { response.sendError(HttpStatus.UNAUTHORIZED.value(), - "Anonymous user cannot download bitstream with REC or ACA license"); + "Anonymous user cannot download this bitstream"); return null; } // Wrap exceptions to the AuthrnRest object. - String errorMessage = "User is not authorized to download the bitstream."; - boolean isAuthorized = false; + String errorMessage = ""; try { - isAuthorized = authorizationBitstreamUtils.authorizeBitstream(context, bitstream); + authorizeService.authorizeAction(context, bitstream, Constants.READ); } catch (AuthorizeException e) { if (e instanceof MissingLicenseAgreementException) { errorMessage = MissingLicenseAgreementException.NAME; } else if (e instanceof DownloadTokenExpiredException) { errorMessage = DownloadTokenExpiredException.NAME; + } else { + errorMessage = e.getMessage(); } } - if (!isAuthorized) { + if (StringUtils.isNotBlank(errorMessage)) { // If the user is not authorized return response with the error message response.sendError(HttpStatus.UNAUTHORIZED.value(), errorMessage); return null; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/MetadataBitstreamController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/MetadataBitstreamController.java index ac26a2c69551..5e6700306959 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/MetadataBitstreamController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/MetadataBitstreamController.java @@ -7,16 +7,18 @@ */ package org.dspace.app.rest; +import static org.dspace.app.rest.utils.RegexUtils.REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID; + import java.io.IOException; import java.io.InputStream; import java.sql.SQLException; import java.util.List; import java.util.Objects; +import java.util.UUID; import java.util.zip.Deflater; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; import org.apache.commons.compress.utils.IOUtils; @@ -24,38 +26,34 @@ import org.apache.logging.log4j.Logger; import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.exception.UnprocessableEntityException; -import org.dspace.app.rest.model.BitstreamRest; +import org.dspace.app.rest.model.ItemRest; import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.authorize.AuthorizationBitstreamUtils; import org.dspace.authorize.AuthorizeException; -import org.dspace.authorize.MissingLicenseAgreementException; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Bitstream; -import org.dspace.content.BitstreamFormat; import org.dspace.content.Bundle; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.content.service.BitstreamService; -import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.handle.service.HandleService; import org.dspace.services.ConfigurationService; +import org.dspace.services.RequestService; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.io.InputStreamResource; -import org.springframework.core.io.Resource; import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -/** + /** * This CLARIN Controller download a single file or a ZIP file from the Item's bitstream. */ @RestController -@RequestMapping("/api/" + BitstreamRest.CATEGORY + "/" + BitstreamRest.PLURAL_NAME) +@RequestMapping("/api/" + ItemRest.CATEGORY + "/" + ItemRest.PLURAL_NAME + REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID) public class MetadataBitstreamController { private static Logger log = org.apache.logging.log4j.LogManager.getLogger(MetadataBitstreamController.class); @@ -69,90 +67,17 @@ public class MetadataBitstreamController { private AuthorizeService authorizeService; @Autowired private ConfigurationService configurationService; - - - @GetMapping("/handle/{id}/{subId}/{fileName}") - public ResponseEntity downloadSingleFile(@PathVariable("id") String id, - @PathVariable("subId") String subId, - @PathVariable("fileName") String fileName, - HttpServletRequest request, HttpServletResponse response) - throws IOException { - String handleID = id + "/" + subId; - if (StringUtils.isBlank(id) || StringUtils.isBlank(subId)) { - log.error("Handle cannot be null! PathVariable `id` or `subId` is null."); - throw new DSpaceBadRequestException("Handle cannot be null!"); - } - - Context context = ContextUtil.obtainContext(request); - if (Objects.isNull(context)) { - log.error("Cannot obtain the context from the request."); - throw new RuntimeException("Cannot obtain the context from the request."); - } - - DSpaceObject dso = null; - try { - dso = handleService.resolveToObject(context, handleID); - } catch (Exception e) { - log.error("Cannot resolve handle: " + handleID); - throw new RuntimeException("Cannot resolve handle: " + handleID); - } - - - if (Objects.isNull(dso)) { - log.error("DSO is null"); - return null; - } - - if (!(dso instanceof Item)) { - log.error("DSO is not instance of Item"); - return null; - } - - Item item = (Item) dso; - List bundles = item.getBundles(); - // Find bitstream and start downloading. - for (Bundle bundle: bundles) { - for (Bitstream bitstream: bundle.getBitstreams()) { - // Authorize the action - it will send response redirect if something gets wrong. - authorizeBitstreamAction(context, bitstream, response); - - String btName = bitstream.getName(); - if (!(btName.equalsIgnoreCase(fileName))) { - continue; - } - try { - BitstreamFormat bitstreamFormat = bitstream.getFormat(context); - // Check if the bitstream has some extensions e.g., `.txt, .jpg,..` - checkBitstreamExtensions(bitstreamFormat); - - // Get content of the bitstream - InputStream inputStream = bitstreamService.retrieve(context, bitstream); - InputStreamResource resource = new InputStreamResource(inputStream); - HttpHeaders header = new HttpHeaders(); - header.add(HttpHeaders.CONTENT_DISPOSITION, - "attachment; filename=" + fileName); - header.add("Cache-Control", "no-cache, no-store, must-revalidate"); - header.add("Pragma", "no-cache"); - header.add("Expires", "0"); - return ResponseEntity.ok() - .headers(header) - .contentLength(inputStream.available()) - .contentType(MediaType.APPLICATION_OCTET_STREAM) - .body(resource); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - } - - return null; - } + @Autowired + AuthorizationBitstreamUtils authorizationBitstreamUtils; + @Autowired + private RequestService requestService; /** * Download all Item's bitstreams as single ZIP file. */ - @GetMapping("/allzip") - public void downloadFileZip(@RequestParam("handleId") String handleId, + @PreAuthorize("hasPermission(#uuid, 'ITEM', 'READ')") + @RequestMapping( method = {RequestMethod.GET, RequestMethod.HEAD}, value = "allzip") + public void downloadFileZip(@PathVariable UUID uuid, @RequestParam("handleId") String handleId, HttpServletResponse response, HttpServletRequest request) throws IOException, SQLException, AuthorizeException { if (StringUtils.isBlank(handleId)) { @@ -195,11 +120,11 @@ public void downloadFileZip(@RequestParam("handleId") String handleId, for (Bundle original : bundles) { List bss = original.getBitstreams(); for (Bitstream bitstream : bss) { - authorizeBitstreamAction(context, bitstream, response); - String filename = bitstream.getName(); ZipArchiveEntry ze = new ZipArchiveEntry(filename); zip.putArchiveEntry(ze); + // Get content of the bitstream + // Retrieve method authorize bitstream download action. InputStream is = bitstreamService.retrieve(context, bitstream); IOUtils.copy(is, zip); zip.closeArchiveEntry(); @@ -209,37 +134,4 @@ public void downloadFileZip(@RequestParam("handleId") String handleId, zip.close(); response.getOutputStream().flush(); } - - /** - * Could the user download that bitstream? - * @param context DSpace context object - * @param bitstream Bitstream to download - * @param response for possibility to redirect - */ - private void authorizeBitstreamAction(Context context, Bitstream bitstream, HttpServletResponse response) - throws IOException { - - String uiURL = configurationService.getProperty("dspace.ui.url"); - if (StringUtils.isBlank(uiURL)) { - log.error("Configuration property `dspace.ui.url` cannot be empty or null!"); - throw new RuntimeException("Configuration property `dspace.ui.url` cannot be empty or null!"); - } - try { - authorizeService.authorizeAction(context, bitstream, Constants.READ); - } catch (MissingLicenseAgreementException e) { - response.sendRedirect(uiURL + "/bitstream/" + bitstream.getID() + "/download"); - } catch (AuthorizeException | SQLException e) { - response.sendRedirect(uiURL + "/login"); - } - } - - /** - * Check if the bitstream has file extension. - */ - private void checkBitstreamExtensions(BitstreamFormat bitstreamFormat) { - if ( Objects.isNull(bitstreamFormat) || CollectionUtils.isEmpty(bitstreamFormat.getExtensions())) { - log.error("Bitstream Extensions cannot be empty for downloading/previewing bitstreams."); - throw new RuntimeException("Bitstream Extensions cannot be empty for downloading/previewing bitstreams."); - } - } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationRestControllerIT.java index 03a5ae2ecc11..82546b0dacff 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationRestControllerIT.java @@ -39,6 +39,8 @@ import org.dspace.builder.ClarinUserRegistrationBuilder; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.ResourcePolicyBuilder; import org.dspace.builder.WorkspaceItemBuilder; import org.dspace.content.Bitstream; import org.dspace.content.Collection; @@ -52,6 +54,11 @@ import org.dspace.content.service.clarin.ClarinLicenseLabelService; import org.dspace.content.service.clarin.ClarinLicenseResourceMappingService; import org.dspace.content.service.clarin.ClarinLicenseService; +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.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; @@ -59,8 +66,8 @@ public class AuthorizationRestControllerIT extends AbstractControllerIntegrationTest { - private static final String CLARIN_LICENSE_NAME = "Test Clarin License"; - private static final String TEXT_PLAIN_UTF_8 = "text/plain;charset=UTF-8"; + public static final String CLARIN_LICENSE_NAME = "Test Clarin License"; + public static final String TEXT_PLAIN_UTF_8 = "text/plain;charset=UTF-8"; @Autowired ClarinLicenseService clarinLicenseService; @@ -72,6 +79,8 @@ public class AuthorizationRestControllerIT extends AbstractControllerIntegration Item item; WorkspaceItem witem; ClarinLicense clarinLicense; + EPerson ePerson2; + String eperson2Password = "secret"; @Before public void setup() throws Exception { @@ -89,6 +98,19 @@ public void setup() throws Exception { context.setCurrentUser(eperson); InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); + // Create another person to try downloading the bitstream as anonymous user. + ePerson2 = EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("test@dtq.com") + .withPassword("secret") + .withNameInMetadata("Test", "User") + .build(); + + // The new user is not added into anonymous group by default + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + Group anonymousGroup = groupService.findByName(context, Group.ANONYMOUS); + groupService.addMember(context, anonymousGroup, ePerson2); + witem = WorkspaceItemBuilder.createWorkspaceItem(context, col1) .withTitle("Test WorkspaceItem") .withIssueDate("2017-10-17") @@ -96,9 +118,16 @@ public void setup() throws Exception { .build(); item = witem.getItem(); + Bitstream bitstream = item.getBundles().get(0).getBitstreams().get(0); + + // Create resource policy to allow anonymous user download the bitstream + ResourcePolicyBuilder.createResourcePolicy(context).withUser(ePerson2) + .withAction(Constants.READ) + .withGroup(anonymousGroup) + .withDspaceObject(bitstream).build(); // Create clarin license with clarin license label - clarinLicense = createClarinLicense(CLARIN_LICENSE_NAME, "Test Def", "Test R Info", 1); + clarinLicense = createClarinLicense(CLARIN_LICENSE_NAME, "Test Def", "Test R Info", 3); context.restoreAuthSystemState(); } @@ -118,12 +147,12 @@ public void shouldAuthorizeUserAsSubmitter() throws Exception { // DownloadTokenExpiredException, 401 @Test public void shouldNotAuthorizeUserByWrongToken() throws Exception { - // Admin is not the submitter. - String authTokenAdmin = getAuthToken(admin.getEmail(), password); + // The user is not submitter. + String authTokenUser = getAuthToken(ePerson2.getEmail(), eperson2Password); // Load bitstream from the item. Bitstream bitstream = item.getBundles().get(0).getBitstreams().get(0); - getClient(authTokenAdmin).perform(get("/api/authrn/" + + getClient(authTokenUser).perform(get("/api/authrn/" + bitstream.getID().toString() + "?dtoken=wrongToken")) .andExpect(status().isUnauthorized()) .andExpect(status().reason(is(Matchers.is(DownloadTokenExpiredException.NAME)))); @@ -192,10 +221,10 @@ public void shouldNotAuthorizeByExpiredToken() throws Exception { context.restoreAuthSystemState(); Bitstream bitstream = witem.getItem().getBundles().get(0).getBitstreams().get(0); - // Admin is not the submitter - String authTokenAdmin = getAuthToken(admin.getEmail(), password); - // The admin should be authorized to download the bitstream with token - getClient(authTokenAdmin).perform(get("/api/authrn/" + + // The user is not submitter + String authTokenUser = getAuthToken(ePerson2.getEmail(), eperson2Password); + // The user should not be authorized to download the bitstream with expired token + getClient(authTokenUser).perform(get("/api/authrn/" + bitstream.getID().toString() + "?dtoken=" + token)) .andExpect(status().isUnauthorized()) .andExpect(status().reason(is(Matchers.is(DownloadTokenExpiredException.NAME)))); @@ -254,8 +283,8 @@ public void shouldNotAuthorizeWhenUserMetadataAreNotFilledIn() throws Exception Bitstream bitstream = witem.getItem().getBundles().get(0).getBitstreams().get(0); // Admin is not the submitter - String authTokenAdmin = getAuthToken(admin.getEmail(), password); - getClient(authTokenAdmin).perform(get("/api/authrn/" + bitstream.getID().toString())) + String eperson2Token = getAuthToken(ePerson2.getEmail(), eperson2Password); + getClient(eperson2Token).perform(get("/api/authrn/" + bitstream.getID().toString())) .andExpect(status().isUnauthorized()) .andExpect(status().reason(is(Matchers.is(MissingLicenseAgreementException.NAME)))); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadataBitstreamControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadataBitstreamControllerIT.java index d397062e0200..63f7e62206f8 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadataBitstreamControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadataBitstreamControllerIT.java @@ -19,6 +19,7 @@ import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; import org.apache.commons.io.IOUtils; +import org.dspace.app.rest.model.ItemRest; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.authorize.service.AuthorizeService; import org.dspace.builder.BitstreamBuilder; @@ -33,9 +34,9 @@ import org.springframework.beans.factory.annotation.Autowired; public class MetadataBitstreamControllerIT extends AbstractControllerIntegrationTest { - private static final String METADATABITSTREAM_ENDPOINT = "/api/core/bitstreams/"; - private static final String METADATABITSTREAM_DOWNLOAD_SINGLE_ENDPOINT = METADATABITSTREAM_ENDPOINT + "/handle"; - private static final String METADATABITSTREAM_DOWNLOAD_ALL_ENDPOINT = METADATABITSTREAM_ENDPOINT + "/allzip"; + private static final String METADATABITSTREAM_ENDPOINT = "/api/" + ItemRest.CATEGORY + "/" + ItemRest.PLURAL_NAME; + private static final String ALL_ZIP_PATH = "allzip"; + private static final String HANDLE_PARAM = "handleId"; private static final String AUTHOR = "Test author name"; private Collection col; @@ -75,29 +76,6 @@ public void setUp() throws Exception { context.restoreAuthSystemState(); } - @Test - public void downloadSingleFileNullPathVariable() throws Exception { - getClient().perform(get(METADATABITSTREAM_DOWNLOAD_SINGLE_ENDPOINT)).andExpect(status().is4xxClientError()); - } - - @Test - public void downloadSingleFileWithAuthorize() throws Exception { - InputStream ip = bitstreamService.retrieve(context, bts); - String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get(METADATABITSTREAM_DOWNLOAD_SINGLE_ENDPOINT + - "/" + publicItem.getHandle() + "/" + bts.getName())) - .andExpect(status().isOk()) - .andExpect(content().contentType("application/octet-stream;charset=UTF-8")) - .andExpect(content().bytes(IOUtils.toByteArray(ip))); - } - - @Test - public void downloadSingleFileWithNoAuthorize() throws Exception { - getClient().perform(get(METADATABITSTREAM_DOWNLOAD_SINGLE_ENDPOINT + - "/" + publicItem.getHandle() + "/" + bts.getName())) - .andExpect(status().isOk()); - } - @Test public void downloadAllZip() throws Exception { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); @@ -113,12 +91,10 @@ public void downloadAllZip() throws Exception { zip.close(); String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get(METADATABITSTREAM_DOWNLOAD_ALL_ENDPOINT ).param("handleId", - publicItem.getHandle())) + getClient(token).perform(get(METADATABITSTREAM_ENDPOINT + "/" + publicItem.getID() + + "/" + ALL_ZIP_PATH).param(HANDLE_PARAM, publicItem.getHandle())) .andExpect(status().isOk()) .andExpect(content().bytes(byteArrayOutputStream.toByteArray())); } - - } From 2f8a81d9095093f091f82633bec0152f68323408 Mon Sep 17 00:00:00 2001 From: milanmajchrak <90026355+milanmajchrak@users.noreply.github.com> Date: Wed, 15 Nov 2023 12:55:58 +0100 Subject: [PATCH 02/11] ufal/be-cannot-download-and-preview-files-after-migration (#454) * Find bitstream format using mimetype not ID. * Updated tests according to fix. --- .../rest/ClarinBitstreamImportController.java | 7 +++-- .../ClarinBitstreamImportControllerIT.java | 26 +++++++++---------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ClarinBitstreamImportController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ClarinBitstreamImportController.java index 97ac1494d160..63380a756c2f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ClarinBitstreamImportController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ClarinBitstreamImportController.java @@ -127,11 +127,10 @@ public BitstreamRest importBitstreamForExistingFile(HttpServletRequest request) log.info("SequenceId is null. Bitstream UUID: " + bitstream.getID()); } //add bitstream format - String bitstreamFormatIdString = request.getParameter("bitstreamFormat"); - Integer bitstreamFormatId = getIntegerFromString(bitstreamFormatIdString); + String bitstreamFormatMimeType = request.getParameter("bitstreamFormat"); BitstreamFormat bitstreamFormat = null; - if (!Objects.isNull(bitstreamFormatId)) { - bitstreamFormat = bitstreamFormatService.find(context, bitstreamFormatId); + if (StringUtils.isNotBlank(bitstreamFormatMimeType)) { + bitstreamFormat = bitstreamFormatService.findByMIMEType(context, bitstreamFormatMimeType); } bitstream.setFormat(context, bitstreamFormat); String deletedString = request.getParameter("deleted"); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinBitstreamImportControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinBitstreamImportControllerIT.java index e16260565dc4..9e0c0f339a7c 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinBitstreamImportControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinBitstreamImportControllerIT.java @@ -95,7 +95,7 @@ public void setup() throws Exception { .withName("TESTINGBUNDLE") .build(); token = getAuthToken(admin.getEmail(), password); - bitstreamFormat = BitstreamFormatBuilder.createBitstreamFormat(context).build(); + bitstreamFormat = BitstreamFormatBuilder.createBitstreamFormat(context).withMimeType("application/pdf").build(); String input = "Hello, World!"; MockMultipartFile file = new MockMultipartFile("file", "hello.txt", MediaType.TEXT_PLAIN_VALUE, @@ -147,7 +147,7 @@ public void importBitstreamForExistingFileWithBundleTest() throws Exception { .contentType(contentType) .param("internal_id", internalId) .param("storeNumber", Integer.toString(storeNumber)) - .param("bitstreamFormat", Integer.toString(bitstreamFormat.getID())) + .param("bitstreamFormat", bitstreamFormat.getMIMEType()) .param("deleted", Boolean.toString(deleted)) .param("sequenceId", Integer.toString(sequence)) .param("primaryBundle_id", "") @@ -156,8 +156,8 @@ public void importBitstreamForExistingFileWithBundleTest() throws Exception { .andReturn().getResponse().getContentAsString(), "$.id")); - checkCreatedBitstream(uuid, internalId, storeNumber, bitstreamFormat.getID(), sequence, deleted, sizeBytes, - checkSum); + checkCreatedBitstream(uuid, internalId, storeNumber, bitstreamFormat.getMIMEType(), sequence, deleted, + sizeBytes, checkSum); //clean all context.turnOffAuthorisationSystem(); @@ -182,7 +182,7 @@ public void importBitstreamForExistingFileWithoutBundleTest() throws Exception { .contentType(contentType) .param("internal_id", internalId) .param("storeNumber", Integer.toString(storeNumber)) - .param("bitstreamFormat", Integer.toString(bitstreamFormat.getID())) + .param("bitstreamFormat", bitstreamFormat.getMIMEType()) .param("deleted", Boolean.toString(deleted)) .param("sequenceId", Integer.toString(sequence)) .param("primaryBundle_id", "") @@ -191,8 +191,8 @@ public void importBitstreamForExistingFileWithoutBundleTest() throws Exception { .andReturn().getResponse().getContentAsString(), "$.id")); - checkCreatedBitstream(uuid, internalId, storeNumber, bitstreamFormat.getID(), sequence, deleted, sizeBytes, - checkSum); + checkCreatedBitstream(uuid, internalId, storeNumber, bitstreamFormat.getMIMEType(), sequence, deleted, + sizeBytes, checkSum); //clean all context.turnOffAuthorisationSystem(); @@ -217,7 +217,7 @@ public void importBitstreamForExistingFileAsPrimaryBitstreamOfBundleTest() throw .contentType(contentType) .param("internal_id", internalId) .param("storeNumber", Integer.toString(storeNumber)) - .param("bitstreamFormat", Integer.toString(bitstreamFormat.getID())) + .param("bitstreamFormat", bitstreamFormat.getMIMEType()) .param("deleted", Boolean.toString(deleted)) .param("sequenceId", Integer.toString(sequence)) .param("primaryBundle_id", bundle.getID().toString()) @@ -226,8 +226,8 @@ public void importBitstreamForExistingFileAsPrimaryBitstreamOfBundleTest() throw .andReturn().getResponse().getContentAsString(), "$.id")); - checkCreatedBitstream(uuid, internalId, storeNumber, bitstreamFormat.getID(), sequence, deleted, sizeBytes, - checkSum); + checkCreatedBitstream(uuid, internalId, storeNumber, bitstreamFormat.getMIMEType(), sequence, deleted, + sizeBytes, checkSum); bundle = bundleService.find(context, bundle.getID()); assertEquals(bundle.getPrimaryBitstream().getID(), bitstream.getID()); @@ -257,7 +257,7 @@ public void importBitstreamForExistingFileValidationErrorTest() throws Exception .contentType(contentType) .param("internal_id", internalId) .param("storeNumber", Integer.toString(storeNumber)) - .param("bitstreamFormat", Integer.toString(bitstreamFormat.getID())) + .param("bitstreamFormat", bitstreamFormat.getMIMEType()) .param("deleted", Boolean.toString(deleted)) .param("sequenceId", Integer.toString(sequence)) .param("primaryBundle_id", "") @@ -271,12 +271,12 @@ public void importBitstreamForExistingFileValidationErrorTest() throws Exception } private void checkCreatedBitstream(UUID uuid, String internalId, int storeNumber, - Integer bitstreamFormat, int sequence, boolean deleted, long sizeBytes, + String bitstreamFormat, int sequence, boolean deleted, long sizeBytes, String checkSum) throws SQLException { bitstream = bitstreamService.find(context, uuid); assertEquals(bitstream.getChecksum(), checkSum); assertEquals(bitstream.getSizeBytes(), sizeBytes); - assertEquals(bitstream.getFormat(context).getID(), bitstreamFormat); + assertEquals(bitstream.getFormat(context).getMIMEType(), bitstreamFormat); assertEquals(bitstream.getInternalId(), internalId); assertEquals(bitstream.getStoreNumber(), storeNumber); assertEquals(bitstream.getSequenceID(), sequence); From 7d1fdb868d689f29c6163fbb28218082f073b150 Mon Sep 17 00:00:00 2001 From: milanmajchrak <90026355+milanmajchrak@users.noreply.github.com> Date: Thu, 23 Nov 2023 10:01:16 +0100 Subject: [PATCH 03/11] ufal/update-canonical-prefix-to-hdl (#460) * Updated `handle.canonical.prefix` to hdl handle. * Integration test cannot have changed handle.canonical.prefix. --- dspace-api/src/test/data/dspaceFolder/config/local.cfg | 2 ++ dspace/config/dspace.cfg | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/test/data/dspaceFolder/config/local.cfg b/dspace-api/src/test/data/dspaceFolder/config/local.cfg index 84f89de4398f..c4c61bf77ff6 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/local.cfg +++ b/dspace-api/src/test/data/dspaceFolder/config/local.cfg @@ -271,3 +271,5 @@ lr.pid.community.configurations = community=*, prefix=123456789, type=local, can #### Authority configuration `authority.cfg` authority.controlled.dc.relation = true + +handle.canonical.prefix = ${dspace.ui.url}/handle/ \ No newline at end of file diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 20e010d72bf2..40ab09083b41 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -305,7 +305,7 @@ identifier.doi.namespaceseparator = dspace/ # # Items in DSpace receive a unique URL, stored in dc.identifier.uri # after it is generated during the submission process. -handle.canonical.prefix = ${dspace.ui.url}/handle/ +handle.canonical.prefix = http://hdl.handle.net/ # If you register with CNRI's handle service at https://www.handle.net/, # these links can be generated as permalinks using https://hdl.handle.net/ From 29880ed71c5765bfcd1126090570c983bbdd1e47 Mon Sep 17 00:00:00 2001 From: milanmajchrak <90026355+milanmajchrak@users.noreply.github.com> Date: Fri, 1 Dec 2023 12:45:40 +0100 Subject: [PATCH 04/11] ufal/be-user-registration-missing (#463) * The user registration is added when the eperson is created by the ui. * try using newer checkout, same as upstream, since error is with git --------- Co-authored-by: MajoBerger <88670521+MajoBerger@users.noreply.github.com> --- .../clarin/ClarinUserRegistration.java | 4 ++ .../repository/EPersonRestRepository.java | 15 +++++++ .../app/rest/EPersonRestRepositoryIT.java | 45 ++++++++++++++++++- 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/content/clarin/ClarinUserRegistration.java b/dspace-api/src/main/java/org/dspace/content/clarin/ClarinUserRegistration.java index a3e3666a01f4..8c8fd8def9b6 100644 --- a/dspace-api/src/main/java/org/dspace/content/clarin/ClarinUserRegistration.java +++ b/dspace-api/src/main/java/org/dspace/content/clarin/ClarinUserRegistration.java @@ -28,8 +28,12 @@ @Table(name = "user_registration") public class ClarinUserRegistration implements ReloadableEntity { + // Anonymous user public static final String ANONYMOUS_USER_REGISTRATION = "anonymous"; + // Registered user without organization + public static final String UNKNOWN_USER_REGISTRATION = "Unknown"; + private static Logger log = org.apache.logging.log4j.LogManager.getLogger(ClarinUserRegistration.class); @Id diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java index 8dd6ed90b1b0..2d381a6abb55 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java @@ -7,6 +7,8 @@ */ package org.dspace.app.rest.repository; +import static org.dspace.content.clarin.ClarinUserRegistration.UNKNOWN_USER_REGISTRATION; + import java.io.IOException; import java.sql.SQLException; import java.util.Arrays; @@ -35,6 +37,8 @@ import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; import org.dspace.authorize.service.ValidatePasswordService; +import org.dspace.content.clarin.ClarinUserRegistration; +import org.dspace.content.service.clarin.ClarinUserRegistrationService; import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.eperson.EmptyWorkflowGroupException; @@ -79,6 +83,9 @@ public class EPersonRestRepository extends DSpaceObjectRestRepository idRef = new AtomicReference(); AtomicReference idRefNoEmbeds = new AtomicReference(); + AtomicReference idRefUserDataReg = new AtomicReference(); + AtomicReference idRefUserDataFullReg = new AtomicReference(); String authToken = getAuthToken(admin.getEmail(), password); @@ -155,11 +158,51 @@ public void createTest() throws Exception { .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", HalMatcher.matchNoEmbeds())) .andDo(result -> idRefNoEmbeds - .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))));; + .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id")))); + + // Check that the user registration for test data user has been created + getClient(authToken).perform(get("/api/core/clarinuserregistration/search/byEPerson") + .param("userUUID", String.valueOf(idRef.get())) + .contentType(contentType)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(1))) + .andExpect(jsonPath( + "$._embedded.clarinuserregistrations[0].id", is(not(empty())))) + .andExpect(jsonPath( + "$._embedded.clarinuserregistrations[0].email", is("createtest@example.com"))) + .andExpect(jsonPath( + "$._embedded.clarinuserregistrations[0].confirmation", is(true))) + .andExpect(jsonPath( + "$._embedded.clarinuserregistrations[0].ePersonID", is(idRef.get().toString()))) + .andDo(result -> idRefUserDataReg + .set(read(result.getResponse().getContentAsString(), + "$._embedded.clarinuserregistrations[0].id"))); + + // Check that the user registration for test data full user has been created + getClient(authToken).perform(get("/api/core/clarinuserregistration/search/byEPerson") + .param("userUUID", String.valueOf(idRefNoEmbeds.get())) + .contentType(contentType)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(1))) + .andExpect(jsonPath( + "$._embedded.clarinuserregistrations[0].id", is(not(empty())))) + .andExpect(jsonPath( + "$._embedded.clarinuserregistrations[0].email", + is("createtestfull@example.com"))) + .andExpect(jsonPath( + "$._embedded.clarinuserregistrations[0].confirmation", is(true))) + .andExpect(jsonPath( + "$._embedded.clarinuserregistrations[0].ePersonID", + is(idRefNoEmbeds.get().toString()))) + .andDo(result -> idRefUserDataFullReg + .set(read(result.getResponse().getContentAsString(), + "$._embedded.clarinuserregistrations[0].id"))); } finally { EPersonBuilder.deleteEPerson(idRef.get()); EPersonBuilder.deleteEPerson(idRefNoEmbeds.get()); + ClarinUserRegistrationBuilder.deleteClarinUserRegistration(idRefUserDataReg.get()); + ClarinUserRegistrationBuilder.deleteClarinUserRegistration(idRefUserDataFullReg.get()); } } From e2322c5de2cea49940c3df6772530e8300206af1 Mon Sep 17 00:00:00 2001 From: milanmajchrak <90026355+milanmajchrak@users.noreply.github.com> Date: Fri, 1 Dec 2023 12:46:41 +0100 Subject: [PATCH 05/11] ufal/be-license-download-statistics (#462) * Add logging of downloading restricted bitstreams. * Log downloading of every bitstream not only restricted ones. * Added docs * Refactored logging. --- .../clarin/ClarinMatomoBitstreamTracker.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/app/statistics/clarin/ClarinMatomoBitstreamTracker.java b/dspace-api/src/main/java/org/dspace/app/statistics/clarin/ClarinMatomoBitstreamTracker.java index f3b6dc9ea4c4..08287caa6ce8 100644 --- a/dspace-api/src/main/java/org/dspace/app/statistics/clarin/ClarinMatomoBitstreamTracker.java +++ b/dspace-api/src/main/java/org/dspace/app/statistics/clarin/ClarinMatomoBitstreamTracker.java @@ -8,6 +8,7 @@ package org.dspace.app.statistics.clarin; import java.sql.SQLException; +import java.text.MessageFormat; import java.util.List; import java.util.Objects; import javax.servlet.http.HttpServletRequest; @@ -22,6 +23,7 @@ import org.dspace.content.service.ItemService; import org.dspace.content.service.clarin.ClarinItemService; import org.dspace.core.Context; +import org.dspace.eperson.EPerson; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; import org.matomo.java.tracking.CustomVariable; @@ -132,6 +134,24 @@ public void trackBitstreamDownload(Context context, HttpServletRequest request, return; } + // Log the user which is downloading the bitstream + this.logUserDownloadingBitstream(context, bit); + // Track the bitstream downloading event trackPage(context, request, item, "Bitstream Download / Single File"); } + + /** + * Log the user which is downloading the bitstream + * @param context DSpace context object + * @param bit Bitstream which is downloading + */ + private void logUserDownloadingBitstream(Context context, Bitstream bit) { + EPerson eperson = context.getCurrentUser(); + String pattern = "The user name: {0}, uuid: {1} is downloading bitstream name: {2}, uuid: {3}."; + String logMessage = Objects.isNull(eperson) + ? MessageFormat.format(pattern, "ANONYMOUS", "null", bit.getName(), bit.getID()) + : MessageFormat.format(pattern, eperson.getFullName(), eperson.getID(), bit.getName(), bit.getID()); + + log.info(logMessage); + } } From c01af03e588ec447bc0fd10b21391cbb1f52e9ea Mon Sep 17 00:00:00 2001 From: milanmajchrak <90026355+milanmajchrak@users.noreply.github.com> Date: Wed, 6 Dec 2023 07:30:58 +0100 Subject: [PATCH 06/11] ufal/be-shibboleth-headers-encoding (#464) * givenname and last name values are converted to UTF-8 encoding * Delete eperson in tests --- .../clarin/ClarinShibAuthentication.java | 8 +-- .../dspace/authenticate/clarin/Headers.java | 58 +++++++++++++++--- .../ClarinShibbolethLoginFilterIT.java | 61 +++++++++++++++++-- dspace/config/clarin-dspace.cfg | 4 ++ 4 files changed, 115 insertions(+), 16 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/authenticate/clarin/ClarinShibAuthentication.java b/dspace-api/src/main/java/org/dspace/authenticate/clarin/ClarinShibAuthentication.java index a10c291ff10f..d553d67308c6 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/clarin/ClarinShibAuthentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/clarin/ClarinShibAuthentication.java @@ -695,8 +695,8 @@ protected EPerson registerNewEPerson(Context context, HttpServletRequest request // Header values String netid = Util.formatNetId(findSingleAttribute(request, netidHeader), org); String email = findSingleAttribute(request, emailHeader); - String fname = findSingleAttribute(request, fnameHeader); - String lname = findSingleAttribute(request, lnameHeader); + String fname = Headers.updateValueByCharset(findSingleAttribute(request, fnameHeader)); + String lname = Headers.updateValueByCharset(findSingleAttribute(request, lnameHeader)); // If the values are not in the request headers try to retrieve it from `shibheaders`. if (StringUtils.isEmpty(netid)) { @@ -817,8 +817,8 @@ protected void updateEPerson(Context context, HttpServletRequest request, EPerso String netid = Util.formatNetId(findSingleAttribute(request, netidHeader), shibheaders.get_idp()); String email = findSingleAttribute(request, emailHeader); - String fname = findSingleAttribute(request, fnameHeader); - String lname = findSingleAttribute(request, lnameHeader); + String fname = Headers.updateValueByCharset(findSingleAttribute(request, fnameHeader)); + String lname = Headers.updateValueByCharset(findSingleAttribute(request, lnameHeader)); // If the values are not in the request headers try to retrieve it from `shibheaders`. if (StringUtils.isEmpty(netid)) { diff --git a/dspace-api/src/main/java/org/dspace/authenticate/clarin/Headers.java b/dspace-api/src/main/java/org/dspace/authenticate/clarin/Headers.java index e683ac2e140d..3305661d194f 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/clarin/Headers.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/clarin/Headers.java @@ -9,6 +9,7 @@ package org.dspace.authenticate.clarin; import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; @@ -16,8 +17,11 @@ import java.util.Map; import javax.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.dspace.services.ConfigurationService; +import org.dspace.utils.DSpace; /** * Helper class for request headers. @@ -29,9 +33,11 @@ public class Headers { private static final Logger log = LogManager.getLogger(org.dspace.authenticate.clarin.Headers.class); // variables // + private static ConfigurationService configurationService = new DSpace().getConfigurationService(); private HashMap> headers_ = new HashMap>(); private String header_separator_ = null; + private static String EMPTY_STRING = ""; // ctors @@ -157,17 +163,53 @@ private List header2values(String header) { /** - * Convert ISO header value to UTF-8 - * @param value ISO header value String - * @return + * Convert ISO header value to UTF-8 or return UTF-8 value if it is not ISO. + * @param value ISO/UTF-8 header value String + * @return Converted ISO value to UTF-8 or UTF-8 value from input */ - private String updateValueByCharset(String value) { + public static String updateValueByCharset(String value) { + String inputEncoding = configurationService.getProperty("shibboleth.name.conversion.inputEncoding", + "ISO-8859-1"); + String outputEncoding = configurationService.getProperty("shibboleth.name.conversion.outputEncoding", + "UTF-8"); + + if (StringUtils.isBlank(value)) { + value = EMPTY_STRING; + } + + // If the value is not ISO-8859-1, then it is already UTF-8 + if (!isISOType(value)) { + return value; + } + try { - return new String(value.getBytes("ISO-8859-1"), "UTF-8"); + // Encode the string to UTF-8 + return new String(value.getBytes(inputEncoding), outputEncoding); } catch (UnsupportedEncodingException ex) { - log.warn("Failed to reconvert shibboleth attribute with value (" - + value + ").", ex); + log.warn("Cannot convert the value: " + value + " from " + inputEncoding + " to " + outputEncoding + + " because of: " + ex.getMessage()); + return value; + } + } + + /** + * Check if the value is ISO-8859-1 encoded. + * @param value String to check + * @return true if the value is ISO-8859-1 encoded, false otherwise + */ + private static boolean isISOType(String value) { + try { + // Encode the string to ISO-8859-1 + byte[] iso8859Bytes = value.getBytes(StandardCharsets.ISO_8859_1); + + // Decode the bytes back to a string using ISO-8859-1 + String decodedString = new String(iso8859Bytes, StandardCharsets.ISO_8859_1); + + // Compare the original string with the decoded string + return StringUtils.equals(value, decodedString); + } catch (Exception e) { + // An exception occurred, so the input is not ISO-8859-1 + return false; } - return value; } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/ClarinShibbolethLoginFilterIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/ClarinShibbolethLoginFilterIT.java index ba793b45a9c9..8b62e95bed79 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/ClarinShibbolethLoginFilterIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/ClarinShibbolethLoginFilterIT.java @@ -446,15 +446,18 @@ public void testRedirectToGivenUntrustedUrl() throws Exception { } @Test - public void testUTF8ShibHeaders() throws Exception { + public void testISOShibHeaders() throws Exception { + String testMail = "test@email.edu"; + String testIdp = IDP_TEST_EPERSON + "test"; + String testNetId = NET_ID_TEST_EPERSON + "000"; // NOTE: The initial call to /shibboleth comes *from* an external Shibboleth site. So, it is always // unauthenticated, but it must include some expected SHIB attributes. // SHIB-MAIL attribute is the default email header sent from Shibboleth after a successful login. // In this test we are simply mocking that behavior by setting it to an existing EPerson. String token = getClient().perform(get("/api/authn/shibboleth") - .header("SHIB-MAIL", clarinEperson.getEmail()) - .header("Shib-Identity-Provider", IDP_TEST_EPERSON) - .header("SHIB-NETID", NET_ID_TEST_EPERSON) + .header("SHIB-MAIL", testMail) + .header("Shib-Identity-Provider", testIdp) + .header("SHIB-NETID", testNetId) .header("SHIB-GIVENNAME", "knihovna KůÅ\u0088 test ŽluÅ¥ouÄ\u008Dký")) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("http://localhost:4000")) @@ -466,6 +469,56 @@ public void testUTF8ShibHeaders() throws Exception { .andExpect(jsonPath("$.authenticated", is(true))) .andExpect(jsonPath("$.authenticationMethod", is("shibboleth"))); + // Check if was created a user with such email and netid. + EPerson ePerson = ePersonService.findByNetid(context, Util.formatNetId(testNetId, testIdp)); + assertTrue(Objects.nonNull(ePerson)); + assertEquals(ePerson.getEmail(), testMail); + assertEquals(ePerson.getFirstName(), "knihovna Kůň test Žluťoučký"); + + EPersonBuilder.deleteEPerson(ePerson.getID()); + + getClient(token).perform( + get("/api/authz/authorizations/search/object") + .param("embed", "feature") + .param("feature", feature) + .param("uri", utils.linkToSingleResource(ePersonRest, "self").getHref())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))) + .andExpect(jsonPath("$._embedded").doesNotExist()); + } + + @Test + public void testUTF8ShibHeaders() throws Exception { + String testMail = "test@email.edu"; + String testIdp = IDP_TEST_EPERSON + "test"; + String testNetId = NET_ID_TEST_EPERSON + "000"; + // NOTE: The initial call to /shibboleth comes *from* an external Shibboleth site. So, it is always + // unauthenticated, but it must include some expected SHIB attributes. + // SHIB-MAIL attribute is the default email header sent from Shibboleth after a successful login. + // In this test we are simply mocking that behavior by setting it to an existing EPerson. + String token = getClient().perform(get("/api/authn/shibboleth") + .header("SHIB-MAIL", testMail) + .header("Shib-Identity-Provider", testIdp) + .header("SHIB-NETID", testNetId) + .header("SHIB-GIVENNAME", "knihovna Kůň test Žluťoučký")) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("http://localhost:4000")) + .andReturn().getResponse().getHeader("Authorization"); + + + getClient(token).perform(get("/api/authn/status")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.authenticated", is(true))) + .andExpect(jsonPath("$.authenticationMethod", is("shibboleth"))); + + // Check if was created a user with such email and netid. + EPerson ePerson = ePersonService.findByNetid(context, Util.formatNetId(testNetId, testIdp)); + assertTrue(Objects.nonNull(ePerson)); + assertEquals(ePerson.getEmail(), testMail); + assertEquals(ePerson.getFirstName(), "knihovna Kůň test Žluťoučký"); + + EPersonBuilder.deleteEPerson(ePerson.getID()); + getClient(token).perform( get("/api/authz/authorizations/search/object") .param("embed", "feature") diff --git a/dspace/config/clarin-dspace.cfg b/dspace/config/clarin-dspace.cfg index 2c50811ec00a..3d317c77b41a 100644 --- a/dspace/config/clarin-dspace.cfg +++ b/dspace/config/clarin-dspace.cfg @@ -224,3 +224,7 @@ themed.by.company.name = dataquest s.r.o. #### Authority configuration `authority.cfg` ## dc.relation authority is configured only because of correct item importing, but it is not used anymore. authority.controlled.dc.relation = true + +#nameConversion +shibboleth.name.conversion.inputEncoding = ISO-8859-1 +shibboleth.name.conversion.outputEncoding = UTF-8 From 6eb3726bf52f7de73a995899bc6769239869aac1 Mon Sep 17 00:00:00 2001 From: MajoBerger Date: Wed, 6 Dec 2023 09:51:12 +0100 Subject: [PATCH 07/11] using our dspace-dependencies because of https://github.com/dataquest-dev/DSpace/issues/466 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 7a1a3fcff6ae..79cb24038666 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ ARG JDK_VERSION=11 # Step 1 - Run Maven Build -FROM dspace/dspace-dependencies:dspace-7_x as build +FROM dataquest/dspace-dependencies:dspace-7_x as build ARG TARGET_DIR=dspace-installer WORKDIR /app # The dspace-installer directory will be written to /install From 4b9e7fc07d285cad4ce064e770350e94cd09b453 Mon Sep 17 00:00:00 2001 From: milanmajchrak <90026355+milanmajchrak@users.noreply.github.com> Date: Wed, 13 Dec 2023 14:18:52 +0100 Subject: [PATCH 08/11] ufal/be-fix-email-parameters Fixed emails sending - added arguments and cfg properties that are sent in the email. (#470) --- .../ClarinAutoRegistrationController.java | 12 ++++++- .../ClarinUserMetadataRestController.java | 18 ++++++++-- dspace/config/clarin-dspace.cfg | 6 ++++ dspace/config/emails/clarin_autoregistration | 35 +++++++++++++------ dspace/config/emails/clarin_download_link | 23 +++++++----- 5 files changed, 71 insertions(+), 23 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ClarinAutoRegistrationController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ClarinAutoRegistrationController.java index 31e234ec2c0c..de2dccb9e866 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ClarinAutoRegistrationController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ClarinAutoRegistrationController.java @@ -84,12 +84,17 @@ public ResponseEntity sendEmail(HttpServletRequest request, HttpServletResponse return null; } + // Fetch DSpace main cfg info and send it in the email String uiUrl = configurationService.getProperty("dspace.ui.url"); + String helpDeskEmail = configurationService.getProperty("lr.help.mail", ""); + String helpDeskPhoneNum = configurationService.getProperty("lr.help.phone", ""); + String dspaceName = configurationService.getProperty("dspace.name", ""); + String dspaceNameShort = configurationService.getProperty("dspace.name.short", ""); + if (StringUtils.isEmpty(uiUrl)) { log.error("Cannot load the `dspace.ui.url` property from the cfg."); throw new RuntimeException("Cannot load the `dspace.ui.url` property from the cfg."); } - // Generate token and create ClarinVerificationToken record with the token and user email. String verificationToken = Utils.generateHexKey(); clarinVerificationToken.setEmail(email); @@ -103,6 +108,11 @@ public ResponseEntity sendEmail(HttpServletRequest request, HttpServletResponse Locale locale = context.getCurrentLocale(); Email bean = Email.getEmail(I18nUtil.getEmailFilename(locale, "clarin_autoregistration")); bean.addArgument(autoregistrationURL); + bean.addArgument(helpDeskEmail); + bean.addArgument(helpDeskPhoneNum); + bean.addArgument(dspaceNameShort); + bean.addArgument(dspaceName); + bean.addArgument(uiUrl); bean.addRecipient(email); bean.send(); } catch (Exception e) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClarinUserMetadataRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClarinUserMetadataRestController.java index 170e85bd6035..e726c600ce80 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClarinUserMetadataRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClarinUserMetadataRestController.java @@ -173,9 +173,18 @@ private void sendEmailWithDownloadLink(Context context, UUID bitstreamUUID, throw new BadRequestException("Cannot find the clarin license for the bitstream with ID: " + bitstreamUUID); } + // Fetch DSpace main cfg info and send it in the email + String uiUrl = configurationService.getProperty("dspace.ui.url", ""); + String helpDeskEmail = configurationService.getProperty("lr.help.mail", ""); + String helpDeskPhoneNum = configurationService.getProperty("lr.help.phone", ""); + String dspaceName = configurationService.getProperty("dspace.name", ""); + String dspaceNameShort = configurationService.getProperty("dspace.name.short", ""); + + if (StringUtils.isEmpty(uiUrl)) { + log.error("Cannot load the `dspace.ui.url` property from the cfg."); + throw new RuntimeException("Cannot load the `dspace.ui.url` property from the cfg."); + } // Compose download link - // Get UI url - String uiUrl = configurationService.getProperty("dspace.ui.url"); String downloadLink = uiUrl + "/" + BitstreamRest.PLURAL_NAME + "/" + bitstream.getID() + "/download?dtoken=" + downloadToken; @@ -185,6 +194,11 @@ private void sendEmailWithDownloadLink(Context context, UUID bitstreamUUID, bean.addArgument(bitstream.getName()); bean.addArgument(downloadLink); bean.addArgument(clarinLicense.getDefinition()); + bean.addArgument(helpDeskEmail); + bean.addArgument(helpDeskPhoneNum); + bean.addArgument(dspaceNameShort); + bean.addArgument(dspaceName); + bean.addArgument(uiUrl); bean.addRecipient(email); bean.send(); } catch (MessagingException e) { diff --git a/dspace/config/clarin-dspace.cfg b/dspace/config/clarin-dspace.cfg index 3d317c77b41a..e51dca06ba8e 100644 --- a/dspace/config/clarin-dspace.cfg +++ b/dspace/config/clarin-dspace.cfg @@ -2,6 +2,12 @@ # one day similar to this # https://github.com/ufal/clarin-dspace/blob/clarin/utilities/project_helpers/config/local.conf.dist +#------------------------------------------------------------------# +#---------------------------DSpace---------------------------------# +#------------------------------------------------------------------# +dspace.name.short = DSpace +dspace.name = CLARIN DSpace + #------------------------------------------------------------------# #---------------------------UPLOAD FILE----------------------------# #------------------------------------------------------------------# diff --git a/dspace/config/emails/clarin_autoregistration b/dspace/config/emails/clarin_autoregistration index f39770cfd258..c05ba67d9e78 100644 --- a/dspace/config/emails/clarin_autoregistration +++ b/dspace/config/emails/clarin_autoregistration @@ -1,14 +1,27 @@ -## E-mail sent to confirm authenticity of the user's e-mail. -## -## Parameters: {0} confirmation e-mail -## -## See org.dspace.core.Email for information on the format of this file. -## +# E-mail sent to confirm authenticity of the user's e-mail. +# +# Parameters: {0} confirmation e-mail +# {1} helpdesk email +# {2} helpdesk phone number +# {3} DSpace name short +# {4} DSpace name +# {5} DSpace UI url +# +# See org.dspace.core.Email for information on the format of this file. +# +Subject: ${params[3]}: Account Registration +To complete registration for a ${params[3]} repository account at {params[5]}, please click the link below: -Confirmation e-mail: ${params[0]} + ${params[0]} + +If you need assistance with your account, please email +${params[1]} or call us at ${params[2]} + + +${params[3]} Team _____________________________________ -${dspace.name}, -WWW: ${dspace.url} -Email: ${lr.help.mail} -Tel.: ${lr.help.phone} +${params[4]}, +WWW: ${params[5]} +Email: ${params[1]} +Tel.: ${params[2]} diff --git a/dspace/config/emails/clarin_download_link b/dspace/config/emails/clarin_download_link index d44e18cca7c0..bf0bb380f3b5 100644 --- a/dspace/config/emails/clarin_download_link +++ b/dspace/config/emails/clarin_download_link @@ -3,24 +3,29 @@ # Parameters: {0} is expanded to filename # {1} to a download URL # {2} to license url +# {3} helpdesk email +# {4} helpdesk phone number +# {5} DSpace name short +# {6} DSpace name +# {7} DSpace UI url # # See org.dspace.core.Email for information on the format of this file. # -Subject: ${lr.dspace.name.short}: Download instructions for {0} +Subject: ${params[5]}: Download instructions for ${params[0]} To download the file you have requested, please click the link below: - {1} + ${params[1]} Remember, the file is distributed under specific license: - {2} + ${params[2]} If you have trouble downloading the file or if you did not request this download please contact -${lr.help.mail} or call us at ${lr.help.phone} +${params[3]} or call us at ${params[4]} -${lr.dspace.name.short} Team +${params[5]} Team _____________________________________ -${dspace.name}, -WWW: ${dspace.url} -Email: ${lr.help.mail} -Tel.: ${lr.help.phone} \ No newline at end of file +${params[6]}, +WWW: ${params[7]} +Email: ${params[3]} +Tel.: ${params[4]} \ No newline at end of file From 989e6e81c1cb905e8da93560b2308830cf1215ef Mon Sep 17 00:00:00 2001 From: milanmajchrak <90026355+milanmajchrak@users.noreply.github.com> Date: Wed, 13 Dec 2023 14:19:44 +0100 Subject: [PATCH 09/11] ufal/be-provenance-subbmitter-missing (#469) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * #8585 Add submitter information to provenance metadata * Cherry picked fix from vanilla and added test to check if the Item provenance metadata is added --------- Co-authored-by: Adán Román Ruiz --- .../xmlworkflow/XmlWorkflowServiceImpl.java | 25 ++++--- .../ClarinWorkflowItemRestRepositoryIT.java | 70 +++++++++++++++++++ 2 files changed, 86 insertions(+), 9 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java index da7910da29f2..51292fd4773a 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java @@ -221,6 +221,8 @@ public XmlWorkflowItem start(Context context, WorkspaceItem wsi) //Get our next step, if none is found, archive our item firstStep = wf.getNextStep(context, wfi, firstStep, ActionResult.OUTCOME_COMPLETE); if (firstStep == null) { + // record the submitted provenance message + recordStart(context, wfi.getItem(),null); archive(context, wfi); } else { activateFirstStep(context, wf, firstStep, wfi); @@ -334,7 +336,7 @@ protected void activateFirstStep(Context context, Workflow wf, Step firstStep, X + "item_id=" + wfi.getItem().getID() + "collection_id=" + wfi.getCollection().getID())); - // record the start of the workflow w/provenance message +// record the start of the workflow w/provenance message recordStart(context, wfi.getItem(), firstActionConfig.getProcessingAction()); //Fire an event ! @@ -1187,25 +1189,30 @@ protected void recordStart(Context context, Item myitem, Action action) DCDate now = DCDate.getCurrent(); // Create provenance description - String provmessage = ""; + StringBuffer provmessage = new StringBuffer(); if (myitem.getSubmitter() != null) { - provmessage = "Submitted by " + myitem.getSubmitter().getFullName() - + " (" + myitem.getSubmitter().getEmail() + ") on " - + now.toString() + " workflow start=" + action.getProvenanceStartId() + "\n"; + provmessage.append("Submitted by ").append(myitem.getSubmitter().getFullName()) + .append(" (").append(myitem.getSubmitter().getEmail()).append(") on ") + .append(now.toString()); } else { // else, null submitter - provmessage = "Submitted by unknown (probably automated) on" - + now.toString() + " workflow start=" + action.getProvenanceStartId() + "\n"; + provmessage.append("Submitted by unknown (probably automated) on") + .append(now.toString()); + } + if (action != null) { + provmessage.append(" workflow start=").append(action.getProvenanceStartId()).append("\n"); + } else { + provmessage.append("\n"); } // add sizes and checksums of bitstreams - provmessage += installItemService.getBitstreamProvenanceMessage(context, myitem); + provmessage.append(installItemService.getBitstreamProvenanceMessage(context, myitem)); // Add message to the DC itemService .addMetadata(context, myitem, MetadataSchemaEnum.DC.getName(), - "description", "provenance", "en", provmessage); + "description", "provenance", "en", provmessage.toString()); itemService.update(context, myitem); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinWorkflowItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinWorkflowItemRestRepositoryIT.java index 2181bedcf08c..20ec8dc38f24 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinWorkflowItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinWorkflowItemRestRepositoryIT.java @@ -10,13 +10,16 @@ import static com.jayway.jsonpath.JsonPath.read; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadata; +import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertFalse; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import java.util.List; import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; @@ -24,17 +27,22 @@ import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; import org.dspace.builder.ItemBuilder; import org.dspace.builder.VersionBuilder; import org.dspace.builder.WorkspaceItemBuilder; import org.dspace.content.Collection; import org.dspace.content.Item; +import org.dspace.content.MetadataValue; +import org.dspace.content.WorkspaceItem; import org.dspace.content.service.ItemService; import org.dspace.content.service.WorkspaceItemService; +import org.dspace.eperson.EPerson; import org.dspace.license.service.CreativeCommonsService; import org.dspace.services.ConfigurationService; import org.dspace.xmlworkflow.factory.XmlWorkflowFactory; import org.dspace.xmlworkflow.storedcomponents.service.CollectionRoleService; +import org.dspace.xmlworkflow.storedcomponents.service.XmlWorkflowItemService; import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Before; @@ -64,6 +72,8 @@ public class ClarinWorkflowItemRestRepositoryIT extends AbstractControllerIntegr @Autowired private CreativeCommonsService creativeCommonsService; + @Autowired + private XmlWorkflowItemService xmlWorkflowItemService; @Autowired private ItemService itemService; @@ -250,4 +260,64 @@ public void shouldAddNewHandleToItemMetadata() throws Exception { WorkspaceItemBuilder.deleteWorkspaceItem(idWorkspaceItemRef.get()); } } + + + @Test + public void shouldCreateProvenanceMessageOnItemSubmit() throws Exception { + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community with one collection. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + //2. create a normal user to use as submitter + EPerson submitter = EPersonBuilder.createEPerson(context) + .withEmail("submitter@example.com") + .withPassword("dspace") + .build(); + + // Submitter group - allow deposit a new item without workflow + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 2") + .withSubmitterGroup(submitter) + .build(); + context.setCurrentUser(submitter); + + //3. a workspace item + WorkspaceItem wsitem = WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withTitle("Submission Item") + .withIssueDate("2017-10-17") + .grantLicense() + .build(); + + context.restoreAuthSystemState(); + + // get the submitter auth token + String authToken = getAuthToken(submitter.getEmail(), "dspace"); + + // submit the workspaceitem to start the workflow + getClient(authToken) + .perform(post(BASE_REST_SERVER_URL + "/api/workflow/workflowitems") + .content("/api/submission/workspaceitems/" + wsitem.getID()) + .contentType(textUriContentType)) + .andExpect(status().isCreated()); + + // Load deposited item and check the provenance metadata + Item depositedItem = itemService.find(context, wsitem.getItem().getID()); + List mvList = itemService.getMetadata(depositedItem, "dc", "description", + "provenance", Item.ANY); + assertFalse(mvList.isEmpty()); + + // Check if the provenance contains the submitter info + boolean containsSubmitterProvenance = false; + for (MetadataValue mv: mvList) { + if (mv.getValue().contains("Submitted by " + submitter.getEmail())) { + containsSubmitterProvenance = true; + break; + } + } + assertThat(containsSubmitterProvenance, is(true)); + } } From 85225d27a042d597427d1a165f4b2fed605740e9 Mon Sep 17 00:00:00 2001 From: milanmajchrak <90026355+milanmajchrak@users.noreply.github.com> Date: Thu, 14 Dec 2023 16:29:40 +0100 Subject: [PATCH 10/11] ufal/be-get-user-ip-address (#468) * Created endpoint for fetching IP address with tests. * Made the code more understable --- .../app/rest/ClarinUserInfoController.java | 56 +++++++++++++++++++ .../app/rest/ClarinUserInfoControllerIT.java | 34 +++++++++++ 2 files changed, 90 insertions(+) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/ClarinUserInfoController.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinUserInfoControllerIT.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ClarinUserInfoController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ClarinUserInfoController.java new file mode 100644 index 000000000000..fc4bf5f19310 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ClarinUserInfoController.java @@ -0,0 +1,56 @@ +/** + * 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; + +import java.io.IOException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +/** + * This class is a REST controller that returns information about the client user. + * E.g. the client's IP address. + * + * @author Milan Majchrak (milan.majchrak at dataquest.sk) + */ +@RequestMapping(value = "/api/userinfo") +@RestController +public class ClarinUserInfoController { + + private final ObjectMapper objectMapper = new ObjectMapper(); + /** + * This method returns the client's IP address. + * @param request The HttpServletRequest object. + * @return The client's IP address. + */ + @RequestMapping(method = RequestMethod.GET, path = "/ipaddress") + public ResponseEntity getUserIPAddress(HttpServletRequest request, HttpServletResponse response) + throws IOException { + // Get client's IP address + String ipAddress = request.getRemoteAddr(); + if (StringUtils.isBlank(ipAddress)) { + String errorMessage = "Cannot get user's IP address"; + response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, errorMessage); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorMessage); + } + + // Create JSON object using Jackson's ObjectNode + ObjectNode jsonObject = objectMapper.createObjectNode(); + jsonObject.put("ipAddress", ipAddress); + + return ResponseEntity.ok().body(jsonObject); + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinUserInfoControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinUserInfoControllerIT.java new file mode 100644 index 000000000000..be32cc55a656 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinUserInfoControllerIT.java @@ -0,0 +1,34 @@ +/** + * 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; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import javax.ws.rs.core.MediaType; + +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.junit.Test; + +/** + * This class test the REST controller that returns information about the client user. + * E.g. the client's IP address. + * + * @author Milan Majchrak (milan.majchrak at dataquest.sk) + */ +public class ClarinUserInfoControllerIT extends AbstractControllerIntegrationTest { + + @Test + public void getUserIPAddress() throws Exception { + getClient().perform(get("/api/userinfo/ipaddress") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().json("{\"ipAddress\":\"127.0.0.1\"}")); + } +} From f85e76ad60759be44f094dd4bb2978bfa1eb7c0b Mon Sep 17 00:00:00 2001 From: milanmajchrak <90026355+milanmajchrak@users.noreply.github.com> Date: Thu, 14 Dec 2023 16:30:01 +0100 Subject: [PATCH 11/11] ufal/publisher-ok-fix (#473) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fixes ufal/clarin-dspace#1096 (#471) * Added publisher filter in the integration test check --------- Co-authored-by: Ondřej Košarko --- .../app/rest/ClarinDiscoveryRestControllerIT.java | 1 + .../dspace/app/rest/matcher/SearchFilterMatcher.java | 11 +++++++++++ dspace/config/spring/api/discovery.xml | 12 ++++++++++++ 3 files changed, 24 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinDiscoveryRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinDiscoveryRestControllerIT.java index 95051697a462..72b12c7962af 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinDiscoveryRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinDiscoveryRestControllerIT.java @@ -984,6 +984,7 @@ public void discoverSearchTest() throws Exception { SearchFilterMatcher.authorFilter(), SearchFilterMatcher.subjectFilter(), // SearchFilterMatcher.dateIssuedFilter(), + SearchFilterMatcher.publisherFilter(), SearchFilterMatcher.hasContentInOriginalBundleFilter(), SearchFilterMatcher.hasFileNameInOriginalBundleFilter(), SearchFilterMatcher.hasFileDescriptionInOriginalBundleFilter(), diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SearchFilterMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SearchFilterMatcher.java index 2d38aeb12968..7700d5291249 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SearchFilterMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SearchFilterMatcher.java @@ -195,4 +195,15 @@ public static Matcher clarinItemsTypeFilter() { checkOperators() ); } + + public static Matcher publisherFilter() { + return allOf( + hasJsonPath("$.filter", is("publisher")), + hasJsonPath("$.hasFacets", is(false)), + hasJsonPath("$.type", is("text")), + hasJsonPath("$.openByDefault", is(false)), + checkOperators() + + ); + } } diff --git a/dspace/config/spring/api/discovery.xml b/dspace/config/spring/api/discovery.xml index 9b8b8351fe55..d40242314260 100644 --- a/dspace/config/spring/api/discovery.xml +++ b/dspace/config/spring/api/discovery.xml @@ -200,6 +200,7 @@ + @@ -2488,6 +2489,17 @@ + + + + + dc.publisher + + + + + +