Merge remote-tracking branch 'dspace/master' into w2p-68732_list-version-history

Conflicts:
	dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemRestRepository.java
	dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java
This commit is contained in:
Raf Ponsaerts
2020-03-20 10:59:55 +01:00
69 changed files with 5099 additions and 340 deletions

View File

@@ -44,15 +44,16 @@ For more information on CheckStyle configurations below, see: http://checkstyle.
with @SuppressWarnings. See also SuppressWarningsHolder below -->
<module name="SuppressWarningsFilter" />
<!-- Maximum line length is 120 characters -->
<module name="LineLength">
<property name="fileExtensions" value="java"/>
<property name="max" value="120"/>
<!-- Only exceptions for packages, imports, URLs, and JavaDoc {@link} tags -->
<property name="ignorePattern" value="^package.*|^import.*|http://|https://|@link"/>
</module>
<!-- Check individual Java source files for specific rules -->
<module name="TreeWalker">
<!-- Maximum line length is 120 characters -->
<module name="LineLength">
<property name="max" value="120"/>
<!-- Only exceptions for packages, imports, URLs, and JavaDoc {@link} tags -->
<property name="ignorePattern" value="^package.*|^import.*|http://|https://|@link"/>
</module>
<!-- Highlight any TODO or FIXME comments in info messages -->
<module name="TodoComment">
<property name="severity" value="info"/>
@@ -94,11 +95,8 @@ For more information on CheckStyle configurations below, see: http://checkstyle.
<!-- <property name="scope" value="public"/> -->
<!-- TODO: Above rule has been disabled because of large amount of missing public method Javadocs -->
<property name="scope" value="nothing"/>
<!-- Allow RuntimeExceptions to be undeclared -->
<property name="allowUndeclaredRTE" value="true"/>
<!-- Allow params, throws and return tags to be optional -->
<property name="allowMissingParamTags" value="true"/>
<property name="allowMissingThrowsTags" value="true"/>
<property name="allowMissingReturnTag" value="true"/>
</module>

View File

@@ -20,7 +20,6 @@ import com.sun.syndication.feed.module.opensearch.OpenSearchModule;
import com.sun.syndication.feed.module.opensearch.entity.OSQuery;
import com.sun.syndication.feed.module.opensearch.impl.OpenSearchModuleImpl;
import com.sun.syndication.io.FeedException;
import org.apache.logging.log4j.Logger;
import org.dspace.app.util.service.OpenSearchService;
import org.dspace.content.DSpaceObject;

View File

@@ -8,10 +8,8 @@
package org.dspace.authorize.dao.impl;
import java.sql.SQLException;
import java.util.List;
import java.util.UUID;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;

View File

@@ -11,7 +11,6 @@ import java.io.InputStream;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;

View File

@@ -13,7 +13,6 @@ import java.sql.SQLException;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import javax.annotation.Nullable;
import org.apache.commons.collections4.CollectionUtils;

View File

@@ -12,7 +12,6 @@ import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.persistence.Cacheable;
import javax.persistence.CascadeType;
import javax.persistence.Column;

View File

@@ -11,7 +11,6 @@ import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.persistence.Cacheable;
import javax.persistence.CascadeType;
import javax.persistence.Column;
@@ -271,4 +270,4 @@ public class Community extends DSpaceObject implements DSpaceObjectLegacySupport
return communityService;
}
}
}

View File

@@ -11,7 +11,6 @@ import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;

View File

@@ -13,7 +13,6 @@ import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;

View File

@@ -10,7 +10,6 @@ package org.dspace.content;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;

View File

