diff --git a/dspace-api/src/main/java/org/dspace/content/WorkspaceItem.java b/dspace-api/src/main/java/org/dspace/content/WorkspaceItem.java index a4c880173bf7..77937e8ee76b 100644 --- a/dspace-api/src/main/java/org/dspace/content/WorkspaceItem.java +++ b/dspace-api/src/main/java/org/dspace/content/WorkspaceItem.java @@ -73,6 +73,9 @@ public class WorkspaceItem @Column(name = "page_reached") private Integer pageReached = -1; + @Column(name = "share_token") + private String shareToken = null; + /** * Protected constructor, create object using: * {@link org.dspace.content.service.WorkspaceItemService#create(Context, Collection, boolean)} @@ -131,6 +134,14 @@ public void setPageReached(int v) { pageReached = v; } + public String getShareToken() { + return shareToken; + } + + public void setShareToken(String shareToken) { + this.shareToken = shareToken; + } + /** * Decide if this WorkspaceItem is equal to another * diff --git a/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java index b6e7372af184..f39ab6ea526e 100644 --- a/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java @@ -243,6 +243,11 @@ public WorkspaceItem findByItem(Context context, Item item) throws SQLException return workspaceItemDAO.findByItem(context, item); } + @Override + public List findByShareToken(Context context, String shareToken) throws SQLException { + return workspaceItemDAO.findByShareToken(context, shareToken); + } + @Override public List findAll(Context context) throws SQLException { return workspaceItemDAO.findAll(context); diff --git a/dspace-api/src/main/java/org/dspace/content/dao/WorkspaceItemDAO.java b/dspace-api/src/main/java/org/dspace/content/dao/WorkspaceItemDAO.java index 900858b72869..6996d6ce4010 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/WorkspaceItemDAO.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/WorkspaceItemDAO.java @@ -37,6 +37,8 @@ public List findByEPerson(Context context, EPerson ep, Integer li public WorkspaceItem findByItem(Context context, Item i) throws SQLException; + public List findByShareToken(Context context, String shareToken) throws SQLException; + public List findAll(Context context) throws SQLException; public List findAll(Context context, Integer limit, Integer offset) throws SQLException; diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/WorkspaceItemDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/WorkspaceItemDAOImpl.java index 138451365522..43b127e7c939 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/WorkspaceItemDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/WorkspaceItemDAOImpl.java @@ -81,6 +81,14 @@ public WorkspaceItem findByItem(Context context, Item i) throws SQLException { return uniqueResult(context, criteriaQuery, false, WorkspaceItem.class); } + @Override + public List findByShareToken(Context context, String shareToken) throws SQLException { + Query query = createQuery(context, + "from WorkspaceItem ws where ws.shareToken = :shareToken"); + query.setParameter("shareToken", shareToken); + return list(query); + } + @Override public List findAll(Context context) throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); diff --git a/dspace-api/src/main/java/org/dspace/content/service/WorkspaceItemService.java b/dspace-api/src/main/java/org/dspace/content/service/WorkspaceItemService.java index c8df68e43498..c4e9c54575a1 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/WorkspaceItemService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/WorkspaceItemService.java @@ -127,6 +127,16 @@ public List findByCollection(Context context, Collection collecti public WorkspaceItem findByItem(Context context, Item item) throws SQLException; + /** + * Find the workspace item by the share token. + * @param context the DSpace context object + * @param shareToken the share token + * @return the List of workspace items or null + * @throws SQLException if database error + */ + public List findByShareToken(Context context, String shareToken) + throws SQLException; + /** * Get all workspace items in the whole system * diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2024.09.30__Add_share_token_to_workspaceitem.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2024.09.30__Add_share_token_to_workspaceitem.sql new file mode 100644 index 000000000000..af472c74f97b --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2024.09.30__Add_share_token_to_workspaceitem.sql @@ -0,0 +1,9 @@ +-- +-- 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/ +-- + +ALTER TABLE workspaceitem ADD share_token varchar(32); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2024.09.30__Add_share_token_to_workspaceitem.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2024.09.30__Add_share_token_to_workspaceitem.sql new file mode 100644 index 000000000000..af472c74f97b --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2024.09.30__Add_share_token_to_workspaceitem.sql @@ -0,0 +1,9 @@ +-- +-- 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/ +-- + +ALTER TABLE workspaceitem ADD share_token varchar(32); diff --git a/dspace-api/src/test/java/org/dspace/builder/WorkspaceItemBuilder.java b/dspace-api/src/test/java/org/dspace/builder/WorkspaceItemBuilder.java index 3f2f46836819..6af9423a5f16 100644 --- a/dspace-api/src/test/java/org/dspace/builder/WorkspaceItemBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/WorkspaceItemBuilder.java @@ -250,4 +250,13 @@ public WorkspaceItemBuilder withMetadata(final String schema, final String eleme final String value) { return addMetadataValue(schema, element, qualifier, value); } + + public WorkspaceItemBuilder withShareToken(String shareToken) { + try { + workspaceItem.setShareToken(shareToken); + } catch (Exception e) { + handleException(e); + } + return this; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ShareSubmissionLinkDTO.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ShareSubmissionLinkDTO.java new file mode 100644 index 000000000000..26b9d5e0679f --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ShareSubmissionLinkDTO.java @@ -0,0 +1,29 @@ +/** + * 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.model; + +/** + * This class represents a DTO that will be used to share a submission link. It will be used to return the share link + * to the user in the UI. + * + * @author Milan Majchrak (dspace at dataquest.sk) + */ +public class ShareSubmissionLinkDTO { + + private String shareLink; + + public ShareSubmissionLinkDTO() { } + + public String getShareLink() { + return shareLink; + } + + public void setShareLink(String shareLink) { + this.shareLink = shareLink; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionController.java new file mode 100644 index 000000000000..fb7c06f606e8 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionController.java @@ -0,0 +1,223 @@ +/** + * 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.repository; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.List; +import java.util.Locale; +import java.util.UUID; +import javax.mail.MessagingException; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.BadRequestException; + +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.Logger; +import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.model.RestAddressableModel; +import org.dspace.app.rest.model.ShareSubmissionLinkDTO; +import org.dspace.app.rest.model.WorkspaceItemRest; +import org.dspace.app.rest.utils.Utils; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.WorkspaceItem; +import org.dspace.content.service.WorkspaceItemService; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.core.Email; +import org.dspace.core.I18nUtil; +import org.dspace.eperson.EPerson; +import org.dspace.services.ConfigurationService; +import org.dspace.web.ContextUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.util.CollectionUtils; +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 class' purpose is to provide an API for sharing an in-progress submission. It allows the user to generate + * a share link for a workspace item and to set the owner of the workspace item to the current user. + * + * @author Milan Majchrak (dspace at dataquest.sk) + */ +@RestController +@RequestMapping("/api/" + RestAddressableModel.SUBMISSION) +public class SubmissionController { + private static Logger log = org.apache.logging.log4j.LogManager.getLogger(SubmissionController.class); + + @Autowired + WorkspaceItemService workspaceItemService; + + @Autowired + ConfigurationService configurationService; + + @Autowired + protected Utils utils; + + @Autowired + AuthorizeService authorizeService; + + @Lazy + @Autowired + protected ConverterService converter; + + @PreAuthorize("hasPermission(#wsoId, 'WORKSPACEITEM', 'WRITE')") + @RequestMapping(method = RequestMethod.GET, value = "share") + public ResponseEntity generateShareLink(@RequestParam(name = "workspaceitemid") + Integer wsoId, HttpServletRequest request) + throws SQLException, AuthorizeException { + + Context context = ContextUtil.obtainContext(request); + // Check the context is not null + this.validateContext(context); + + // Get workspace item from ID + WorkspaceItem wsi = workspaceItemService.find(context, wsoId); + // Check the wsi does exist + validateWorkspaceItem(wsi, wsoId, null); + + // Generate a share link + String shareToken = generateShareToken(); + + // Update workspace item with share link + wsi.setShareToken(shareToken); + workspaceItemService.update(context, wsi); + // Without commit the changes are not persisted into the database + context.commit(); + + // Get submitter email + EPerson currentUser = context.getCurrentUser(); + if (currentUser == null) { + String errorMessage = "The current user is not valid, it cannot be null."; + log.error(errorMessage); + throw new BadRequestException(errorMessage); + } + + // Send email to submitter with share link + String shareLink = sendShareLinkEmail(context, wsi, currentUser); + if (StringUtils.isEmpty(shareLink)) { + String errorMessage = "The share link is empty."; + log.error(errorMessage); + throw new RuntimeException(errorMessage); + } + + // Create a DTO with the share link for better processing in the FE + ShareSubmissionLinkDTO shareSubmissionLinkDTO = new ShareSubmissionLinkDTO(); + shareSubmissionLinkDTO.setShareLink(shareLink); + + // Send share link in response + return ResponseEntity.ok().body(shareSubmissionLinkDTO); + } + + @PreAuthorize("hasPermission(#wsoId, 'WORKSPACEITEM', 'WRITE')") + @RequestMapping(method = RequestMethod.GET, value = "setOwner") + public WorkspaceItemRest setOwner(@RequestParam(name = "shareToken") String shareToken, + @RequestParam(name = "workspaceitemid") Integer wsoId, + HttpServletRequest request) + throws SQLException, AuthorizeException { + + Context context = ContextUtil.obtainContext(request); + // Check the context is not null + this.validateContext(context); + + // Get workspace by share token + List wsiList = workspaceItemService.findByShareToken(context, shareToken); + // Check the wsi does exist + if (CollectionUtils.isEmpty(wsiList)) { + String errorMessage = "The workspace item with share token:" + shareToken + " does not exist."; + log.error(errorMessage); + throw new BadRequestException(errorMessage); + } + + // Get the first workspace item - the only one + WorkspaceItem wsi = wsiList.get(0); + // Check the wsi does exist + validateWorkspaceItem(wsi, null, shareToken); + + if (!authorizeService.authorizeActionBoolean(context, wsi.getItem(), Constants.READ)) { + String errorMessage = "The current user does not have rights to view the WorkflowItem"; + log.error(errorMessage); + throw new AccessDeniedException(errorMessage); + } + + // Set the owner of the workspace item to the current user + EPerson currentUser = context.getCurrentUser(); + // If the current user is null, throw an exception + if (currentUser == null) { + String errorMessage = "The current user is not valid, it cannot be null."; + log.error(errorMessage); + throw new BadRequestException(errorMessage); + } + + wsi.getItem().setSubmitter(currentUser); + workspaceItemService.update(context, wsi); + WorkspaceItemRest wsiRest = converter.toRest(wsi, utils.obtainProjection()); + + // Without commit the changes are not persisted into the database + context.commit(); + return wsiRest; + } + + private static String generateShareToken() { + // UUID generates a 36-char string with hyphens, so we can strip them to get a 32-char string + return UUID.randomUUID().toString().replace("-", "").substring(0, 32); + } + + private String sendShareLinkEmail(Context context, WorkspaceItem wsi, EPerson currentUser) { + // Get the UI URL from the configuration + String uiUrl = configurationService.getProperty("dspace.ui.url"); + // Get submitter email + String email = currentUser.getEmail(); + // Compose the url with the share token. The user will be redirected to the UI. + String shareTokenUrl = uiUrl + "/share-submission/change-submitter?share_token=" + wsi.getShareToken() + + "&workspaceitemid=" + wsi.getID(); + try { + Locale locale = context.getCurrentLocale(); + Email bean = Email.getEmail(I18nUtil.getEmailFilename(locale, "share_submission")); + bean.addArgument(shareTokenUrl); + bean.addRecipient(email); + bean.send(); + } catch (MessagingException | IOException e) { + String errorMessage = "Unable send the email because: " + e.getMessage(); + log.error(errorMessage); + throw new RuntimeException(errorMessage); + } + return shareTokenUrl; + } + + /** + * Check if the context is valid - not null. If not, throw an exception. + */ + private void validateContext(Context context) { + if (context == null) { + String errorMessage = "The current context is not valid, it cannot be null."; + log.error(errorMessage); + throw new RuntimeException(errorMessage); + } + } + + /** + * Check if the workspace item is valid - not null. If not, throw an exception. The workspace item can be found by + * ID or the share token. + */ + private void validateWorkspaceItem(WorkspaceItem wsi, Integer wsoId, String shareToken) { + if (wsi == null) { + String identifier = wsoId != null ? wsoId.toString() : shareToken; + String identifierName = wsoId != null ? "ID" : "share token"; + String errorMessage = "The workspace item with " + identifierName + ":" + identifier + " does not exist."; + log.error(errorMessage); + throw new BadRequestException(errorMessage); + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java index d3fb87b0e501..e6ff59f2a729 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java @@ -22,6 +22,7 @@ import java.util.Objects; import java.util.UUID; import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.BadRequestException; import com.fasterxml.jackson.databind.JsonNode; import org.apache.commons.io.FileUtils; @@ -84,6 +85,7 @@ import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.springframework.web.multipart.MultipartFile; @@ -100,6 +102,8 @@ public class WorkspaceItemRestRepository extends DSpaceRestRepository findByShareToken(@Parameter(value = SHARE_TOKEN, required = true) String shareToken, + Pageable pageable) { + try { + Context context = obtainContext(); + List witems = workspaceItemService.findByShareToken(context, shareToken); + if (CollectionUtils.isEmpty(witems)) { + String errorMessage = "The workspace item with share token:" + shareToken + " does not exist."; + log.error(errorMessage); + throw new BadRequestException(errorMessage); + } + if (!authorizeService.authorizeActionBoolean(context, witems.get(0).getItem(), Constants.READ)) { + String errorMessage = "The current user does not have rights to view the WorkflowItem"; + log.error(errorMessage); + throw new AccessDeniedException(errorMessage); + } + // It must return a Page because the FE cannot parse single result from the search endpoint because + // the FE service always expects a Page object. + return converter.toRestPage(witems, pageable, utils.obtainProjection()); + } catch (SQLException e) { + String errorMessage = "Cannot retrieve a workspace item with the share token: " + shareToken + + " because: " + e.getMessage(); + log.error(errorMessage, e); + throw new RuntimeException(errorMessage, e); + } + } + /** * Detach the clarin license from the bitstreams and if the clarin license is not null attach the * new clarin license to the bitstream. diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WorkspaceItemRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WorkspaceItemRestPermissionEvaluatorPlugin.java index c0efbd60f204..4f92b0f650a6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WorkspaceItemRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WorkspaceItemRestPermissionEvaluatorPlugin.java @@ -7,8 +7,12 @@ */ package org.dspace.app.rest.security; +import static org.dspace.app.rest.repository.WorkspaceItemRestRepository.SHARE_TOKEN; + import java.io.Serializable; import java.sql.SQLException; +import java.util.Objects; +import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.model.WorkspaceItemRest; @@ -91,6 +95,16 @@ public boolean hasDSpacePermission(Authentication authentication, Serializable t } } + // Check the request has shareToken the same as the workspace item + if (witem.getShareToken() != null) { + HttpServletRequest req = request.getHttpServletRequest(); + if (Objects.nonNull(req)) { + if (witem.getShareToken().equals(req.getParameter(SHARE_TOKEN))) { + return true; + } + } + } + if (witem.getItem() != null) { if (supervisionOrderService.isSupervisor(context, ePerson, witem.getItem())) { return authorizeService.authorizeActionBoolean(context, ePerson, witem.getItem(), 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 d8aca96b37b3..e1361a023b0c 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 @@ -74,7 +74,7 @@ * This class is modified version of the DiscoveryRestControllerIT. Because for CLARIN customization * the facets/filters was updated and the tests were failing. So the test class had must be updated. * - * @author Milan Majchrak (milan.majchrak at dataquest.sk) + * @author Milan Majchrak (dspace at dataquest.sk) */ public class ClarinDiscoveryRestControllerIT extends AbstractControllerIntegrationTest { @Autowired 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 20ec8dc38f24..fbb6e8fb7935 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 @@ -54,7 +54,7 @@ /** * For testing the ClarinVersionedHandleIdentifierProvider * - * @author Milan Majchrak (milan.majchrak at dataquest.sk) + * @author Milan Majchrak (dspace at dataquest.sk) */ public class ClarinWorkflowItemRestRepositoryIT extends AbstractControllerIntegrationTest { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinWorkspaceItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinWorkspaceItemRestRepositoryIT.java index 1b55cc44288a..8decdcb4d143 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinWorkspaceItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinWorkspaceItemRestRepositoryIT.java @@ -967,6 +967,30 @@ public void createItemWhenSpecificHandleHasNoSubprefix() throws Exception { context.restoreAuthSystemState(); } + @Test + public void testWsiWithShareToken() throws Exception { + String shareToken = "1234567890"; + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection").build(); + WorkspaceItem wItem = WorkspaceItemBuilder.createWorkspaceItem(context, col) + .withTitle("Item with custom handle") + .withIssueDate("2017-10-17") + .withShareToken(shareToken) + .build(); + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/submission/workspaceitems/search/shareToken") + .param("shareToken", shareToken) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.workspaceitems[0].id", is(wItem.getID()))); + } + /** * Create Clarin License Label object for testing purposes. */ diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionControllerIT.java new file mode 100644 index 000000000000..957f275220e1 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionControllerIT.java @@ -0,0 +1,116 @@ +/** + * 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 com.jayway.jsonpath.JsonPath.read; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.concurrent.atomic.AtomicReference; +import javax.ws.rs.core.MediaType; + +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.WorkspaceItemBuilder; +import org.dspace.content.Collection; +import org.dspace.content.WorkspaceItem; +import org.dspace.content.service.WorkspaceItemService; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.service.EPersonService; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Integration test for the SubmissionController + * + * @author Milan Majchrak (dspace at dataquest.sk) + */ +public class SubmissionControllerIT extends AbstractControllerIntegrationTest { + + private static final String SUBMITTER_EMAIL = "submitter@example.com"; + @Autowired + private WorkspaceItemService workspaceItemService; + @Autowired + private EPersonService ePersonService; + + WorkspaceItem wsi; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + 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_EMAIL) + .withPassword("dspace") + .build(); + + // Submitter group - allow deposit a new item without workflow + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection") + .withSubmitterGroup(submitter) + .build(); + + wsi = WorkspaceItemBuilder.createWorkspaceItem(context, col) + .withTitle("Item with custom handle") + .withIssueDate("2017-10-17") + .withSubmitter(submitter) + .build(); + context.restoreAuthSystemState(); + } + + @Test + public void generateShareTokenAndSetOwnerTest() throws Exception { + AtomicReference shareLink = new AtomicReference<>(); + EPerson currentUser = context.getCurrentUser(); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/submission/share") + .param("workspaceitemid", wsi.getID().toString()) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.shareLink", is(notNullValue()))) + .andDo(result -> shareLink.set(read(result.getResponse().getContentAsString(), "$.shareLink"))); + + // Check that the share token was set on the WorkspaceItem and persisted into the database + WorkspaceItem updatedWsi = workspaceItemService.find(context, wsi.getID()); + assertThat(wsi.getID(), is(updatedWsi.getID())); + assertThat(updatedWsi.getSubmitter().getEmail(), is(SUBMITTER_EMAIL)); + assertThat(updatedWsi.getSubmitter().getEmail(), not(currentUser.getEmail())); + + EPerson adminUser = ePersonService.findByEmail(context, admin.getEmail()); + context.setCurrentUser(adminUser); + // Set workspace item owner to the current user + getClient(adminToken).perform(get("/api/submission/setOwner") + .param("shareToken", updatedWsi.getShareToken()) + .param("workspaceitemid", updatedWsi.getID().toString()) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + // Check that the owner of the WorkspaceItem was set to the current user + // Check the wsi was persisted into the database + updatedWsi = workspaceItemService.find(context, wsi.getID()); + assertThat(updatedWsi.getSubmitter().getEmail(), is(adminUser.getEmail())); + assertThat(updatedWsi.getSubmitter().getEmail(), not(SUBMITTER_EMAIL)); + } +} diff --git a/dspace/config/emails/clarin_download_link_admin b/dspace/config/emails/clarin_download_link_admin index ecb909bf5d31..3119ee134fa1 100644 --- a/dspace/config/emails/clarin_download_link_admin +++ b/dspace/config/emails/clarin_download_link_admin @@ -30,6 +30,6 @@ ${config.get('dspace.name.short')} Team _____________________________________ ${config.get('dspace.name')}, -WWW: ${config.get('dspace.url')} -Email: ${config.get('help.mail')} -Tel.: ${config.get('help.phone')} +WWW: ${config.get('dspace.ui.url')} +Email: ${config.get('lr.help.mail')} +Tel.: ${config.get('lr.help.phone')} diff --git a/dspace/config/emails/share_submission b/dspace/config/emails/share_submission new file mode 100644 index 000000000000..884d183726d1 --- /dev/null +++ b/dspace/config/emails/share_submission @@ -0,0 +1,21 @@ +## E-mail with a download link +## +## Parameters: {0} is expanded to submission link +## +## See org.dspace.core.Email for information on the format of this file. +## +#set($subject = 'Submission share link') +To pass your submission to another user give them the following link: + ${params[0]} + +If you have trouble please contact +${config.get('lr.help.mail')} or call us at ${config.get('lr.help.phone')} + + +${config.get('dspace.name.short')} Team + +_____________________________________ +${config.get('dspace.name')}, +WWW: ${config.get('dspace.ui.url')} +Email: ${config.get('lr.help.mail')} +Tel.: ${config.get('lr.help.phone')}