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); + } } 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-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-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-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-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/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/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/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()); } } 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())); } - - } 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 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/