@@ -8,7 +8,6 @@
package org.dspace.content.dao.impl;
import java.sql.SQLException;
import java.util.List;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
@@ -21,7 +20,7 @@ import org.dspace.scripts.Process;
import org.dspace.scripts.Process_;
/**
*
*
* Implementation class for {@link ProcessDAO}
*/
public class ProcessDAOImpl extends AbstractHibernateDAO<Process> implements ProcessDAO {

View File

@@ -13,7 +13,6 @@ import java.sql.SQLException;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import javax.annotation.Nullable;
import org.dspace.authorize.AuthorizeException;
@@ -46,7 +45,7 @@ public interface BitstreamService extends DSpaceObjectService<Bitstream>, DSpace
* checksum algorithm as same as the given bitstream.
* This allows multiple bitstreams to share the same internal identifier of assets .
* An example of such a use case scenario is versioning.
*
*
* @param context
* DSpace context object
* @param bitstream

View File

@@ -21,7 +21,6 @@ import net.handle.hdllib.HandleStorage;
import net.handle.hdllib.HandleValue;
import net.handle.hdllib.ScanCallback;
import net.handle.hdllib.Util;
import org.apache.logging.log4j.Logger;
import org.dspace.core.Context;
import org.dspace.handle.factory.HandleServiceFactory;

View File

@@ -15,7 +15,6 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.annotation.Nullable;
import org.apache.commons.collections4.MapUtils;

View File

@@ -18,7 +18,6 @@ import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.UUID;
import javax.mail.MessagingException;
import javax.servlet.http.HttpServletRequest;
@@ -309,7 +308,7 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService {
@Override
public WorkflowActionConfig doState(Context c, EPerson user, HttpServletRequest request, int workflowItemId,
Workflow workflow, WorkflowActionConfig currentActionConfig)
throws SQLException, AuthorizeException, IOException, MessagingException, WorkflowException {
throws SQLException, AuthorizeException, IOException, WorkflowException {
try {
XmlWorkflowItem wi = xmlWorkflowItemService.find(c, workflowItemId);
Step currentStep = currentActionConfig.getStep();

View File

@@ -50,6 +50,20 @@ public abstract class Action {
*/
public abstract List<String> getOptions();
/**
* Returns true if one of the options is a parameter of the request
* @param request Action request
* @return true if one of the options is a parameter of the request; false if none was found
*/
protected boolean isOptionInParam(HttpServletRequest request) {
for (String option: this.getOptions()) {
if (request.getParameter(option) != null) {
return true;
}
}
return false;
}
public WorkflowActionConfig getParent() {
return parent;
}

View File

@@ -13,6 +13,7 @@ import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.dspace.app.util.Util;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.DCDate;
import org.dspace.content.MetadataSchemaEnum;
@@ -46,12 +47,14 @@ public class AcceptEditRejectAction extends ProcessingAction {
@Override
public ActionResult execute(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request)
throws SQLException, AuthorizeException, IOException {
if (request.getParameter(SUBMIT_APPROVE) != null) {
return processAccept(c, wfi);
} else {
if (request.getParameter(SUBMIT_REJECT) != null) {
return processRejectPage(c, wfi, request);
if (super.isOptionInParam(request)) {
switch (Util.getSubmitButton(request, SUBMIT_CANCEL)) {
case SUBMIT_APPROVE:
return processAccept(c, wfi);
case SUBMIT_REJECT:
return processRejectPage(c, wfi, request);
default:
return new ActionResult(ActionResult.TYPE.TYPE_CANCEL);
}
}
return new ActionResult(ActionResult.TYPE.TYPE_CANCEL);

View File

@@ -12,6 +12,7 @@ import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.dspace.app.util.Util;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.DCDate;
import org.dspace.content.MetadataSchemaEnum;
@@ -42,20 +43,23 @@ public class FinalEditAction extends ProcessingAction {
@Override
public ActionResult execute(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request)
throws SQLException, AuthorizeException {
return processMainPage(c, wfi, step, request);
return processMainPage(c, wfi, request);
}
public ActionResult processMainPage(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request)
public ActionResult processMainPage(Context c, XmlWorkflowItem wfi, HttpServletRequest request)
throws SQLException, AuthorizeException {
if (request.getParameter(SUBMIT_APPROVE) != null) {
//Delete the tasks
addApprovedProvenance(c, wfi);
return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE);
} else {
//We pressed the leave button so return to our submissions page
return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE);
if (super.isOptionInParam(request)) {
switch (Util.getSubmitButton(request, SUBMIT_CANCEL)) {
case SUBMIT_APPROVE:
//Delete the tasks
addApprovedProvenance(c, wfi);
return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE);
default:
//We pressed the leave button so return to our submissions page
return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE);
}
}
return new ActionResult(ActionResult.TYPE.TYPE_CANCEL);
}
@Override

View File

@@ -34,7 +34,8 @@ public abstract class ProcessingAction extends Action {
@Autowired(required = true)
protected ItemService itemService;
protected static final String SUBMIT_EDIT_METADATA = "submit_edit_metadata";
public static final String SUBMIT_EDIT_METADATA = "submit_edit_metadata";
public static final String SUBMIT_CANCEL = "submit_cancel";
@Override
public boolean isAuthorized(Context context, HttpServletRequest request, XmlWorkflowItem wfi) throws SQLException {

View File

@@ -11,9 +11,9 @@ import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.dspace.app.util.Util;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.DCDate;
import org.dspace.content.MetadataSchemaEnum;
@@ -47,14 +47,16 @@ public class ReviewAction extends ProcessingAction {
@Override
public ActionResult execute(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request)
throws SQLException, AuthorizeException, IOException {
if (request.getParameter(SUBMIT_APPROVE) != null) {
return processAccept(c, wfi, step, request);
} else {
if (request.getParameter(SUBMIT_REJECT) != null) {
return processRejectPage(c, wfi, step, request);
if (super.isOptionInParam(request)) {
switch (Util.getSubmitButton(request, SUBMIT_CANCEL)) {
case SUBMIT_APPROVE:
return processAccept(c, wfi);
case SUBMIT_REJECT:
return processRejectPage(c, wfi, step, request);
default:
return new ActionResult(ActionResult.TYPE.TYPE_CANCEL);
}
}
return new ActionResult(ActionResult.TYPE.TYPE_CANCEL);
}
@@ -66,11 +68,9 @@ public class ReviewAction extends ProcessingAction {
return options;
}
public ActionResult processAccept(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request)
throws SQLException, AuthorizeException {
public ActionResult processAccept(Context c, XmlWorkflowItem wfi) throws SQLException, AuthorizeException {
//Delete the tasks
addApprovedProvenance(c, wfi);
return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE);
}
@@ -80,14 +80,14 @@ public class ReviewAction extends ProcessingAction {
// Get user's name + email address
String usersName = XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService()
.getEPersonName(c.getCurrentUser());
.getEPersonName(c.getCurrentUser());
String provDescription = getProvenanceStartId() + " Approved for entry into archive by "
+ usersName + " on " + now + " (GMT) ";
// Add to item as a DC field
itemService.addMetadata(c, wfi.getItem(), MetadataSchemaEnum.DC.getName(), "description", "provenance", "en",
provDescription);
provDescription);
itemService.update(c, wfi.getItem());
}
@@ -102,8 +102,8 @@ public class ReviewAction extends ProcessingAction {
//We have pressed reject, so remove the task the user has & put it back to a workspace item
XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService()
.sendWorkflowItemBackSubmission(c, wfi, c.getCurrentUser(),
this.getProvenanceStartId(), reason);
.sendWorkflowItemBackSubmission(c, wfi, c.getCurrentUser(),
this.getProvenanceStartId(), reason);
return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE);

View File

@@ -9,7 +9,6 @@ package org.dspace.xmlworkflow.storedcomponents.dao.impl;
import java.sql.SQLException;
import java.util.List;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Join;

View File

@@ -13,7 +13,6 @@ import static org.junit.Assert.fail;
import java.sql.SQLException;
import org.apache.logging.log4j.Logger;
import org.dspace.AbstractUnitTest;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.Collection;
@@ -24,7 +23,6 @@ import org.dspace.content.service.CommunityService;
import org.dspace.utils.DSpace;
import org.dspace.xmlworkflow.factory.XmlWorkflowFactory;
import org.dspace.xmlworkflow.state.Workflow;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

View File

@@ -22,7 +22,6 @@ import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
import javax.xml.stream.XMLStreamException;
import com.lyncode.xoai.dataprovider.exceptions.ConfigurationException;

View File

@@ -9,7 +9,6 @@ package org.dspace.xoai.services.impl;
import java.sql.SQLException;
import java.util.Date;
import javax.persistence.NoResultException;
import org.apache.logging.log4j.LogManager;

View File

@@ -10,7 +10,6 @@ package org.dspace.app.rest;
import java.io.IOException;
import java.sql.SQLException;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import org.dspace.app.rest.exception.UnprocessableEntityException;

View File

@@ -0,0 +1,311 @@
/**
* 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 java.util.regex.Pattern.compile;
import static org.apache.http.HttpStatus.SC_NO_CONTENT;
import static org.apache.http.HttpStatus.SC_UNPROCESSABLE_ENTITY;
import static org.dspace.app.rest.utils.ContextUtil.obtainContext;
import static org.dspace.app.rest.utils.RegexUtils.REGEX_UUID;
import static org.dspace.app.util.AuthorizeUtil.authorizeManageAdminGroup;
import static org.dspace.app.util.AuthorizeUtil.authorizeManageSubmittersGroup;
import static org.dspace.app.util.AuthorizeUtil.authorizeManageWorkflowsGroup;
import static org.springframework.web.bind.annotation.RequestMethod.DELETE;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.dspace.app.rest.exception.UnprocessableEntityException;
import org.dspace.app.rest.model.GroupRest;
import org.dspace.app.rest.utils.GroupUtil;
import org.dspace.app.rest.utils.Utils;
import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group;
import org.dspace.eperson.service.EPersonService;
import org.dspace.eperson.service.GroupService;
import org.dspace.xmlworkflow.storedcomponents.CollectionRole;
import org.dspace.xmlworkflow.storedcomponents.service.CollectionRoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.rest.webmvc.ResourceNotFoundException;
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.RestController;
/**
* This will be the entry point for the api/eperson/groups endpoint with additional paths to it
*/
@RestController
@RequestMapping("/api/" + GroupRest.CATEGORY + "/" + GroupRest.GROUPS)
public class GroupRestController {
@Autowired
private GroupService groupService;
@Autowired
private EPersonService ePersonService;
@Autowired
private AuthorizeService authorizeService;
@Autowired
private CollectionRoleService collectionRoleService;
@Autowired
Utils utils;
@Autowired
GroupUtil groupUtil;
/**
* Method to add one or more subgroups to a group.
* The subgroups to be added should be provided in the request body as a uri-list.
* Note that only the 'AUTHENTICATED' state will be checked in PreAuthorize, a more detailed check will be done by
* using the 'checkAuthorization' method.
*
* @param uuid the uuid of the group to add the subgroups to
*/
@PreAuthorize("hasAuthority('AUTHENTICATED')")
@RequestMapping(method = POST, path = "/{uuid}/subgroups", consumes = {"text/uri-list"})
public void addChildGroups(@PathVariable UUID uuid, HttpServletResponse response, HttpServletRequest request)
throws SQLException, AuthorizeException {
Context context = obtainContext(request);
Group parentGroup = groupService.find(context, uuid);
if (parentGroup == null) {
throw new ResourceNotFoundException("parent group is not found for uuid: " + uuid);
}
checkAuthorization(context, parentGroup);
List<String> groupLinks = utils.getStringListFromRequest(request);
List<Group> childGroups = new ArrayList<>();
for (String groupLink : groupLinks) {
Optional<Group> childGroup = findGroup(context, groupLink);
if (!childGroup.isPresent() || !canAddGroup(context, parentGroup, childGroup.get())) {
throw new UnprocessableEntityException("cannot add child group: " + groupLink);
}
childGroups.add(childGroup.get());
}
for (Group childGroup : childGroups) {
groupService.addMember(context, parentGroup, childGroup);
}
context.complete();
response.setStatus(SC_NO_CONTENT);
}
private Optional<Group> findGroup(Context context, String groupLink) throws SQLException {
Group group = null;
Pattern linkPattern = compile("^.*/(" + REGEX_UUID + ")/?$");
Matcher matcher = linkPattern.matcher(groupLink);
if (matcher.matches()) {
group = groupService.find(context, UUID.fromString(matcher.group(1)));
}
return Optional.ofNullable(group);
}
private boolean canAddGroup(Context context, Group parentGroup, Group childGroup) throws SQLException {
return !groupService.isParentOf(context, childGroup, parentGroup);
}
/**
* Method to add one or more members to a group.
* The members to be added should be provided in the request body as a uri-list.
* Note that only the 'AUTHENTICATED' state will be checked in PreAuthorize, a more detailed check will be done by
* using the 'checkAuthorization' method.
*
* @param uuid the uuid of the group to add the members to
*/
@PreAuthorize("hasAuthority('AUTHENTICATED')")
@RequestMapping(method = POST, path = "/{uuid}/epersons", consumes = {"text/uri-list"})
public void addMembers(@PathVariable UUID uuid, HttpServletResponse response, HttpServletRequest request)
throws SQLException, AuthorizeException {
Context context = obtainContext(request);
Group parentGroup = groupService.find(context, uuid);
if (parentGroup == null) {
throw new ResourceNotFoundException("parent group is not found for uuid: " + uuid);
}
checkAuthorization(context, parentGroup);
List<String> memberLinks = utils.getStringListFromRequest(request);
List<EPerson> members = new ArrayList<>();
for (String memberLink : memberLinks) {
Optional<EPerson> member = findEPerson(context, memberLink);
if (!member.isPresent()) {
throw new UnprocessableEntityException("cannot add child group: " + memberLink);
}
members.add(member.get());
}
for (EPerson member : members) {
groupService.addMember(context, parentGroup, member);
}
context.complete();
response.setStatus(SC_NO_CONTENT);
}
private Optional<EPerson> findEPerson(Context context, String groupLink) throws SQLException {
EPerson ePerson = null;
Pattern linkPattern = compile("^.*/(" + REGEX_UUID + ")/?$");
Matcher matcher = linkPattern.matcher(groupLink);
if (matcher.matches()) {
ePerson = ePersonService.find(context, UUID.fromString(matcher.group(1)));
}
return Optional.ofNullable(ePerson);
}
/**
* Method to remove a subgroup from a group.
* Note that only the 'AUTHENTICATED' state will be checked in PreAuthorize, a more detailed check will be done by
* using the 'checkAuthorization' method.
*
* @param parentUUID the uuid of the parent group
* @param childUUID the uuid of the subgroup which has to be removed
*/
@PreAuthorize("hasAuthority('AUTHENTICATED')")
@RequestMapping(method = DELETE, path = "/{parentUUID}/subgroups/{childUUID}")
public void removeChildGroup(@PathVariable UUID parentUUID, @PathVariable UUID childUUID,
HttpServletResponse response, HttpServletRequest request)
throws IOException, SQLException, AuthorizeException {
Context context = obtainContext(request);
Group parentGroup = groupService.find(context, parentUUID);
if (parentGroup == null) {
throw new ResourceNotFoundException("parent group is not found for uuid: " + parentUUID);
}
checkAuthorization(context, parentGroup);
Group childGroup = groupService.find(context, childUUID);
if (childGroup == null) {
response.sendError(SC_UNPROCESSABLE_ENTITY);
}
groupService.removeMember(context, parentGroup, childGroup);
context.complete();
response.setStatus(SC_NO_CONTENT);
}
/**
* Method to remove a member from a group.
* Note that only the 'AUTHENTICATED' state will be checked in PreAuthorize, a more detailed check will be done by
* using the 'checkAuthorization' method.
*
* @param parentUUID the uuid of the parent group
* @param memberUUID the uuid of the member which has to be removed
*/
@PreAuthorize("hasAuthority('AUTHENTICATED')")
@RequestMapping(method = DELETE, path = "/{parentUUID}/epersons/{memberUUID}")
public void removeMember(@PathVariable UUID parentUUID, @PathVariable UUID memberUUID,
HttpServletResponse response, HttpServletRequest request)
throws IOException, SQLException, AuthorizeException {
Context context = obtainContext(request);
Group parentGroup = groupService.find(context, parentUUID);
if (parentGroup == null) {
throw new ResourceNotFoundException("parent group is not found for uuid: " + parentUUID);
}
checkAuthorization(context, parentGroup);
EPerson childGroup = ePersonService.find(context, memberUUID);
if (childGroup == null) {
response.sendError(SC_UNPROCESSABLE_ENTITY);
}
groupService.removeMember(context, parentGroup, childGroup);
context.complete();
response.setStatus(SC_NO_CONTENT);
}
/**
* This method checks whether the current user has sufficient rights to modify the group.
* Depending on the kind of group and due to delegated administration, separate checks need to be done to verify
* whether the user is allowed to modify the group.
*
* @param context the context of which the user will be checked
* @param group the group to be checked
* @throws SQLException
* @throws AuthorizeException
*/
private void checkAuthorization(Context context, Group group) throws SQLException, AuthorizeException {
if (authorizeService.isAdmin(context)) {
return;
}
Collection collection = groupUtil.getCollection(context, group);
if (collection != null) {
if (group.equals(collection.getSubmitters())) {
authorizeManageSubmittersGroup(context, collection);
return;
}
List<CollectionRole> collectionRoles = collectionRoleService.findByCollection(context, collection);
for (CollectionRole role : collectionRoles) {
if (group.equals(role.getGroup())) {
authorizeManageWorkflowsGroup(context, collection);
return;
}
}
if (group.equals(collection.getAdministrators())) {
authorizeManageAdminGroup(context, collection);
return;
}
}
Community community = groupUtil.getCommunity(context, group);
if (community != null) {
authorizeManageAdminGroup(context, community);
return;
}
throw new AuthorizeException("not authorized to manage this group");
}
}

View File

@@ -12,7 +12,6 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

View File

@@ -36,6 +36,7 @@ import org.dspace.app.rest.converter.ConverterService;
import org.dspace.app.rest.converter.JsonPatchConverter;
import org.dspace.app.rest.exception.DSpaceBadRequestException;
import org.dspace.app.rest.exception.PaginationException;
import org.dspace.app.rest.exception.RESTAuthorizationException;
import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException;
import org.dspace.app.rest.exception.RepositoryNotFoundException;
import org.dspace.app.rest.exception.RepositorySearchMethodNotFoundException;
@@ -580,13 +581,14 @@ public class RestResourceController implements InitializingBean {
MultipartFile uploadfile) {
checkModelPluralForm(apiCategory, model);
DSpaceRestRepository<RestAddressableModel, ID> repository = utils.getResourceRepository(apiCategory, model);
RestAddressableModel modelObject = null;
try {
modelObject = repository.upload(request, apiCategory, model, id, uploadfile);
} catch (Exception e) {
log.error(e.getMessage(), e);
return ControllerUtils.toEmptyResponse(HttpStatus.INTERNAL_SERVER_ERROR);
} catch (SQLException | IOException e) {
throw new RuntimeException("Error " + e.getMessage() +
" uploading file to " + model + " with ID= " + id, e);
} catch ( AuthorizeException ae) {
throw new RESTAuthorizationException(ae);
}
DSpaceResource result = converter.toResource(modelObject);
return ControllerUtils.toResponseEntity(HttpStatus.CREATED, new HttpHeaders(), result);
@@ -910,7 +912,6 @@ public class RestResourceController implements InitializingBean {
Pageable page,
PagedResourcesAssembler assembler,
HttpServletResponse response) {
DSpaceRestRepository<T, ?> repository = utils.getResourceRepository(apiCategory, model);
Link link = linkTo(methodOn(this.getClass(), apiCategory, model).findAll(apiCategory, model,
page, assembler, response))

View File

@@ -19,8 +19,12 @@ import org.dspace.app.rest.RestResourceController;
@JsonIgnoreProperties(ignoreUnknown = true)
@LinksRest(links = {
@LinkRest(
name = GroupRest.GROUPS,
name = GroupRest.SUBGROUPS,
method = "getGroups"
),
@LinkRest(
name = GroupRest.EPERSONS,
method = "getMembers"
)
})
public class GroupRest extends DSpaceObjectRest {
@@ -28,6 +32,8 @@ public class GroupRest extends DSpaceObjectRest {
public static final String CATEGORY = RestAddressableModel.EPERSON;
public static final String GROUPS = "groups";
public static final String SUBGROUPS = "subgroups";
public static final String EPERSONS = "epersons";
private String name;

View File

@@ -8,7 +8,6 @@
package org.dspace.app.rest.model.step;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -93,4 +92,4 @@ public class UploadBitstreamRest extends UploadStatusResponse {
public void setFormat(BitstreamFormatRest format) {
this.format = format;
}
}
}

View File

@@ -23,6 +23,7 @@ import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException;
import org.dspace.app.rest.exception.UnprocessableEntityException;
import org.dspace.app.rest.model.ClaimedTaskRest;
import org.dspace.app.rest.model.PoolTaskRest;
import org.dspace.app.util.Util;
import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.content.service.ItemService;
@@ -137,6 +138,11 @@ public class ClaimedTaskRestRepository extends DSpaceRestRepository<ClaimedTaskR
Step step = workflow.getStep(task.getStepID());
WorkflowActionConfig currentActionConfig = step.getActionConfig(task.getActionID());
String submitButton = Util.getSubmitButton(request, null);
if (!currentActionConfig.getProcessingAction().getOptions().contains(submitButton)) {
throw new UnprocessableEntityException(submitButton + " is not a valid option on this action (" +
currentActionConfig.getProcessingAction().getClass() + ").");
}
workflowService
.doState(context, context.getCurrentUser(), request, task.getWorkflowItem().getID(), workflow,
currentActionConfig);

View File

@@ -14,7 +14,6 @@ import java.sql.SQLException;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import com.fasterxml.jackson.databind.JsonNode;
@@ -380,10 +379,10 @@ public abstract class DSpaceRestRepository<T extends RestAddressableModel, ID ex
* @param file
* the uploaded file
* @return the new state of the REST object
* @throws Exception
*/
public T upload(HttpServletRequest request, String apiCategory, String model,
ID id, MultipartFile file) throws Exception {
ID id, MultipartFile file)
throws SQLException, FileNotFoundException, IOException, AuthorizeException {
throw new RuntimeException("No implementation found; Method not allowed!");
}

View File

@@ -28,7 +28,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
/**
* Link repository for "groups" subresource of an individual eperson.
* Link repository for the direct "groups" subresource of an individual eperson.
*/
@Component(EPersonRest.CATEGORY + "." + EPersonRest.NAME + "." + EPersonRest.GROUPS)
public class EPersonGroupLinkRepository extends AbstractDSpaceRestRepository
@@ -51,7 +51,7 @@ public class EPersonGroupLinkRepository extends AbstractDSpaceRestRepository
if (eperson == null) {
throw new ResourceNotFoundException("No such eperson: " + epersonId);
}
Page<Group> groups = utils.getPage(groupService.allMemberGroups(context, eperson), optionalPageable);
Page<Group> groups = utils.getPage(eperson.getGroups(), optionalPageable);
return converter.toRestPage(groups, projection);
} catch (SQLException e) {
throw new RuntimeException(e);

View File

@@ -114,37 +114,13 @@ public class EPersonRestRepository extends DSpaceObjectRestRepository<EPerson, E
}
}
/**
* Find the epersons matching the query q parameter. The search is delegated to the
* {@link EPersonService#search(Context, String, int, int)} method
*
* @param q
* is the *required* query string
* @param pageable
* contains the pagination information
* @return a Page of EPersonRest instances matching the user query
*/
@SearchRestMethod(name = "byName")
public Page<EPersonRest> findByName(@Parameter(value = "q", required = true) String q,
Pageable pageable) {
try {
Context context = obtainContext();
long total = es.searchResultCount(context, q);
List<EPerson> epersons = es.search(context, q, Math.toIntExact(pageable.getOffset()),
Math.toIntExact(pageable.getOffset() + pageable.getPageSize()));
return converter.toRestPage(epersons, pageable, total, utils.obtainProjection());
} catch (SQLException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
/**
* Find the eperson with the provided email address if any. The search is delegated to the
* {@link EPersonService#findByEmail(Context, String)} method
*
* @param email
* is the *required* email address
* @return the EPersonRest instance, if any, matching the user query
* @return a Page of EPersonRest instances matching the user query
*/
@SearchRestMethod(name = "byEmail")
public EPersonRest findByEmail(@Parameter(value = "email", required = true) String email) {
@@ -161,6 +137,32 @@ public class EPersonRestRepository extends DSpaceObjectRestRepository<EPerson, E
return converter.toRest(eperson, utils.obtainProjection());
}
/**
* Find the epersons matching the query parameter. The search is delegated to the
* {@link EPersonService#search(Context, String, int, int)} method
*
* @param query
* is the *required* query string
* @param pageable
* contains the pagination information
* @return a Page of EPersonRest instances matching the user query
*/
@PreAuthorize("hasAuthority('ADMIN')")
@SearchRestMethod(name = "byMetadata")
public Page<EPersonRest> findByMetadata(@Parameter(value = "query", required = true) String query,
Pageable pageable) {
try {
Context context = obtainContext();
long total = es.searchResultCount(context, query);
List<EPerson> epersons = es.search(context, query, Math.toIntExact(pageable.getOffset()),
Math.toIntExact(pageable.getOffset() + pageable.getPageSize()));
return converter.toRestPage(epersons, pageable, total, utils.obtainProjection());
} catch (SQLException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
@Override
@PreAuthorize("hasPermission(#uuid, 'EPERSON', #patch)")
protected void patch(Context context, HttpServletRequest request, String apiCategory, String model, UUID uuid,

View File

@@ -0,0 +1,55 @@
/**
* 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.sql.SQLException;
import java.util.UUID;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
import org.dspace.app.rest.model.GroupRest;
import org.dspace.app.rest.projection.Projection;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group;
import org.dspace.eperson.service.GroupService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.rest.webmvc.ResourceNotFoundException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
/**
* Link repository for "epersons" subresource of an individual group.
*/
@Component(GroupRest.CATEGORY + "." + GroupRest.NAME + "." + GroupRest.EPERSONS)
public class GroupEPersonLinkRepository extends AbstractDSpaceRestRepository
implements LinkRestRepository {
@Autowired
GroupService groupService;
@PreAuthorize("hasPermission(#groupId, 'GROUP', 'READ')")
public Page<GroupRest> getMembers(@Nullable HttpServletRequest request,
UUID groupId,
@Nullable Pageable optionalPageable,
Projection projection) {
try {
Context context = obtainContext();
Group group = groupService.find(context, groupId);
if (group == null) {
throw new ResourceNotFoundException("No such group: " + groupId);
}
Page<EPerson> ePersons = utils.getPage(group.getMembers(), optionalPageable);
return converter.toRestPage(ePersons, projection);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -27,7 +27,7 @@ import org.springframework.stereotype.Component;
/**
* Link repository for "groups" subresource of an individual group.
*/
@Component(GroupRest.CATEGORY + "." + GroupRest.NAME + "." + GroupRest.GROUPS)
@Component(GroupRest.CATEGORY + "." + GroupRest.NAME + "." + GroupRest.SUBGROUPS)
public class GroupGroupLinkRepository extends AbstractDSpaceRestRepository
implements LinkRestRepository {

View File

@@ -7,6 +7,8 @@
*/
package org.dspace.app.rest.repository;
import static org.apache.commons.lang3.StringUtils.isBlank;
import java.io.IOException;
import java.sql.SQLException;
import java.util.List;
@@ -14,6 +16,8 @@ import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.dspace.app.rest.Parameter;
import org.dspace.app.rest.SearchRestMethod;
import org.dspace.app.rest.converter.MetadataConverter;
import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException;
import org.dspace.app.rest.exception.UnprocessableEntityException;
@@ -53,9 +57,10 @@ public class GroupRestRepository extends DSpaceObjectRestRepository<Group, Group
@PreAuthorize("hasAuthority('ADMIN')")
protected GroupRest createAndReturn(Context context)
throws AuthorizeException, RepositoryMethodNotImplementedException {
HttpServletRequest req = getRequestService().getCurrentRequest().getHttpServletRequest();
ObjectMapper mapper = new ObjectMapper();
GroupRest groupRest = null;
GroupRest groupRest;
try {
groupRest = mapper.readValue(req.getInputStream(), GroupRest.class);
@@ -63,7 +68,11 @@ public class GroupRestRepository extends DSpaceObjectRestRepository<Group, Group
throw new UnprocessableEntityException("error parsing the body ..." + excIO.getMessage());
}
Group group = null;
if (isBlank(groupRest.getName())) {
throw new UnprocessableEntityException("cannot create group, no group name is provided");
}
Group group;
try {
group = gs.create(context);
gs.setName(group, groupRest.getName());
@@ -97,7 +106,7 @@ public class GroupRestRepository extends DSpaceObjectRestRepository<Group, Group
try {
long total = gs.countTotal(context);
List<Group> groups = gs.findAll(context, null, pageable.getPageSize(),
Math.toIntExact(pageable.getOffset()));
Math.toIntExact(pageable.getOffset()));
return converter.toRestPage(groups, pageable, total, utils.obtainProjection());
} catch (SQLException e) {
throw new RuntimeException(e.getMessage(), e);
@@ -111,6 +120,31 @@ public class GroupRestRepository extends DSpaceObjectRestRepository<Group, Group
patchDSpaceObject(apiCategory, model, id, patch);
}
/**
* Find the groups matching the query parameter. The search is delegated to the
* {@link GroupService#search(Context, String, int, int)} method
*
* @param query is the *required* query string
* @param pageable contains the pagination information
* @return a Page of GroupRest instances matching the user query
*/
@PreAuthorize("hasAuthority('ADMIN')")
@SearchRestMethod(name = "byMetadata")
public Page<GroupRest> findByMetadata(@Parameter(value = "query", required = true) String query,
Pageable pageable) {
try {
Context context = obtainContext();
long total = gs.searchResultCount(context, query);
List<Group> groups = gs.search(context, query, Math.toIntExact(pageable.getOffset()),
Math.toIntExact(pageable.getOffset() + pageable.getPageSize()));
return converter.toRestPage(groups, pageable, total, utils.obtainProjection());
} catch (SQLException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
@Override
public Class<GroupRest> getDomainClass() {
return GroupRest.class;

View File

@@ -11,11 +11,9 @@ import java.io.IOException;
import java.sql.SQLException;
import java.util.List;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.dspace.app.rest.Parameter;
import org.dspace.app.rest.SearchRestMethod;
import org.dspace.app.rest.exception.MissingParameterException;

View File

@@ -7,6 +7,8 @@
*/
package org.dspace.app.rest.repository;
import static org.dspace.xmlworkflow.state.actions.processingaction.ProcessingAction.SUBMIT_EDIT_METADATA;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
@@ -44,7 +46,14 @@ import org.dspace.eperson.EPersonServiceImpl;
import org.dspace.services.ConfigurationService;
import org.dspace.workflow.WorkflowException;
import org.dspace.workflow.WorkflowService;
import org.dspace.xmlworkflow.WorkflowConfigurationException;
import org.dspace.xmlworkflow.factory.XmlWorkflowFactory;
import org.dspace.xmlworkflow.state.Step;
import org.dspace.xmlworkflow.state.Workflow;
import org.dspace.xmlworkflow.state.actions.WorkflowActionConfig;
import org.dspace.xmlworkflow.storedcomponents.ClaimedTask;
import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem;
import org.dspace.xmlworkflow.storedcomponents.service.ClaimedTaskService;
import org.dspace.xmlworkflow.storedcomponents.service.XmlWorkflowItemService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
@@ -95,6 +104,15 @@ public class WorkflowItemRestRepository extends DSpaceRestRepository<WorkflowIte
@Autowired
AuthorizeService authorizeService;
@Autowired
ClaimedTaskService claimedTaskService;
@Autowired
protected XmlWorkflowItemService xmlWorkflowItemService;
@Autowired
protected XmlWorkflowFactory workflowFactory;
private final SubmissionConfigReader submissionConfigReader;
public WorkflowItemRestRepository() throws SubmissionConfigReaderException {
@@ -124,7 +142,8 @@ public class WorkflowItemRestRepository extends DSpaceRestRepository<WorkflowIte
List<XmlWorkflowItem> witems = wis.findAll(context, pageable.getPageNumber(), pageable.getPageSize());
return converter.toRestPage(witems, pageable, total, utils.obtainProjection());
} catch (SQLException e) {
throw new RuntimeException(e.getMessage(), e);
throw new RuntimeException("SQLException in " + this.getClass() + "#findAll trying to retrieve all " +
"workflowitems from db.", e);
}
}
@@ -139,7 +158,8 @@ public class WorkflowItemRestRepository extends DSpaceRestRepository<WorkflowIte
pageable.getPageSize());
return converter.toRestPage(witems, pageable, total, utils.obtainProjection());
} catch (SQLException e) {
throw new RuntimeException(e.getMessage(), e);
throw new RuntimeException("SQLException in " + this.getClass() + "#findBySubmitter trying to retrieve " +
"eperson or their workflowitems from db.", e);
}
}
@@ -157,7 +177,8 @@ public class WorkflowItemRestRepository extends DSpaceRestRepository<WorkflowIte
throw new UnprocessableEntityException(
"Invalid workflow action: " + e.getMessage(), e);
} catch (SQLException e) {
throw new RuntimeException(e.getMessage(), e);
throw new RuntimeException("SQLException in " + this.getClass() + "#findBySubmitter trying to create " +
"a workflow and adding it to db.", e);
}
//if the item go directly in published status we have to manage a status code 204 with no content
if (source.getItem().isArchived()) {
@@ -173,11 +194,14 @@ public class WorkflowItemRestRepository extends DSpaceRestRepository<WorkflowIte
@Override
public WorkflowItemRest upload(HttpServletRequest request, String apiCategory, String model, Integer id,
MultipartFile file) throws Exception {
MultipartFile file) throws SQLException {
Context context = obtainContext();
WorkflowItemRest wsi = findOne(context, id);
XmlWorkflowItem source = wis.find(context, id);
this.checkIfEditMetadataAllowedInCurrentStep(context, source);
List<ErrorRest> errors = new ArrayList<ErrorRest>();
SubmissionConfig submissionConfig =
submissionConfigReader.getSubmissionConfigByName(wsi.getSubmissionDefinition().getName());
@@ -226,6 +250,9 @@ public class WorkflowItemRestRepository extends DSpaceRestRepository<WorkflowIte
List<Operation> operations = patch.getOperations();
WorkflowItemRest wsi = findOne(context, id);
XmlWorkflowItem source = wis.find(context, id);
this.checkIfEditMetadataAllowedInCurrentStep(context, source);
for (Operation op : operations) {
//the value in the position 0 is a null value
String[] path = op.getPath().substring(1).split("/", 3);
@@ -296,8 +323,42 @@ public class WorkflowItemRestRepository extends DSpaceRestRepository<WorkflowIte
wfs.abort(context, witem, context.getCurrentUser());
} catch (AuthorizeException e) {
throw new RESTAuthorizationException(e);
} catch (SQLException | IOException e) {
throw new RuntimeException(e.getMessage(), e);
} catch (SQLException e) {
throw new RuntimeException("SQLException in " + this.getClass() + "#delete trying to retrieve or delete a" +
" workflowitem from db.", e);
} catch (IOException e) {
throw new RuntimeException("IOException in " + this.getClass() + "#delete trying to delete a workflowitem" +
" from db (abort).", e);
}
}
/**
* Checks if @link{SUBMIT_EDIT_METADATA} is a valid option in the workflow step this task is currently at.
* Patching and uploading is only allowed if this is the case.
* @param context Context
* @param xmlWorkflowItem WorkflowItem of the task
*/
private void checkIfEditMetadataAllowedInCurrentStep(Context context, XmlWorkflowItem xmlWorkflowItem) {
try {
ClaimedTask claimedTask = claimedTaskService.findByWorkflowIdAndEPerson(context, xmlWorkflowItem,
context.getCurrentUser());
if (claimedTask == null) {
throw new UnprocessableEntityException("WorkflowItem with id " + xmlWorkflowItem.getID()
+ " has not been claimed yet.");
}
Workflow workflow = workflowFactory.getWorkflow(claimedTask.getWorkflowItem().getCollection());
Step step = workflow.getStep(claimedTask.getStepID());
WorkflowActionConfig currentActionConfig = step.getActionConfig(claimedTask.getActionID());
if (!currentActionConfig.getProcessingAction().getOptions().contains(SUBMIT_EDIT_METADATA)) {
throw new UnprocessableEntityException(SUBMIT_EDIT_METADATA + " is not a valid option on this " +
"action (" + currentActionConfig.getProcessingAction().getClass() + ").");
}
} catch (SQLException e) {
throw new RuntimeException("SQLException in " + this.getClass()
+ "#checkIfEditMetadataAllowedInCurrentStep trying to retrieve workflowitem from db by eperson.", e);
} catch (WorkflowConfigurationException e) {
throw new RuntimeException("WorkflowConfigurationException in " + this.getClass()
+ "#checkIfEditMetadataAllowedInCurrentStep trying to retrieve workflow configuration from config", e);
}
}

View File

@@ -68,6 +68,7 @@ import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.rest.webmvc.json.patch.PatchException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
@@ -125,7 +126,7 @@ public class WorkspaceItemRestRepository extends DSpaceRestRepository<WorkspaceI
submissionConfigReader = new SubmissionConfigReader();
}
//TODO @PreAuthorize("hasPermission(#id, 'WORKSPACEITEM', 'READ')")
@PreAuthorize("hasPermission(#id, 'WORKSPACEITEM', 'READ')")
@Override
public WorkspaceItemRest findOne(Context context, Integer id) {
WorkspaceItem witem = null;
@@ -140,7 +141,7 @@ public class WorkspaceItemRestRepository extends DSpaceRestRepository<WorkspaceI
return converter.toRest(witem, utils.obtainProjection());
}
//TODO @PreAuthorize("hasAuthority('ADMIN')")
@PreAuthorize("hasAuthority('ADMIN')")
@Override
public Page<WorkspaceItemRest> findAll(Context context, Pageable pageable) {
try {
@@ -153,7 +154,7 @@ public class WorkspaceItemRestRepository extends DSpaceRestRepository<WorkspaceI
}
}
//TODO @PreAuthorize("hasPermission(#submitterID, 'EPERSON', 'READ')")
@PreAuthorize("hasPermission(#submitterID, 'EPERSON', 'READ')")
@SearchRestMethod(name = "findBySubmitter")
public Page<WorkspaceItemRest> findBySubmitter(@Parameter(value = "uuid", required = true) UUID submitterID,
Pageable pageable) {
@@ -219,10 +220,10 @@ public class WorkspaceItemRestRepository extends DSpaceRestRepository<WorkspaceI
return WorkspaceItemRest.class;
}
//TODO @PreAuthorize("hasPermission(#id, 'WORKSPACEITEM', 'WRITE')")
@PreAuthorize("hasPermission(#id, 'WORKSPACEITEM', 'WRITE')")
@Override
public WorkspaceItemRest upload(HttpServletRequest request, String apiCategory, String model, Integer id,
MultipartFile file) throws Exception {
MultipartFile file) throws SQLException {
Context context = obtainContext();
WorkspaceItemRest wsi = findOne(context, id);
@@ -269,7 +270,7 @@ public class WorkspaceItemRestRepository extends DSpaceRestRepository<WorkspaceI
return wsi;
}
//TODO @PreAuthorize("hasPermission(#id, 'WORKSPACEITEM', 'WRITE')")
@PreAuthorize("hasPermission(#id, 'WORKSPACEITEM', 'WRITE')")
@Override
public void patch(Context context, HttpServletRequest request, String apiCategory, String model, Integer id,
Patch patch) throws SQLException, AuthorizeException {
@@ -332,7 +333,7 @@ public class WorkspaceItemRestRepository extends DSpaceRestRepository<WorkspaceI
}
}
//TODO @PreAuthorize("hasPermission(#id, 'WORKSPACEITEM', 'DELETE')")
@PreAuthorize("hasPermission(#id, 'WORKSPACEITEM', 'DELETE')")
@Override
protected void delete(Context context, Integer id) throws AuthorizeException {
WorkspaceItem witem = null;

View File

@@ -15,7 +15,6 @@ import org.dspace.app.rest.model.patch.Operation;
import org.dspace.app.rest.repository.patch.operation.PatchOperation;
import org.dspace.authorize.ResourcePolicy;
import org.dspace.core.Context;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

View File

@@ -14,6 +14,7 @@ import java.util.UUID;
import org.dspace.app.rest.utils.ContextUtil;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.DSpaceObjectService;
import org.dspace.core.Constants;
@@ -84,6 +85,15 @@ public class AuthorizeServicePermissionEvaluatorPlugin extends RestObjectPermiss
return true;
}
// If the item is still inprogress we can process here only the READ permission.
// Other actions need to be evaluated against the wrapper object (workspace or workflow item)
if (dSpaceObject instanceof Item) {
if (!DSpaceRestPermission.READ.equals(restPermission)
&& !((Item) dSpaceObject).isArchived() && !((Item) dSpaceObject).isWithdrawn()) {
return false;
}
}
return authorizeService.authorizeActionBoolean(context, ePerson, dSpaceObject,
restPermission.getDspaceApiActionId(), true);
}

View File

@@ -8,7 +8,6 @@
package org.dspace.app.rest.security;
import java.io.Serializable;
import java.sql.SQLException;
import org.apache.commons.lang3.StringUtils;
@@ -29,9 +28,9 @@ import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
/**
*
*
* {@link RestPermissionEvaluatorPlugin} class that evaluate ADMIN permissions over a Resource Policy
*
*
* @author Mykhaylo Boychuk - (4Science.it)
*/
@Component

View File

@@ -0,0 +1,92 @@
/**
* 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.security;
import java.io.Serializable;
import java.sql.SQLException;
import org.apache.commons.lang3.StringUtils;
import org.dspace.app.rest.model.WorkspaceItemRest;
import org.dspace.app.rest.utils.ContextUtil;
import org.dspace.content.WorkspaceItem;
import org.dspace.content.service.WorkspaceItemService;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
import org.dspace.services.RequestService;
import org.dspace.services.model.Request;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
/**
* {@link RestPermissionEvaluatorPlugin} class that evaluate READ, WRITE and DELETE permissions over a WorkspaceItem
*
* @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it)
*/
@Component
public class WorkspaceItemRestPermissionEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin {
private static final Logger log = LoggerFactory.getLogger(WorkspaceItemRestPermissionEvaluatorPlugin.class);
@Autowired
private RequestService requestService;
@Autowired
WorkspaceItemService wis;
@Override
public boolean hasDSpacePermission(Authentication authentication, Serializable targetId, String targetType,
DSpaceRestPermission permission) {
DSpaceRestPermission restPermission = DSpaceRestPermission.convert(permission);
if (!DSpaceRestPermission.READ.equals(restPermission)
&& !DSpaceRestPermission.WRITE.equals(restPermission)
&& !DSpaceRestPermission.DELETE.equals(restPermission)) {
return false;
}
if (!StringUtils.equalsIgnoreCase(targetType, WorkspaceItemRest.NAME)) {
return false;
}
Request request = requestService.getCurrentRequest();
Context context = ContextUtil.obtainContext(request.getServletRequest());
EPerson ePerson = null;
WorkspaceItem witem = null;
try {
ePerson = context.getCurrentUser();
Integer dsoId = Integer.parseInt(targetId.toString());
// anonymous user
if (ePerson == null) {
return false;
}
witem = wis.find(context, dsoId);
// If the dso is null then we give permission so we can throw another status
// code instead
if (witem == null) {
return true;
}
if (witem.getSubmitter() != null) {
if (witem.getSubmitter().getID().equals(ePerson.getID())) {
return true;
}
}
} catch (SQLException e) {
log.error(e.getMessage(), e);
}
return false;
}
}

View File

@@ -8,7 +8,6 @@
package org.dspace.app.rest.submit;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
@@ -141,7 +140,7 @@ public class SubmissionService {
* Build the rest representation of a bitstream as used in the upload section
* ({@link DataUpload}. It contains all its metadata and the list of applied
* access conditions (@link {@link UploadBitstreamAccessConditionDTO}
*
*
* @param configurationService the DSpace ConfigurationService
* @param source the bitstream to translate in its rest submission
* representation

View File

@@ -8,7 +8,6 @@
package org.dspace.app.rest.submit.factory.impl;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

View File

@@ -0,0 +1,110 @@
/**
* 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.utils;
import java.sql.SQLException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.content.service.CollectionService;
import org.dspace.content.service.CommunityService;
import org.dspace.core.Context;
import org.dspace.eperson.Group;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* A class which provides utility methods for Groups
*/
@Component
public class GroupUtil {
private GroupUtil() {
}
/**
* UUID regex used in the collection regex
*/
private static final String UUID_REGEX = "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{4}\\-[0" +
"-9a-fA-F]{12}";
/**
* Collection regex used to extract the ID
*/
private static final String COLLECTION_REGEX = "COLLECTION_(" + UUID_REGEX + ")_.*?";
/**
* The community prefix: all groups which are specific to
* a community start with this.
*/
private static final String COMMUNITY_PREFIX = "COMMUNITY_";
/**
* These are the possible community suffixes. All groups which are
* specific to a community will end with one of these.
*/
private static final String[] COMMUNITY_SUFFIXES = {"_ADMIN"};
@Autowired
protected CollectionService collectionService;
@Autowired
protected CommunityService communityService;
/**
* Get the collection a given group is related to, or null if it is not related to a collection.
*/
public Collection getCollection(Context context, Group group) throws SQLException {
String groupName = group.getName();
if (groupName == null) {
return null;
}
Matcher groupNameMatcher = Pattern.compile(COLLECTION_REGEX).matcher(groupName);
if (groupNameMatcher.find()) {
String uuid = groupNameMatcher.group();
Collection collection = collectionService.findByIdOrLegacyId(context, uuid);
if (collection != null) {
return collection;
}
}
return null;
}
/**
* Get the community a given group is related to, or null if it is not related to a community.
*/
public Community getCommunity(Context context, Group group) throws SQLException {
String groupName = group.getName();
if (groupName == null || !groupName.startsWith(COMMUNITY_PREFIX)) {
return null;
}
for (String suffix : COMMUNITY_SUFFIXES) {
if (groupName.endsWith(suffix)) {
String idString = groupName.substring(COMMUNITY_PREFIX.length());
idString = idString.substring(0, idString.length() - suffix.length());
Community community = communityService.findByIdOrLegacyId(context, idString);
if (community != null) {
return community;
} else {
return null;
}
}
}
return null;
}
}

View File

@@ -11,11 +11,17 @@ public class RegexUtils {
private RegexUtils(){}
/**
* Regular expression in the request mapping to accept UUID as identifier
*/
public static final String REGEX_UUID =
"[0-9a-fxA-FX]{8}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{12}";
/**
* Regular expression in the request mapping to accept UUID as identifier
*/
public static final String REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID =
"/{uuid:[0-9a-fxA-FX]{8}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{12}}";
"/{uuid:" + REGEX_UUID + "}";
/**
* Regular expression in the request mapping to accept a string as identifier but not the other kind of

View File

@@ -28,7 +28,6 @@ import com.lyncode.xoai.dataprovider.services.api.ResourceResolver;
import com.lyncode.xoai.dataprovider.services.impl.BaseDateProvider;
import com.lyncode.xoai.dataprovider.xml.xoaiconfig.Configuration;
import com.lyncode.xoai.dataprovider.xml.xoaiconfig.ContextConfiguration;
import org.apache.commons.lang3.time.DateUtils;
import org.dspace.app.rest.builder.CollectionBuilder;
import org.dspace.app.rest.builder.CommunityBuilder;
@@ -39,7 +38,6 @@ import org.dspace.xoai.services.api.EarliestDateResolver;
import org.dspace.xoai.services.api.cache.XOAICacheService;
import org.dspace.xoai.services.api.config.XOAIManagerResolver;
import org.dspace.xoai.services.api.xoai.DSpaceFilterResolver;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;

View File

@@ -12,10 +12,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.dspace.app.rest.test.AbstractControllerIntegrationTest;
import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory;
import org.junit.Before;
import org.junit.Test;

View File

@@ -15,13 +15,10 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import org.dspace.app.rest.builder.CollectionBuilder;
import org.dspace.app.rest.builder.CommunityBuilder;
import org.dspace.app.rest.builder.ItemBuilder;
import org.dspace.app.rest.test.AbstractControllerIntegrationTest;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.content.Item;
import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory;
import org.junit.Before;

View File

@@ -30,6 +30,7 @@ import org.dspace.app.rest.test.AbstractControllerIntegrationTest;
import org.dspace.eperson.Group;
import org.dspace.services.ConfigurationService;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
@@ -58,6 +59,8 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio
}
@Test
@Ignore
// Ignored until an endpoint is added to return all groups. Anonymous is not considered a direct group.
public void testStatusAuthenticated() throws Exception {
String token = getAuthToken(eperson.getEmail(), password);
@@ -352,6 +355,8 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio
}
@Test
@Ignore
// Ignored until an endpoint is added to return all groups
public void testShibbolethLoginRequestAttribute() throws Exception {
context.turnOffAuthorisationSystem();
//Enable Shibboleth login
@@ -393,6 +398,8 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio
}
@Test
@Ignore
// Ignored until an endpoint is added to return all groups
public void testShibbolethLoginRequestHeaderWithIpAuthentication() throws Exception {
configurationService.setProperty("plugin.sequence.org.dspace.authenticate.AuthenticationMethod", SHIB_AND_IP);
configurationService.setProperty("authentication-ip.Administrator", "123.123.123.123");

View File

@@ -8,7 +8,6 @@
package org.dspace.app.rest;
import static java.util.UUID.randomUUID;
import static org.apache.commons.codec.CharEncoding.UTF_8;
import static org.apache.commons.collections.CollectionUtils.isEmpty;
import static org.apache.commons.io.IOUtils.toInputStream;

View File

@@ -7,6 +7,8 @@
*/
package org.dspace.app.rest;
import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
@@ -3717,4 +3719,407 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest
}
}
@Test
public void discoverSearchObjectsTestForAdministrativeViewAnonymous() throws Exception {
//We turn off the authorization system in order to create the structure as defined below
context.turnOffAuthorisationSystem();
//** GIVEN **
//1. A community-collection structure with one parent community with sub-community and two collections.
parentCommunity = CommunityBuilder
.createCommunity(context)
.withName("Parent Community")
.build();
Community child1 = CommunityBuilder
.createSubCommunity(context, parentCommunity)
.withName("Sub Community")
.build();
Collection col1 = CollectionBuilder
.createCollection(context, child1)
.withName("Collection 1")
.build();
Collection col2 = CollectionBuilder
.createCollection(context, child1)
.withName("Collection 2")
.build();
//2. One public item, one private, one withdrawn.
ItemBuilder.createItem(context, col1)
.withTitle("Public Test Item")
.withIssueDate("2010-10-17")
.withAuthor("Smith, Donald")
.withSubject("ExtraEntry")
.build();
ItemBuilder.createItem(context, col2)
.withTitle("Withdrawn Test Item")
.withIssueDate("1990-02-13")
.withAuthor("Smith, Maria")
.withAuthor("Doe, Jane")
.withSubject("ExtraEntry")
.withdrawn()
.build();
ItemBuilder.createItem(context, col2)
.withTitle("Private Test Item")
.withIssueDate("2010-02-13")
.withAuthor("Smith, Maria")
.withAuthor("Doe, Jane")
.withSubject("AnotherTest")
.withSubject("ExtraEntry")
.makeUnDiscoverable()
.build();
context.restoreAuthSystemState();
//** WHEN **
// An anonymous user browses this endpoint to find the withdrawn or private objects in the system
// With a query stating 'Test'
getClient().perform(get("/api/discover/search/objects")
.param("configuration", "administrativeView")
.param("query", "Test"))
//** THEN **
.andExpect(status().isOk())
.andExpect(jsonPath("$.type", is("discover")))
.andExpect(jsonPath("$._embedded.searchResult.page", is(
PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 1)
)))
.andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.contains(
SearchResultMatcher.matchOnItemName("item", "items", "Public Test Item")
)))
.andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects")));
}
@Test
public void discoverSearchObjectsTestForAdministrativeViewEPerson() throws Exception {
//We turn off the authorization system in order to create the structure as defined below
context.turnOffAuthorisationSystem();
//** GIVEN **
//1. A community-collection structure with one parent community with sub-community and two collections.
parentCommunity = CommunityBuilder
.createCommunity(context)
.withName("Parent Community")
.build();
Community child1 = CommunityBuilder
.createSubCommunity(context, parentCommunity)
.withName("Sub Community")
.build();
Collection col1 = CollectionBuilder
.createCollection(context, child1)
.withName("Collection 1")
.build();
Collection col2 = CollectionBuilder
.createCollection(context, child1)
.withName("Collection 2")
.build();
//2. One public item, one private, one withdrawn.
ItemBuilder.createItem(context, col1)
.withTitle("Public Test Item")
.withIssueDate("2010-10-17")
.withAuthor("Smith, Donald")
.withSubject("ExtraEntry")
.build();
ItemBuilder.createItem(context, col2)
.withTitle("Withdrawn Test Item")
.withIssueDate("1990-02-13")
.withAuthor("Smith, Maria")
.withAuthor("Doe, Jane")
.withSubject("ExtraEntry")
.withdrawn()
.build();
ItemBuilder.createItem(context, col2)
.withTitle("Private Test Item")
.withIssueDate("2010-02-13")
.withAuthor("Smith, Maria")
.withAuthor("Doe, Jane")
.withSubject("AnotherTest")
.withSubject("ExtraEntry")
.makeUnDiscoverable()
.build();
context.restoreAuthSystemState();
//** WHEN **
// A non-admin user browses this endpoint to find the withdrawn or private objects in the system
// With a query stating 'Test'
String authToken = getAuthToken(eperson.getEmail(), password);
getClient(authToken).perform(get("/api/discover/search/objects")
.param("configuration", "administrativeView")
.param("query", "Test"))
//** THEN **
.andExpect(status().isOk())
.andExpect(jsonPath("$.type", is("discover")))
.andExpect(jsonPath("$._embedded.searchResult.page", is(
PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 1)
)))
.andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.contains(
SearchResultMatcher.matchOnItemName("item", "items", "Public Test Item")
)))
.andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects")));
}
@Test
public void discoverSearchObjectsTestForAdministrativeViewAdmin() throws Exception {
//We turn off the authorization system in order to create the structure as defined below
context.turnOffAuthorisationSystem();
//** GIVEN **
//1. A community-collection structure with one parent community with sub-community and two collections.
parentCommunity = CommunityBuilder
.createCommunity(context)
.withName("Parent Community")
.build();
Community child1 = CommunityBuilder
.createSubCommunity(context, parentCommunity)
.withName("Sub Community")
.build();
Collection col1 = CollectionBuilder
.createCollection(context, child1)
.withName("Collection 1")
.build();
Collection col2 = CollectionBuilder
.createCollection(context, child1)
.withName("Collection 2")
.build();
//2. One public item, one private, one withdrawn.
ItemBuilder.createItem(context, col1)
.withTitle("Public Test Item")
.withIssueDate("2010-10-17")
.withAuthor("Smith, Donald")
.withSubject("ExtraEntry")
.build();
ItemBuilder.createItem(context, col2)
.withTitle("Withdrawn Test Item")
.withIssueDate("1990-02-13")
.withAuthor("Smith, Maria")
.withAuthor("Doe, Jane")
.withSubject("ExtraEntry")
.withdrawn()
.build();
ItemBuilder.createItem(context, col2)
.withTitle("Private Test Item")
.withIssueDate("2010-02-13")
.withAuthor("Smith, Maria")
.withAuthor("Doe, Jane")
.withSubject("AnotherTest")
.withSubject("ExtraEntry")
.makeUnDiscoverable()
.build();
context.restoreAuthSystemState();
//** WHEN **
// A system admin user browses this endpoint to find the withdrawn or private objects in the system
// With a query stating 'Test'
String adminToken = getAuthToken(admin.getEmail(), password);
getClient(adminToken).perform(get("/api/discover/search/objects")
.param("configuration", "administrativeView")
.param("query", "Test"))
//** THEN **
.andExpect(status().isOk())
.andExpect(jsonPath("$.type", is("discover")))
.andExpect(jsonPath("$._embedded.searchResult.page", is(
PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 3)
)))
.andExpect(jsonPath("$._embedded.searchResult._embedded.objects",
Matchers.containsInAnyOrder(
SearchResultMatcher.matchOnItemName("item", "items", "Public Test Item"),
SearchResultMatcher.matchOnItemName("item", "items", "Withdrawn Test Item"),
SearchResultMatcher.matchOnItemName("item", "items", "Private Test Item")
)
))
.andExpect(jsonPath("$._embedded.facets", Matchers.hasItems(
allOf(
hasJsonPath("$.name", is("discoverable")),
hasJsonPath("$._embedded.values", Matchers.hasItems(
allOf(
hasJsonPath("$.label", is("true")),
hasJsonPath("$.count", is(2))
),
allOf(
hasJsonPath("$.label", is("false")),
hasJsonPath("$.count", is(1))
)
))
),
allOf(
hasJsonPath("$.name", is("withdrawn")),
hasJsonPath("$._embedded.values", Matchers.hasItems(
allOf(
hasJsonPath("$.label", is("true")),
hasJsonPath("$.count", is(1))
),
allOf(
hasJsonPath("$.label", is("false")),
hasJsonPath("$.count", is(2))
)
))
)
)))
.andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects")));
}
@Test
public void discoverSearchObjectsTestForAdministrativeViewWithFilters() throws Exception {
context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder
.createCommunity(context)
.withName("Parent Community")
.build();
Community child1 = CommunityBuilder
.createSubCommunity(context, parentCommunity)
.withName("Sub Community")
.build();
Collection col1 = CollectionBuilder
.createCollection(context, child1)
.withName("Collection 1")
.build();
Collection col2 = CollectionBuilder
.createCollection(context, child1)
.withName("Collection 2")
.build();
ItemBuilder.createItem(context, col1)
.withTitle("Public Test Item")
.withIssueDate("2010-10-17")
.withAuthor("Smith, Donald")
.withSubject("ExtraEntry")
.build();
ItemBuilder.createItem(context, col2)
.withTitle("Withdrawn Test Item")
.withIssueDate("1990-02-13")
.withAuthor("Smith, Maria")
.withAuthor("Doe, Jane")
.withSubject("ExtraEntry")
.withdrawn()
.build();
ItemBuilder.createItem(context, col2)
.withTitle("Private Test Item")
.withIssueDate("2010-02-13")
.withAuthor("Smith, Maria")
.withAuthor("Doe, Jane")
.withSubject("AnotherTest")
.withSubject("ExtraEntry")
.makeUnDiscoverable()
.build();
context.restoreAuthSystemState();
String adminToken = getAuthToken(admin.getEmail(), password);
getClient(adminToken)
.perform(get("/api/discover/search/objects")
.param("configuration", "administrativeView")
.param("query", "Test")
.param("f.withdrawn", "true")
)
.andExpect(status().isOk())
.andExpect(jsonPath("$.type", is("discover")))
.andExpect(jsonPath("$._embedded.searchResult.page", is(
PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 1)
)))
.andExpect(jsonPath("$._embedded.searchResult._embedded.objects",
Matchers.contains(
SearchResultMatcher.matchOnItemName("item", "items", "Withdrawn Test Item")
)
))
.andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects")));
getClient(adminToken)
.perform(get("/api/discover/search/objects")
.param("configuration", "administrativeView")
.param("query", "Test")
.param("f.withdrawn", "false")
)
.andExpect(status().isOk())
.andExpect(jsonPath("$.type", is("discover")))
.andExpect(jsonPath("$._embedded.searchResult.page", is(
PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 2)
)))
.andExpect(jsonPath("$._embedded.searchResult._embedded.objects",
Matchers.containsInAnyOrder(
SearchResultMatcher.matchOnItemName("item", "items", "Public Test Item"),
SearchResultMatcher.matchOnItemName("item", "items", "Private Test Item")
)
))
.andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects")));
getClient(adminToken)
.perform(get("/api/discover/search/objects")
.param("configuration", "administrativeView")
.param("query", "Test")
.param("f.discoverable", "true")
)
.andExpect(status().isOk())
.andExpect(jsonPath("$.type", is("discover")))
.andExpect(jsonPath("$._embedded.searchResult.page", is(
PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 2)
)))
.andExpect(jsonPath("$._embedded.searchResult._embedded.objects",
Matchers.containsInAnyOrder(
SearchResultMatcher.matchOnItemName("item", "items", "Public Test Item"),
SearchResultMatcher.matchOnItemName("item", "items", "Withdrawn Test Item")
)
))
.andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects")));
getClient(adminToken)
.perform(get("/api/discover/search/objects")
.param("configuration", "administrativeView")
.param("query", "Test")
.param("f.discoverable", "false")
)
.andExpect(status().isOk())
.andExpect(jsonPath("$.type", is("discover")))
.andExpect(jsonPath("$._embedded.searchResult.page", is(
PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 1)
)))
.andExpect(jsonPath("$._embedded.searchResult._embedded.objects",
Matchers.contains(
SearchResultMatcher.matchOnItemName("item", "items", "Private Test Item")
)
))
.andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects")));
}
}

View File

@@ -9,6 +9,7 @@ package org.dspace.app.rest;
import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadata;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
@@ -30,8 +31,10 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import org.dspace.app.rest.builder.CollectionBuilder;
import org.dspace.app.rest.builder.CommunityBuilder;
import org.dspace.app.rest.builder.EPersonBuilder;
import org.dspace.app.rest.builder.GroupBuilder;
import org.dspace.app.rest.builder.ItemBuilder;
import org.dspace.app.rest.matcher.EPersonMatcher;
import org.dspace.app.rest.matcher.GroupMatcher;
import org.dspace.app.rest.matcher.HalMatcher;
import org.dspace.app.rest.model.EPersonRest;
import org.dspace.app.rest.model.MetadataRest;
@@ -43,6 +46,7 @@ import org.dspace.app.rest.test.MetadataPatchSuite;
import org.dspace.content.Collection;
import org.dspace.content.Item;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group;
import org.hamcrest.Matchers;
import org.junit.Test;
@@ -310,7 +314,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$._links.byEmail", Matchers.notNullValue()))
.andExpect(jsonPath("$._links.byName", Matchers.notNullValue()));
.andExpect(jsonPath("$._links.byMetadata", Matchers.notNullValue()));
}
@Test
@@ -331,40 +335,40 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken).perform(get("/api/eperson/epersons/search/byEmail")
.param("email", ePerson.getEmail()))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$", is(
EPersonMatcher.matchEPersonEntry(ePerson)
)));
.param("email", ePerson.getEmail()))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$", is(
EPersonMatcher.matchEPersonEntry(ePerson)
)));
// it must be case-insensitive
getClient(authToken).perform(get("/api/eperson/epersons/search/byEmail")
.param("email", ePerson.getEmail().toUpperCase()))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$", is(
EPersonMatcher.matchEPersonEntry(ePerson)
)));
.param("email", ePerson.getEmail().toUpperCase()))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$", is(
EPersonMatcher.matchEPersonEntry(ePerson)
)));
}
@Test
public void findByEmailUndefined() throws Exception {
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken).perform(get("/api/eperson/epersons/search/byEmail")
.param("email", "undefined@undefined.com"))
.andExpect(status().isNoContent());
.param("email", "undefined@undefined.com"))
.andExpect(status().isNoContent());
}
@Test
public void findByEmailUnprocessable() throws Exception {
public void findByEmailMissingParameter() throws Exception {
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken).perform(get("/api/eperson/epersons/search/byEmail"))
.andExpect(status().isBadRequest());
.andExpect(status().isBadRequest());
}
@Test
public void findByName() throws Exception {
public void findByMetadataUsingLastName() throws Exception {
context.turnOffAuthorisationSystem();
EPerson ePerson = EPersonBuilder.createEPerson(context)
.withNameInMetadata("John", "Doe")
@@ -392,8 +396,8 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
.build();
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken).perform(get("/api/eperson/epersons/search/byName")
.param("q", ePerson.getLastName()))
getClient(authToken).perform(get("/api/eperson/epersons/search/byMetadata")
.param("query", ePerson.getLastName()))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$._embedded.epersons", Matchers.containsInAnyOrder(
@@ -405,8 +409,8 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
.andExpect(jsonPath("$.page.totalElements", is(4)));
// it must be case insensitive
getClient(authToken).perform(get("/api/eperson/epersons/search/byName")
.param("q", ePerson.getLastName().toLowerCase()))
getClient(authToken).perform(get("/api/eperson/epersons/search/byMetadata")
.param("query", ePerson.getLastName().toLowerCase()))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$._embedded.epersons", Matchers.containsInAnyOrder(
@@ -419,19 +423,188 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
}
@Test
public void findByNameUndefined() throws Exception {
public void findByMetadataUsingFirstName() throws Exception {
context.turnOffAuthorisationSystem();
EPerson ePerson = EPersonBuilder.createEPerson(context)
.withNameInMetadata("John", "Doe")
.withEmail("Johndoe@fake-email.com")
.build();
EPerson ePerson2 = EPersonBuilder.createEPerson(context)
.withNameInMetadata("Jane", "Smith")
.withEmail("janesmith@fake-email.com")
.build();
EPerson ePerson3 = EPersonBuilder.createEPerson(context)
.withNameInMetadata("John", "Smith")
.withEmail("tomdoe@fake-email.com")
.build();
EPerson ePerson4 = EPersonBuilder.createEPerson(context)
.withNameInMetadata("John-Postfix", "Smath")
.withEmail("dirkdoepostfix@fake-email.com")
.build();
EPerson ePerson5 = EPersonBuilder.createEPerson(context)
.withNameInMetadata("Prefix-John", "Smoth")
.withEmail("harrydoeprefix@fake-email.com")
.build();
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken).perform(get("/api/eperson/epersons/search/byName")
.param("q", "Doe, John"))
getClient(authToken).perform(get("/api/eperson/epersons/search/byMetadata")
.param("query", ePerson.getFirstName()))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$._embedded.epersons", Matchers.containsInAnyOrder(
EPersonMatcher.matchEPersonEntry(ePerson),
EPersonMatcher.matchEPersonEntry(ePerson3),
EPersonMatcher.matchEPersonEntry(ePerson4),
EPersonMatcher.matchEPersonEntry(ePerson5)
)))
.andExpect(jsonPath("$.page.totalElements", is(4)));
// it must be case insensitive
getClient(authToken).perform(get("/api/eperson/epersons/search/byMetadata")
.param("query", ePerson.getFirstName().toLowerCase()))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$._embedded.epersons", Matchers.containsInAnyOrder(
EPersonMatcher.matchEPersonEntry(ePerson),
EPersonMatcher.matchEPersonEntry(ePerson3),
EPersonMatcher.matchEPersonEntry(ePerson4),
EPersonMatcher.matchEPersonEntry(ePerson5)
)))
.andExpect(jsonPath("$.page.totalElements", is(4)));
}
@Test
public void findByMetadataUsingEmail() throws Exception {
context.turnOffAuthorisationSystem();
EPerson ePerson = EPersonBuilder.createEPerson(context)
.withNameInMetadata("John", "Doe")
.withEmail("Johndoe@fake-email.com")
.build();
EPerson ePerson2 = EPersonBuilder.createEPerson(context)
.withNameInMetadata("Jane", "Smith")
.withEmail("janesmith@fake-email.com")
.build();
EPerson ePerson3 = EPersonBuilder.createEPerson(context)
.withNameInMetadata("Tom", "Doe")
.withEmail("tomdoe@fake-email.com")
.build();
EPerson ePerson4 = EPersonBuilder.createEPerson(context)
.withNameInMetadata("Dirk", "Doe-Postfix")
.withEmail("dirkdoepostfix@fake-email.com")
.build();
EPerson ePerson5 = EPersonBuilder.createEPerson(context)
.withNameInMetadata("Harry", "Prefix-Doe")
.withEmail("harrydoeprefix@fake-email.com")
.build();
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken).perform(get("/api/eperson/epersons/search/byMetadata")
.param("query", ePerson.getEmail()))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$._embedded.epersons", Matchers.contains(
EPersonMatcher.matchEPersonEntry(ePerson)
)))
.andExpect(jsonPath("$.page.totalElements", is(1)));
// it must be case insensitive
getClient(authToken).perform(get("/api/eperson/epersons/search/byMetadata")
.param("query", ePerson.getEmail().toLowerCase()))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$._embedded.epersons", Matchers.contains(
EPersonMatcher.matchEPersonEntry(ePerson)
)))
.andExpect(jsonPath("$.page.totalElements", is(1)));
}
@Test
public void findByMetadataUsingUuid() throws Exception {
context.turnOffAuthorisationSystem();
EPerson ePerson = EPersonBuilder.createEPerson(context)
.withNameInMetadata("John", "Doe")
.withEmail("Johndoe@fake-email.com")
.build();
EPerson ePerson2 = EPersonBuilder.createEPerson(context)
.withNameInMetadata("Jane", "Smith")
.withEmail("janesmith@fake-email.com")
.build();
EPerson ePerson3 = EPersonBuilder.createEPerson(context)
.withNameInMetadata("Tom", "Doe")
.withEmail("tomdoe@fake-email.com")
.build();
EPerson ePerson4 = EPersonBuilder.createEPerson(context)
.withNameInMetadata("Dirk", "Doe-Postfix")
.withEmail("dirkdoepostfix@fake-email.com")
.build();
EPerson ePerson5 = EPersonBuilder.createEPerson(context)
.withNameInMetadata("Harry", "Prefix-Doe")
.withEmail("harrydoeprefix@fake-email.com")
.build();
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken).perform(get("/api/eperson/epersons/search/byMetadata")
.param("query", String.valueOf(ePerson.getID())))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$._embedded.epersons", Matchers.contains(
EPersonMatcher.matchEPersonEntry(ePerson)
)))
.andExpect(jsonPath("$.page.totalElements", is(1)));
// it must be case insensitive
getClient(authToken).perform(get("/api/eperson/epersons/search/byMetadata")
.param("query", String.valueOf(ePerson.getID()).toLowerCase()))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$._embedded.epersons", Matchers.contains(
EPersonMatcher.matchEPersonEntry(ePerson)
)))
.andExpect(jsonPath("$.page.totalElements", is(1)));
}
@Test
public void findByMetadataUnauthorized() throws Exception {
getClient().perform(get("/api/eperson/epersons/search/byMetadata")
.param("query", "Doe, John"))
.andExpect(status().isUnauthorized());
}
@Test
public void findByMetadataForbidden() throws Exception {
String authToken = getAuthToken(eperson.getEmail(), password);
getClient(authToken).perform(get("/api/eperson/epersons/search/byMetadata")
.param("query", "Doe, John"))
.andExpect(status().isForbidden());
}
@Test
public void findByMetadataUndefined() throws Exception {
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken).perform(get("/api/eperson/epersons/search/byMetadata")
.param("query", "Doe, John"))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$.page.totalElements", is(0)));
}
@Test
public void findByNameUnprocessable() throws Exception {
public void findByMetadataMissingParameter() throws Exception {
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken).perform(get("/api/eperson/epersons/search/byName"))
getClient(authToken).perform(get("/api/eperson/epersons/search/byMetadata"))
.andExpect(status().isBadRequest());
}
@@ -1321,4 +1494,54 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
new MetadataPatchSuite().runWith(getClient(token), "/api/eperson/epersons/" + ePerson.getID(), expectedStatus);
}
/**
* Test that epersons/<:uuid>/groups endpoint returns the direct groups of the epersons
* @throws Exception
*/
@Test
public void getDirectEpersonGroups() throws Exception {
context.turnOffAuthorisationSystem();
EPerson ePerson = EPersonBuilder.createEPerson(context)
.withNameInMetadata("John", "Doe")
.withEmail("Johndoe@fake-email.com")
.withPassword(password)
.build();
Group parentGroup1 = GroupBuilder.createGroup(context)
.withName("Test Parent Group 1")
.build();
Group childGroup1 = GroupBuilder.createGroup(context)
.withName("Test Child Group 1")
.withParent(parentGroup1)
.addMember(ePerson)
.build();
Group parentGroup2 = GroupBuilder.createGroup(context)
.withName("Test Parent Group 2")
.build();
Group childGroup2 = GroupBuilder.createGroup(context)
.withName("Test Child Group 2")
.withParent(parentGroup2)
.addMember(ePerson)
.build();
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken).perform(get("/api/eperson/epersons/" + ePerson.getID() + "/groups"))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$._embedded.groups", containsInAnyOrder(
GroupMatcher.matchGroupWithName(childGroup1.getName()),
GroupMatcher.matchGroupWithName(childGroup2.getName()))))
.andExpect(jsonPath("$._embedded.groups", Matchers.not(
containsInAnyOrder(
GroupMatcher.matchGroupWithName(parentGroup1.getName()),
GroupMatcher.matchGroupWithName(parentGroup2.getName()))))
);
}
}

View File

@@ -8,7 +8,6 @@
package org.dspace.app.rest;
import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;

View File

@@ -8,7 +8,6 @@
package org.dspace.app.rest;
import static com.jayway.jsonpath.JsonPath.read;
import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
@@ -27,11 +26,9 @@ import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
import javax.ws.rs.core.MediaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.dspace.app.rest.builder.CollectionBuilder;
import org.dspace.app.rest.builder.CommunityBuilder;
import org.dspace.app.rest.builder.EPersonBuilder;

View File

@@ -24,7 +24,6 @@ import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
import javax.ws.rs.core.MediaType;
import org.apache.commons.io.IOUtils;
@@ -669,15 +668,15 @@ public class WorkflowItemRestRepositoryIT extends AbstractControllerIntegrationT
.andExpect(status().is(200));
// a workspaceitem should exist now in the submitter workspace
getClient(tokenSubmitter).perform(get("/api/submission/workspaceitems"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.workspaceitems", Matchers.containsInAnyOrder(
WorkspaceItemMatcher.matchItemWithTitleAndDateIssued(null, "Workflow Item 1",
"2017-10-17"))))
.andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/submission/workspaceitems")))
.andExpect(jsonPath("$.page.size", is(20)))
.andExpect(jsonPath("$.page.totalElements", is(1)));
getClient(tokenSubmitter).perform(get("/api/submission/workspaceitems/search/findBySubmitter")
.param("uuid", submitter.getID().toString()))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.workspaceitems", Matchers.containsInAnyOrder(
WorkspaceItemMatcher.matchItemWithTitleAndDateIssued(null, "Workflow Item 1",
"2017-10-17"))))
.andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/submission/workspaceitems")))
.andExpect(jsonPath("$.page.size", is(20)))
.andExpect(jsonPath("$.page.totalElements", is(1)));
}
@Test
@@ -914,12 +913,16 @@ public class WorkflowItemRestRepositoryIT extends AbstractControllerIntegrationT
context.setCurrentUser(submitter);
//3. a workflow item
XmlWorkflowItem witem = WorkflowItemBuilder.createWorkflowItem(context, col1)
.withTitle("Workflow Item 1")
.withIssueDate("2017-10-17")
.withSubject("ExtraEntry")
.build();
//3. a claimed task with workflow item in edit step
ClaimedTask claimedTask = ClaimedTaskBuilder.createClaimedTask(context, col1, eperson)
.withTitle("Workflow Item 1")
.withIssueDate("2017-10-17")
.withAuthor("Smith, Donald").withAuthor("Doe, John")
.withSubject("ExtraEntry")
.build();
claimedTask.setStepID("editstep");
claimedTask.setActionID("editaction");
XmlWorkflowItem witem = claimedTask.getWorkflowItem();
context.restoreAuthSystemState();
@@ -942,7 +945,6 @@ public class WorkflowItemRestRepositoryIT extends AbstractControllerIntegrationT
// check the new title and untouched values
Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject(witem,
"New Title", "2017-10-17", "ExtraEntry"))));
;
// verify that the patch changes have been persisted
getClient(authToken).perform(get("/api/workflow/workflowitems/" + witem.getID()))
@@ -1039,12 +1041,15 @@ public class WorkflowItemRestRepositoryIT extends AbstractControllerIntegrationT
context.setCurrentUser(submitter);
//3. a workflow item
XmlWorkflowItem witem = WorkflowItemBuilder.createWorkflowItem(context, col1)
.withTitle("Workflow Item 1")
.withIssueDate("2017-10-17")
.withSubject("ExtraEntry")
.build();
//3. a claimed task with workflow item in edit step
ClaimedTask claimedTask = ClaimedTaskBuilder.createClaimedTask(context, col1, eperson)
.withTitle("Workflow Item 1")
.withIssueDate("2017-10-17")
.withSubject("ExtraEntry")
.build();
claimedTask.setStepID("editstep");
claimedTask.setActionID("editaction");
XmlWorkflowItem witem = claimedTask.getWorkflowItem();
context.restoreAuthSystemState();
@@ -1104,30 +1109,40 @@ public class WorkflowItemRestRepositoryIT extends AbstractControllerIntegrationT
context.setCurrentUser(submitter);
//3. some workflow items for our test
XmlWorkflowItem witem = WorkflowItemBuilder.createWorkflowItem(context, col1)
.withTitle("Workflow Item 1")
.withIssueDate("2017-10-17")
.withSubject("ExtraEntry")
.build();
//3. some claimed tasks with workflow items in edit step
ClaimedTask claimedTask = ClaimedTaskBuilder.createClaimedTask(context, col1, eperson)
.withTitle("Workflow Item 1")
.withIssueDate("2017-10-17")
.withAuthor("Smith, Donald").withAuthor("Doe, John")
.withSubject("ExtraEntry")
.build();
claimedTask.setStepID("editstep");
claimedTask.setActionID("editaction");
XmlWorkflowItem witem = claimedTask.getWorkflowItem();
XmlWorkflowItem witemMultipleSubjects = WorkflowItemBuilder.createWorkflowItem(context, col1)
.withTitle("Workflow Item 2")
.withIssueDate("2017-10-17")
.withSubject("Subject1")
.withSubject("Subject2")
.withSubject("Subject3")
.withSubject("Subject4")
.build();
ClaimedTask claimedTask2 = ClaimedTaskBuilder.createClaimedTask(context, col1, eperson)
.withTitle("Workflow Item 2")
.withIssueDate("2017-10-17")
.withSubject("Subject1")
.withSubject("Subject2")
.withSubject("Subject3")
.withSubject("Subject4")
.build();
claimedTask2.setStepID("editstep");
claimedTask2.setActionID("editaction");
XmlWorkflowItem witemMultipleSubjects = claimedTask2.getWorkflowItem();
XmlWorkflowItem witemWithTitleDateAndSubjects = WorkflowItemBuilder.createWorkflowItem(context, col1)
.withTitle("Workflow Item 3")
.withIssueDate("2017-10-17")
.withSubject("Subject1")
.withSubject("Subject2")
.withSubject("Subject3")
.withSubject("Subject4")
.build();
ClaimedTask claimedTask3 = ClaimedTaskBuilder.createClaimedTask(context, col1, eperson)
.withTitle("Workflow Item 3")
.withIssueDate("2017-10-17")
.withSubject("Subject1")
.withSubject("Subject2")
.withSubject("Subject3")
.withSubject("Subject4")
.build();
claimedTask3.setStepID("editstep");
claimedTask3.setActionID("editaction");
XmlWorkflowItem witemWithTitleDateAndSubjects = claimedTask3.getWorkflowItem();
context.restoreAuthSystemState();
@@ -1294,11 +1309,14 @@ public class WorkflowItemRestRepositoryIT extends AbstractControllerIntegrationT
context.setCurrentUser(submitter);
//3. some workflow items for our test
XmlWorkflowItem witem = WorkflowItemBuilder.createWorkflowItem(context, col1)
.withIssueDate("2017-10-17")
.withSubject("ExtraEntry")
.build();
//3. a claimed task with workflow item in edit step
ClaimedTask claimedTask = ClaimedTaskBuilder.createClaimedTask(context, col1, eperson)
.withIssueDate("2017-10-17")
.withSubject("ExtraEntry")
.build();
claimedTask.setStepID("editstep");
claimedTask.setActionID("editaction");
XmlWorkflowItem witem = claimedTask.getWorkflowItem();
context.restoreAuthSystemState();
@@ -1359,11 +1377,16 @@ public class WorkflowItemRestRepositoryIT extends AbstractControllerIntegrationT
context.setCurrentUser(submitter);
//3. some workflow items for our test
XmlWorkflowItem witem = WorkflowItemBuilder.createWorkflowItem(context, col1)
.withTitle("Test WorkflowItem")
.withIssueDate("2017-10-17")
.build();
//3. a claimed task with workflow item in edit step
ClaimedTask claimedTask = ClaimedTaskBuilder.createClaimedTask(context, col1, eperson)
.withTitle("Workflow Item 1")
.withIssueDate("2017-10-17")
.withAuthor("Smith, Donald").withAuthor("Doe, John")
.withSubject("ExtraEntry")
.build();
claimedTask.setStepID("editstep");
claimedTask.setActionID("editaction");
XmlWorkflowItem witem = claimedTask.getWorkflowItem();
context.restoreAuthSystemState();

View File

@@ -7,7 +7,9 @@
*/
package org.dspace.app.rest.builder;
import java.io.IOException;
import java.sql.SQLException;
import java.util.UUID;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.service.DSpaceObjectService;
@@ -107,4 +109,20 @@ public class EPersonBuilder extends AbstractDSpaceObjectBuilder<EPerson> {
ePersonService.setPassword(ePerson, password);
return this;
}
public static void deleteEPerson(UUID uuid) throws SQLException, IOException {
try (Context c = new Context()) {
c.turnOffAuthorisationSystem();
EPerson ePerson = ePersonService.find(c, uuid);
if (ePerson != null) {
try {
ePersonService.delete(c, ePerson);
} catch (AuthorizeException e) {
// cannot occur, just wrap it to make the compiler happy
throw new RuntimeException(e);
}
}
c.complete();
}
}
}

View File

@@ -8,7 +8,6 @@
package org.dspace.app.rest.builder;
import java.io.IOException;
import java.sql.SQLException;
import java.util.Date;

View File

@@ -34,7 +34,7 @@ public class GroupMatcher {
hasJsonPath("$.name", is(name)),
hasJsonPath("$.type", is("group")),
hasJsonPath("$._links.self.href", containsString("/api/eperson/groups/")),
hasJsonPath("$._links.groups.href", endsWith("/groups"))
hasJsonPath("$._links.subgroups.href", endsWith("/subgroups"))
);
}
@@ -43,7 +43,8 @@ public class GroupMatcher {
*/
public static Matcher<? super Object> matchFullEmbeds() {
return matchEmbeds(
"groups[]"
"subgroups[]",
"epersons[]"
);
}
@@ -52,7 +53,8 @@ public class GroupMatcher {
*/
public static Matcher<? super Object> matchLinks(UUID uuid) {
return HalMatcher.matchLinks(REST_SERVER_URL + "eperson/groups/" + uuid,
"groups",
"subgroups",
"epersons",
"self"
);
}
@@ -63,7 +65,8 @@ public class GroupMatcher {
hasJsonPath("$.name", is(name)),
hasJsonPath("$.type", is("group")),
hasJsonPath("$._links.self.href", containsString("/api/eperson/groups/" + uuid.toString())),
hasJsonPath("$._links.groups.href", endsWith(uuid.toString() + "/groups"))
hasJsonPath("$._links.subgroups.href", endsWith(uuid.toString() + "/subgroups")),
hasJsonPath("$._links.epersons.href", endsWith(uuid.toString() + "/epersons"))
);
}
}

View File

@@ -9,7 +9,6 @@ package org.dspace.app.rest.matcher;
import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasNoJsonPath;
import static org.dspace.app.rest.matcher.HalMatcher.matchEmbeds;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.is;
@@ -21,7 +20,7 @@ import org.hamcrest.Matcher;
/**
* Provide convenient org.hamcrest.Matcher to verify a ResourcePolicyRest json response
*
*
* @author Mykhaylo Boychuk (4science.it)
*
*/

View File

@@ -18,7 +18,6 @@ import org.dspace.app.rest.model.WorkflowDefinitionRest;
import org.dspace.xmlworkflow.factory.XmlWorkflowFactory;
import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory;
import org.dspace.xmlworkflow.state.Workflow;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;

View File

@@ -18,7 +18,7 @@
</xsl:attribute>
<metsHdr>
<xsl:attribute name="CREATEDATE">
<xsl:value-of select="concat(format-date(current-date(), 'yyyy-MM-dd'), 'T' , format-time(current-time(), 'HH:mm:ss'), 'Z')"/>
<xsl:value-of select="concat(format-date(current-date(), '[Y0001]-[M02]-[D02]'), 'T' , format-time(current-time(), '[H01]:[m01]:[s01]'), 'Z')"/>
</xsl:attribute>
<agent ROLE="CUSTODIAN" TYPE="ORGANIZATION">
<name><xsl:value-of select="doc:metadata/doc:element[@name='repository']/doc:field[@name='name']/text()" /></name>

View File

@@ -55,6 +55,7 @@
<entry key="workspace" value-ref="workspaceConfiguration" />
<entry key="workflow" value-ref="workflowConfiguration" />
<entry key="undiscoverable" value-ref="unDiscoverableItems" />
<entry key="administrativeView" value-ref="administrativeView" />
<entry key="publication" value-ref="publication"/>
<entry key="person" value-ref="person"/>
<entry key="organization" value-ref="organization"/>
@@ -392,6 +393,150 @@
<property name="spellCheckEnabled" value="true"/>
</bean>
<!--The configuration settings for discovery of withdrawn and undiscoverable items (admin only) and regular items-->
<bean id="administrativeView" class="org.dspace.discovery.configuration.DiscoveryConfiguration" scope="prototype">
<!--Which sidebar facets are to be displayed-->
<property name="sidebarFacets">
<list>
<ref bean="searchFilterDiscoverable" />
<ref bean="searchFilterWithdrawn" />
<ref bean="searchFilterAuthor" />
<ref bean="searchFilterSubject" />
<ref bean="searchFilterIssued" />
<ref bean="searchFilterContentInOriginalBundle"/>
<ref bean="searchFilterEntityType"/>
</list>
</property>
<!-- Set TagCloud configuration per discovery configuration -->
<property name="tagCloudFacetConfiguration" ref="defaultTagCloudFacetConfiguration"/>
<!--The search filters which can be used on the discovery search page-->
<property name="searchFilters">
<list>
<ref bean="searchFilterDiscoverable" />
<ref bean="searchFilterWithdrawn" />
<ref bean="searchFilterTitle" />
<ref bean="searchFilterAuthor" />
<ref bean="searchFilterSubject" />
<ref bean="searchFilterIssued" />
<ref bean="searchFilterContentInOriginalBundle"/>
<ref bean="searchFilterFileNameInOriginalBundle" />
<ref bean="searchFilterFileDescriptionInOriginalBundle" />
<ref bean="searchFilterEntityType"/>
<ref bean="searchFilterIsAuthorOfPublicationRelation"/>
<ref bean="searchFilterIsProjectOfPublicationRelation"/>
<ref bean="searchFilterIsOrgUnitOfPublicationRelation"/>
<ref bean="searchFilterIsPublicationOfJournalIssueRelation"/>
<ref bean="searchFilterIsJournalOfPublicationRelation"/>
</list>
</property>
<!--The sort filters for the discovery search-->
<property name="searchSortConfiguration">
<bean class="org.dspace.discovery.configuration.DiscoverySortConfiguration">
<!--<property name="defaultSort" ref="sortDateIssued"/>-->
<!--DefaultSortOrder can either be desc or asc (desc is default)-->
<property name="defaultSortOrder" value="desc"/>
<property name="sortFields">
<list>
<ref bean="sortTitle" />
<ref bean="sortDateIssued" />
<ref bean="sortDateAccessioned"/>
</list>
</property>
</bean>
</property>
<!--Any default filter queries, these filter queries will be used for all
queries done by discovery for this configuration -->
<property name="defaultFilterQueries">
<list>
<!--Only find items-->
<value>search.resourcetype:Item</value>
</list>
</property>
<!--The configuration for the recent submissions-->
<property name="recentSubmissionConfiguration">
<bean class="org.dspace.discovery.configuration.DiscoveryRecentSubmissionsConfiguration">
<property name="metadataSortField" value="dc.date.accessioned" />
<property name="type" value="date"/>
<property name="max" value="20"/>
<!-- If enabled the collection home page will not display metadata but show a pageable list of recent submissions -->
<property name="useAsHomePage" value="false"/>
</bean>
</property>
<!--Default result per page -->
<property name="defaultRpp" value="10" />
<property name="hitHighlightingConfiguration">
<bean class="org.dspace.discovery.configuration.DiscoveryHitHighlightingConfiguration">
<property name="metadataFields">
<list>
<bean class="org.dspace.discovery.configuration.DiscoveryHitHighlightFieldConfiguration">
<property name="field" value="dc.contributor.author"/>
<property name="snippets" value="5"/>
</bean>
<bean class="org.dspace.discovery.configuration.DiscoveryHitHighlightFieldConfiguration">
<property name="field" value="relationship.type"/>
<property name="snippets" value="5"/>
</bean>
<bean class="org.dspace.discovery.configuration.DiscoveryHitHighlightFieldConfiguration">
<property name="field" value="person.identifier.jobtitle"/>
<property name="snippets" value="5"/>
</bean>
<bean class="org.dspace.discovery.configuration.DiscoveryHitHighlightFieldConfiguration">
<property name="field" value="project.identifier.name"/>
<property name="snippets" value="5"/>
</bean>
<bean class="org.dspace.discovery.configuration.DiscoveryHitHighlightFieldConfiguration">
<property name="field" value="dc.description.abstract"/>
<property name="maxSize" value="250"/>
<property name="snippets" value="2"/>
</bean>
<bean class="org.dspace.discovery.configuration.DiscoveryHitHighlightFieldConfiguration">
<property name="field" value="dc.title"/>
<property name="snippets" value="5"/>
</bean>
<!-- By default, full text snippets are disabled, as snippets of embargoed/restricted bitstreams
may appear in search results when the Item is public. See DS-3498
<bean class="org.dspace.discovery.configuration.DiscoveryHitHighlightFieldConfiguration">
<property name="field" value="project.identifier.status"/>
<property name="snippets" value="5"/>
</bean>
<bean class="org.dspace.discovery.configuration.DiscoveryHitHighlightFieldConfiguration">
<property name="field" value="orgunit.identifier.name"/>
<property name="snippets" value="5"/>
</bean>
<bean class="org.dspace.discovery.configuration.DiscoveryHitHighlightFieldConfiguration">
<property name="field" value="orgunit.identifier.description"/>
<property name="maxSize" value="250"/>
<property name="snippets" value="5"/>
</bean>
-->
</list>
</property>
</bean>
</property>
<property name="moreLikeThisConfiguration">
<bean class="org.dspace.discovery.configuration.DiscoveryMoreLikeThisConfiguration">
<!--When altering this list also alter the "xmlui.Discovery.RelatedItems.help" key as it describes
the metadata fields below-->
<property name="similarityMetadataFields">
<list>
<value>dc.title</value>
<value>dc.contributor.author</value>
<value>dc.creator</value>
<value>dc.subject</value>
</list>
</property>
<!--The minimum number of matching terms across the metadata fields above before an item is found as related -->
<property name="minTermFrequency" value="5"/>
<!--The maximum number of related items displayed-->
<property name="max" value="3"/>
<!--The minimum word length below which words will be ignored-->
<property name="minWordLength" value="5"/>
</bean>
</property>
<!-- When true a "did you mean" example will be displayed, value can be true or false -->
<property name="spellCheckEnabled" value="true"/>
</bean>
<!--The Homepage specific configuration settings for discovery-->
<bean id="homepageConfiguration" class="org.dspace.discovery.configuration.DiscoveryConfiguration" scope="prototype">
<!--Which sidebar facets are to be displayed (same as defaultConfiguration above)-->
@@ -1313,6 +1458,29 @@
</bean>
<!--Search filter configuration beans-->
<bean id="searchFilterDiscoverable" class="org.dspace.discovery.configuration.DiscoverySearchFilterFacet">
<property name="indexFieldName" value="discoverable"/>
<property name="type" value="standard"/>
<property name="metadataFields">
<list>
</list>
</property>
<property name="isOpenByDefault" value="true"/>
<property name="pageSize" value="10"/>
</bean>
<bean id="searchFilterWithdrawn" class="org.dspace.discovery.configuration.DiscoverySearchFilterFacet">
<property name="indexFieldName" value="withdrawn"/>
<property name="type" value="standard"/>
<property name="metadataFields">
<list>
</list>
</property>
<property name="isOpenByDefault" value="true"/>
<property name="pageSize" value="10"/>
</bean>
<bean id="searchFilterTitle" class="org.dspace.discovery.configuration.DiscoverySearchFilter">
<property name="indexFieldName" value="title"/>
<property name="metadataFields">

View File

@@ -228,7 +228,7 @@
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>8.18</version>
<version>8.30</version>
</dependency>
</dependencies>
</plugin>