Merge pull request #1976 from atmire/DS-3542_Spring-permission-evaluator

DS-3542 Spring security authorizations 2
This commit is contained in:
Tim Donohue
2018-08-23 14:26:30 -05:00
committed by GitHub
57 changed files with 1203 additions and 153 deletions

View File

@@ -164,7 +164,7 @@ public class EPersonDAOImpl extends AbstractHibernateDSODAO<EPerson> implements
addMetadataValueWhereQuery(queryBuilder, queryFields, "like", addMetadataValueWhereQuery(queryBuilder, queryFields, "like",
EPerson.class.getSimpleName().toLowerCase() + ".email like :queryParam"); EPerson.class.getSimpleName().toLowerCase() + ".email like :queryParam");
} }
if (!CollectionUtils.isEmpty(sortFields)) { if (!CollectionUtils.isEmpty(sortFields) || StringUtils.isNotBlank(sortField)) {
addMetadataSortQuery(queryBuilder, sortFields, Collections.singletonList(sortField)); addMetadataSortQuery(queryBuilder, sortFields, Collections.singletonList(sortField));
} }

View File

@@ -212,6 +212,11 @@
<artifactId>spring-boot-starter-data-rest</artifactId> <artifactId>spring-boot-starter-data-rest</artifactId>
<version>${spring-boot.version}</version> <version>${spring-boot.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>

View File

@@ -22,16 +22,15 @@ import org.dspace.app.rest.model.BitstreamRest;
import org.dspace.app.rest.utils.ContextUtil; import org.dspace.app.rest.utils.ContextUtil;
import org.dspace.app.rest.utils.MultipartFileSender; import org.dspace.app.rest.utils.MultipartFileSender;
import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.content.Bitstream; import org.dspace.content.Bitstream;
import org.dspace.content.BitstreamFormat; import org.dspace.content.BitstreamFormat;
import org.dspace.content.service.BitstreamService; import org.dspace.content.service.BitstreamService;
import org.dspace.core.Constants;
import org.dspace.core.Context; import org.dspace.core.Context;
import org.dspace.disseminate.service.CitationDocumentService; import org.dspace.disseminate.service.CitationDocumentService;
import org.dspace.services.EventService; import org.dspace.services.EventService;
import org.dspace.usage.UsageEvent; import org.dspace.usage.UsageEvent;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
@@ -68,12 +67,10 @@ public class BitstreamContentRestController {
@Autowired @Autowired
private EventService eventService; private EventService eventService;
@Autowired
private AuthorizeService authorizeService;
@Autowired @Autowired
private CitationDocumentService citationDocumentService; private CitationDocumentService citationDocumentService;
@PreAuthorize("hasPermission(#uuid, 'BITSTREAM', 'READ')")
@RequestMapping(method = {RequestMethod.GET, RequestMethod.HEAD}) @RequestMapping(method = {RequestMethod.GET, RequestMethod.HEAD})
public void retrieve(@PathVariable UUID uuid, HttpServletResponse response, public void retrieve(@PathVariable UUID uuid, HttpServletResponse response,
HttpServletRequest request) throws IOException, SQLException, AuthorizeException { HttpServletRequest request) throws IOException, SQLException, AuthorizeException {
@@ -81,9 +78,9 @@ public class BitstreamContentRestController {
Context context = ContextUtil.obtainContext(request); Context context = ContextUtil.obtainContext(request);
Bitstream bit = getBitstream(context, uuid, response); Bitstream bit = bitstreamService.find(context, uuid);
if (bit == null) { if (bit == null) {
//The bitstream was not found or we're not authorized to read it. response.sendError(HttpServletResponse.SC_NOT_FOUND);
return; return;
} }
@@ -169,18 +166,6 @@ public class BitstreamContentRestController {
return name; return name;
} }
private Bitstream getBitstream(Context context, @PathVariable UUID uuid, HttpServletResponse response)
throws SQLException, IOException, AuthorizeException {
Bitstream bit = bitstreamService.find(context, uuid);
if (bit == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
} else {
authorizeService.authorizeAction(context, bit, Constants.READ);
}
return bit;
}
private boolean isNotAnErrorResponse(HttpServletResponse response) { private boolean isNotAnErrorResponse(HttpServletResponse response) {
Response.Status.Family responseCode = Response.Status.Family.familyOf(response.getStatus()); Response.Status.Family responseCode = Response.Status.Family.familyOf(response.getStatus());
return responseCode.equals(Response.Status.Family.SUCCESSFUL) return responseCode.equals(Response.Status.Family.SUCCESSFUL)

View File

@@ -21,6 +21,7 @@ import org.dspace.app.rest.model.RestModel;
import org.dspace.app.rest.model.hateoas.DSpaceResource; import org.dspace.app.rest.model.hateoas.DSpaceResource;
import org.dspace.app.rest.utils.Utils; import org.dspace.app.rest.utils.Utils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.hateoas.Link; import org.springframework.hateoas.Link;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -45,7 +46,7 @@ public class DSpaceResourceHalLinkFactory extends HalLinkFactory<DSpaceResource,
Method readMethod = pd.getReadMethod(); Method readMethod = pd.getReadMethod();
String name = pd.getName(); String name = pd.getName();
if (readMethod != null && !"class".equals(name)) { if (readMethod != null && !"class".equals(name)) {
LinkRest linkAnnotation = readMethod.getAnnotation(LinkRest.class); LinkRest linkAnnotation = AnnotationUtils.findAnnotation(readMethod, LinkRest.class);
if (linkAnnotation != null) { if (linkAnnotation != null) {
if (StringUtils.isNotBlank(linkAnnotation.name())) { if (StringUtils.isNotBlank(linkAnnotation.name())) {
@@ -57,10 +58,9 @@ public class DSpaceResourceHalLinkFactory extends HalLinkFactory<DSpaceResource,
if (StringUtils.isBlank(linkAnnotation.method())) { if (StringUtils.isBlank(linkAnnotation.method())) {
Object linkedObject = readMethod.invoke(data); Object linkedObject = readMethod.invoke(data);
if (linkedObject instanceof RestAddressableModel && linkAnnotation.linkClass() if (linkedObject instanceof RestAddressableModel
.isAssignableFrom( && linkAnnotation.linkClass().isAssignableFrom(linkedObject.getClass())) {
linkedObject
.getClass())) {
linkToSubResource = utils linkToSubResource = utils
.linkToSingleResource((RestAddressableModel) linkedObject, name); .linkToSingleResource((RestAddressableModel) linkedObject, name);
} }

View File

@@ -8,6 +8,7 @@
package org.dspace.app.rest.model.hateoas; package org.dspace.app.rest.model.hateoas;
import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.hateoas.core.EvoInflectorRelProvider; import org.springframework.hateoas.core.EvoInflectorRelProvider;
/** /**
@@ -20,7 +21,7 @@ public class DSpaceRelProvider extends EvoInflectorRelProvider {
@Override @Override
public String getItemResourceRelFor(Class<?> type) { public String getItemResourceRelFor(Class<?> type) {
RelNameDSpaceResource nameAnnotation = type.getAnnotation(RelNameDSpaceResource.class); RelNameDSpaceResource nameAnnotation = AnnotationUtils.findAnnotation(type, RelNameDSpaceResource.class);
if (nameAnnotation != null) { if (nameAnnotation != null) {
return nameAnnotation.value(); return nameAnnotation.value();
} }

View File

@@ -25,6 +25,7 @@ import org.dspace.app.rest.model.RestAddressableModel;
import org.dspace.app.rest.repository.DSpaceRestRepository; import org.dspace.app.rest.repository.DSpaceRestRepository;
import org.dspace.app.rest.repository.LinkRestRepository; import org.dspace.app.rest.repository.LinkRestRepository;
import org.dspace.app.rest.utils.Utils; import org.dspace.app.rest.utils.Utils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageImpl;
import org.springframework.hateoas.Link; import org.springframework.hateoas.Link;
@@ -91,7 +92,7 @@ public abstract class DSpaceResource<T extends RestAddressableModel> extends HAL
Method readMethod = pd.getReadMethod(); Method readMethod = pd.getReadMethod();
String name = pd.getName(); String name = pd.getName();
if (readMethod != null && !"class".equals(name)) { if (readMethod != null && !"class".equals(name)) {
LinkRest linkAnnotation = readMethod.getAnnotation(LinkRest.class); LinkRest linkAnnotation = AnnotationUtils.findAnnotation(readMethod, LinkRest.class);
if (linkAnnotation != null) { if (linkAnnotation != null) {
if (StringUtils.isNotBlank(linkAnnotation.name())) { if (StringUtils.isNotBlank(linkAnnotation.name())) {

View File

@@ -29,6 +29,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
@@ -54,6 +55,7 @@ public class AuthorityEntryLinkRepository extends AbstractDSpaceRestRepository
return new AuthorityEntryResource(model); return new AuthorityEntryResource(model);
} }
@PreAuthorize("hasAuthority('AUTHENTICATED')")
public Page<AuthorityEntryRest> query(HttpServletRequest request, String name, public Page<AuthorityEntryRest> query(HttpServletRequest request, String name,
Pageable pageable, String projection) { Pageable pageable, String projection) {
Context context = obtainContext(); Context context = obtainContext();

View File

@@ -20,6 +20,7 @@ import org.dspace.content.authority.service.ChoiceAuthorityService;
import org.dspace.core.Context; import org.dspace.core.Context;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
@@ -42,6 +43,7 @@ public class AuthorityEntryValueLinkRepository extends AbstractDSpaceRestReposit
return new AuthorityEntryResource(model); return new AuthorityEntryResource(model);
} }
@PreAuthorize("hasAuthority('AUTHENTICATED')")
public AuthorityEntryRest getResource(HttpServletRequest request, String name, String relId, public AuthorityEntryRest getResource(HttpServletRequest request, String name, String relId,
Pageable pageable, String projection) { Pageable pageable, String projection) {
Context context = obtainContext(); Context context = obtainContext();

View File

@@ -21,6 +21,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
@@ -37,6 +38,7 @@ public class AuthorityRestRepository extends DSpaceRestRepository<AuthorityRest,
@Autowired @Autowired
private AuthorityUtils authorityUtils; private AuthorityUtils authorityUtils;
@PreAuthorize("hasAuthority('AUTHENTICATED')")
@Override @Override
public AuthorityRest findOne(Context context, String name) { public AuthorityRest findOne(Context context, String name) {
ChoiceAuthority source = cas.getChoiceAuthorityByAuthorityName(name); ChoiceAuthority source = cas.getChoiceAuthorityByAuthorityName(name);
@@ -44,6 +46,7 @@ public class AuthorityRestRepository extends DSpaceRestRepository<AuthorityRest,
return result; return result;
} }
@PreAuthorize("hasAuthority('AUTHENTICATED')")
@Override @Override
public Page<AuthorityRest> findAll(Context context, Pageable pageable) { public Page<AuthorityRest> findAll(Context context, Pageable pageable) {
Set<String> authoritiesName = cas.getChoiceAuthoritiesNames(); Set<String> authoritiesName = cas.getChoiceAuthoritiesNames();

View File

@@ -28,6 +28,7 @@ import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.data.rest.webmvc.ResourceNotFoundException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -51,6 +52,7 @@ public class BitstreamRestRepository extends DSpaceRestRepository<BitstreamRest,
} }
@Override @Override
@PreAuthorize("hasPermission(#id, 'BITSTREAM', 'READ')")
public BitstreamRest findOne(Context context, UUID id) { public BitstreamRest findOne(Context context, UUID id) {
Bitstream bit = null; Bitstream bit = null;
try { try {
@@ -72,6 +74,7 @@ public class BitstreamRestRepository extends DSpaceRestRepository<BitstreamRest,
} }
@Override @Override
@PreAuthorize("hasAuthority('ADMIN')")
public Page<BitstreamRest> findAll(Context context, Pageable pageable) { public Page<BitstreamRest> findAll(Context context, Pageable pageable) {
List<Bitstream> bit = new ArrayList<Bitstream>(); List<Bitstream> bit = new ArrayList<Bitstream>();
Iterator<Bitstream> it = null; Iterator<Bitstream> it = null;

View File

@@ -29,6 +29,7 @@ import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.data.rest.webmvc.ResourceNotFoundException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
@@ -55,6 +56,7 @@ public class CollectionRestRepository extends DSpaceRestRepository<CollectionRes
} }
@Override @Override
@PreAuthorize("hasPermission(#id, 'COLLECTION', 'READ')")
public CollectionRest findOne(Context context, UUID id) { public CollectionRest findOne(Context context, UUID id) {
Collection collection = null; Collection collection = null;
try { try {

View File

@@ -25,6 +25,7 @@ import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.data.rest.webmvc.ResourceNotFoundException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
@@ -47,6 +48,7 @@ public class CommunityRestRepository extends DSpaceRestRepository<CommunityRest,
} }
@Override @Override
@PreAuthorize("hasPermission(#id, 'COMMUNITY', 'READ')")
public CommunityRest findOne(Context context, UUID id) { public CommunityRest findOne(Context context, UUID id) {
Community community = null; Community community = null;
try { try {

View File

@@ -23,6 +23,7 @@ import org.dspace.app.rest.model.step.UploadStatusResponse;
import org.dspace.app.util.DCInputsReaderException; import org.dspace.app.util.DCInputsReaderException;
import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.AuthorizeException;
import org.dspace.core.Context; import org.dspace.core.Context;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
@@ -43,6 +44,13 @@ public abstract class DSpaceRestRepository<T extends RestAddressableModel, ID ex
private static final Logger log = Logger.getLogger(DSpaceRestRepository.class); private static final Logger log = Logger.getLogger(DSpaceRestRepository.class);
//Trick to make inner-calls to ourselves that are checked by Spring security
//See:
// https://stackoverflow.com/questions/13564627/spring-aop-not-working-for-method-call-inside-another-method
// https://docs.spring.io/spring/docs/4.3.18.RELEASE/spring-framework-reference/htmlsingle/#aop-understanding-aop-proxies
@Autowired
private DSpaceRestRepository<T, ID> thisRepository;
@Override @Override
public <S extends T> S save(S entity) { public <S extends T> S save(S entity) {
Context context = null; Context context = null;
@@ -72,7 +80,7 @@ public abstract class DSpaceRestRepository<T extends RestAddressableModel, ID ex
@Override @Override
public T findOne(ID id) { public T findOne(ID id) {
Context context = obtainContext(); Context context = obtainContext();
return findOne(context, id); return thisRepository.findOne(context, id);
} }
public abstract T findOne(Context context, ID id); public abstract T findOne(Context context, ID id);
@@ -103,7 +111,7 @@ public abstract class DSpaceRestRepository<T extends RestAddressableModel, ID ex
public void delete(ID id) { public void delete(ID id) {
Context context = obtainContext(); Context context = obtainContext();
try { try {
delete(context, id); thisRepository.delete(context, id);
context.commit(); context.commit();
} catch (AuthorizeException e) { } catch (AuthorizeException e) {
throw new RESTAuthorizationException(e); throw new RESTAuthorizationException(e);
@@ -141,7 +149,7 @@ public abstract class DSpaceRestRepository<T extends RestAddressableModel, ID ex
@Override @Override
public Page<T> findAll(Pageable pageable) { public Page<T> findAll(Pageable pageable) {
Context context = obtainContext(); Context context = obtainContext();
return findAll(context, pageable); return thisRepository.findAll(context, pageable);
} }
public abstract Page<T> findAll(Context context, Pageable pageable); public abstract Page<T> findAll(Context context, Pageable pageable);
@@ -154,7 +162,7 @@ public abstract class DSpaceRestRepository<T extends RestAddressableModel, ID ex
Context context = null; Context context = null;
try { try {
context = obtainContext(); context = obtainContext();
T entity = createAndReturn(context); T entity = thisRepository.createAndReturn(context);
context.commit(); context.commit();
return entity; return entity;
} catch (AuthorizeException e) { } catch (AuthorizeException e) {
@@ -177,7 +185,7 @@ public abstract class DSpaceRestRepository<T extends RestAddressableModel, ID ex
throws HttpRequestMethodNotSupportedException, UnprocessableEntityException, PatchBadRequestException { throws HttpRequestMethodNotSupportedException, UnprocessableEntityException, PatchBadRequestException {
Context context = obtainContext(); Context context = obtainContext();
try { try {
patch(context, request, apiCategory, model, id, patch); thisRepository.patch(context, request, apiCategory, model, id, patch);
context.commit(); context.commit();
} catch (AuthorizeException ae) { } catch (AuthorizeException ae) {
throw new RESTAuthorizationException(ae); throw new RESTAuthorizationException(ae);

View File

@@ -34,6 +34,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -94,6 +95,7 @@ public class EPersonRestRepository extends DSpaceRestRepository<EPersonRest, UUI
} }
@Override @Override
@PreAuthorize("hasPermission(#id, 'EPERSON', 'READ')")
public EPersonRest findOne(Context context, UUID id) { public EPersonRest findOne(Context context, UUID id) {
EPerson eperson = null; EPerson eperson = null;
try { try {
@@ -108,6 +110,7 @@ public class EPersonRestRepository extends DSpaceRestRepository<EPersonRest, UUI
} }
@Override @Override
@PreAuthorize("hasAuthority('ADMIN')")
public Page<EPersonRest> findAll(Context context, Pageable pageable) { public Page<EPersonRest> findAll(Context context, Pageable pageable) {
List<EPerson> epersons = null; List<EPerson> epersons = null;
int total = 0; int total = 0;
@@ -117,7 +120,7 @@ public class EPersonRestRepository extends DSpaceRestRepository<EPersonRest, UUI
"The EPerson collection endpoint is reserved to system administrators"); "The EPerson collection endpoint is reserved to system administrators");
} }
total = es.countTotal(context); total = es.countTotal(context);
epersons = es.findAll(context, EPerson.ID, pageable.getPageSize(), pageable.getOffset()); epersons = es.findAll(context, EPerson.EMAIL, pageable.getPageSize(), pageable.getOffset());
} catch (SQLException e) { } catch (SQLException e) {
throw new RuntimeException(e.getMessage(), e); throw new RuntimeException(e.getMessage(), e);
} }

View File

@@ -22,6 +22,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
@@ -38,6 +39,7 @@ public class GroupRestRepository extends DSpaceRestRepository<GroupRest, UUID> {
GroupConverter converter; GroupConverter converter;
@Override @Override
@PreAuthorize("hasPermission(#id, 'GROUP', 'READ')")
public GroupRest findOne(Context context, UUID id) { public GroupRest findOne(Context context, UUID id) {
Group group = null; Group group = null;
try { try {
@@ -51,6 +53,7 @@ public class GroupRestRepository extends DSpaceRestRepository<GroupRest, UUID> {
return converter.fromModel(group); return converter.fromModel(group);
} }
@PreAuthorize("hasAuthority('ADMIN')")
@Override @Override
public Page<GroupRest> findAll(Context context, Pageable pageable) { public Page<GroupRest> findAll(Context context, Pageable pageable) {
List<Group> groups = null; List<Group> groups = null;

View File

@@ -32,6 +32,7 @@ import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.data.rest.webmvc.ResourceNotFoundException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
@@ -63,6 +64,7 @@ public class ItemRestRepository extends DSpaceRestRepository<ItemRest, UUID> {
} }
@Override @Override
@PreAuthorize("hasPermission(#id, 'ITEM', 'READ')")
public ItemRest findOne(Context context, UUID id) { public ItemRest findOne(Context context, UUID id) {
Item item = null; Item item = null;
try { try {
@@ -77,6 +79,7 @@ public class ItemRestRepository extends DSpaceRestRepository<ItemRest, UUID> {
} }
@Override @Override
@PreAuthorize("hasAuthority('ADMIN')")
public Page<ItemRest> findAll(Context context, Pageable pageable) { public Page<ItemRest> findAll(Context context, Pageable pageable) {
Iterator<Item> it = null; Iterator<Item> it = null;
List<Item> items = new ArrayList<Item>(); List<Item> items = new ArrayList<Item>();

View File

@@ -21,6 +21,7 @@ import org.dspace.core.Context;
import org.dspace.core.service.LicenseService; import org.dspace.core.service.LicenseService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
@@ -43,6 +44,7 @@ public class LicenseRestLinkRepository extends AbstractDSpaceRestRepository
return new LicenseResource(model); return new LicenseResource(model);
} }
@PreAuthorize("hasAuthority('AUTHENTICATED')")
public LicenseRest getLicenseCollection(HttpServletRequest request, UUID uuid, Pageable pageable, String projection) public LicenseRest getLicenseCollection(HttpServletRequest request, UUID uuid, Pageable pageable, String projection)
throws Exception { throws Exception {
Context context = obtainContext(); Context context = obtainContext();

View File

@@ -20,6 +20,7 @@ import org.dspace.core.Context;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
@@ -39,6 +40,7 @@ public class ResourcePolicyRestRepository extends DSpaceRestRepository<ResourceP
@Autowired @Autowired
Utils utils; Utils utils;
@PreAuthorize("hasAuthority('AUTHENTICATED')")
@Override @Override
public ResourcePolicyRest findOne(Context context, Integer id) { public ResourcePolicyRest findOne(Context context, Integer id) {
ResourcePolicy source = null; ResourcePolicy source = null;
@@ -53,6 +55,7 @@ public class ResourcePolicyRestRepository extends DSpaceRestRepository<ResourceP
return resourcePolicyConverter.convert(source); return resourcePolicyConverter.convert(source);
} }
@PreAuthorize("hasAuthority('AUTHENTICATED')")
@Override @Override
public Page<ResourcePolicyRest> findAll(Context context, Pageable pageable) { public Page<ResourcePolicyRest> findAll(Context context, Pageable pageable) {
throw new RepositoryMethodNotImplementedException(ResourcePolicyRest.NAME, "findAll"); throw new RepositoryMethodNotImplementedException(ResourcePolicyRest.NAME, "findAll");

View File

@@ -28,6 +28,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
@@ -48,6 +49,7 @@ public class SubmissionDefinitionRestRepository extends DSpaceRestRepository<Sub
submissionConfigReader = new SubmissionConfigReader(); submissionConfigReader = new SubmissionConfigReader();
} }
@PreAuthorize("hasAuthority('AUTHENTICATED')")
@Override @Override
public SubmissionDefinitionRest findOne(Context context, String submitName) { public SubmissionDefinitionRest findOne(Context context, String submitName) {
SubmissionConfig subConfig = submissionConfigReader.getSubmissionConfigByName(submitName); SubmissionConfig subConfig = submissionConfigReader.getSubmissionConfigByName(submitName);
@@ -57,6 +59,7 @@ public class SubmissionDefinitionRestRepository extends DSpaceRestRepository<Sub
return converter.convert(subConfig); return converter.convert(subConfig);
} }
@PreAuthorize("hasAuthority('AUTHENTICATED')")
@Override @Override
public Page<SubmissionDefinitionRest> findAll(Context context, Pageable pageable) { public Page<SubmissionDefinitionRest> findAll(Context context, Pageable pageable) {
List<SubmissionConfig> subConfs = new ArrayList<SubmissionConfig>(); List<SubmissionConfig> subConfs = new ArrayList<SubmissionConfig>();
@@ -66,6 +69,7 @@ public class SubmissionDefinitionRestRepository extends DSpaceRestRepository<Sub
return page; return page;
} }
@PreAuthorize("hasAuthority('AUTHENTICATED')")
@SearchRestMethod(name = "findByCollection") @SearchRestMethod(name = "findByCollection")
public SubmissionDefinitionRest findByCollection(@Parameter(value = "uuid", required = true) UUID collectionUuid) public SubmissionDefinitionRest findByCollection(@Parameter(value = "uuid", required = true) UUID collectionUuid)
throws SQLException { throws SQLException {

View File

@@ -21,6 +21,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
@@ -41,6 +42,7 @@ public class SubmissionFormRestRepository extends DSpaceRestRepository<Submissio
inputReader = new DCInputsReader(); inputReader = new DCInputsReader();
} }
@PreAuthorize("hasAuthority('AUTHENTICATED')")
@Override @Override
public SubmissionFormRest findOne(Context context, String submitName) { public SubmissionFormRest findOne(Context context, String submitName) {
DCInputSet inputConfig; DCInputSet inputConfig;
@@ -55,6 +57,7 @@ public class SubmissionFormRestRepository extends DSpaceRestRepository<Submissio
return converter.convert(inputConfig); return converter.convert(inputConfig);
} }
@PreAuthorize("hasAuthority('AUTHENTICATED')")
@Override @Override
public Page<SubmissionFormRest> findAll(Context context, Pageable pageable) { public Page<SubmissionFormRest> findAll(Context context, Pageable pageable) {
List<DCInputSet> subConfs = new ArrayList<DCInputSet>(); List<DCInputSet> subConfs = new ArrayList<DCInputSet>();

View File

@@ -23,6 +23,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
@@ -42,6 +43,7 @@ public class SubmissionPanelRestRepository extends DSpaceRestRepository<Submissi
submissionConfigReader = new SubmissionConfigReader(); submissionConfigReader = new SubmissionConfigReader();
} }
@PreAuthorize("hasAuthority('AUTHENTICATED')")
@Override @Override
public SubmissionSectionRest findOne(Context context, String id) { public SubmissionSectionRest findOne(Context context, String id) {
try { try {
@@ -53,6 +55,7 @@ public class SubmissionPanelRestRepository extends DSpaceRestRepository<Submissi
} }
} }
@PreAuthorize("hasAuthority('AUTHENTICATED')")
@Override @Override
public Page<SubmissionSectionRest> findAll(Context context, Pageable pageable) { public Page<SubmissionSectionRest> findAll(Context context, Pageable pageable) {
List<SubmissionConfig> subConfs = new ArrayList<SubmissionConfig>(); List<SubmissionConfig> subConfs = new ArrayList<SubmissionConfig>();

View File

@@ -30,6 +30,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
@@ -61,6 +62,7 @@ public class SubmissionUploadRestRepository extends DSpaceRestRepository<Submiss
submissionConfigReader = new SubmissionConfigReader(); submissionConfigReader = new SubmissionConfigReader();
} }
@PreAuthorize("hasAuthority('AUTHENTICATED')")
@Override @Override
public SubmissionUploadRest findOne(Context context, String submitName) { public SubmissionUploadRest findOne(Context context, String submitName) {
UploadConfiguration config = uploadConfigurationService.getMap().get(submitName); UploadConfiguration config = uploadConfigurationService.getMap().get(submitName);
@@ -72,6 +74,7 @@ public class SubmissionUploadRestRepository extends DSpaceRestRepository<Submiss
return null; return null;
} }
@PreAuthorize("hasAuthority('AUTHENTICATED')")
@Override @Override
public Page<SubmissionUploadRest> findAll(Context context, Pageable pageable) { public Page<SubmissionUploadRest> findAll(Context context, Pageable pageable) {
List<SubmissionConfig> subConfs = new ArrayList<SubmissionConfig>(); List<SubmissionConfig> subConfs = new ArrayList<SubmissionConfig>();

View File

@@ -93,6 +93,7 @@ public class WorkspaceItemRestRepository extends DSpaceRestRepository<WorkspaceI
submissionConfigReader = new SubmissionConfigReader(); submissionConfigReader = new SubmissionConfigReader();
} }
//TODO @PreAuthorize("hasPermission(#id, 'WORKSPACEITEM', 'READ')")
@Override @Override
public WorkspaceItemRest findOne(Context context, Integer id) { public WorkspaceItemRest findOne(Context context, Integer id) {
WorkspaceItem witem = null; WorkspaceItem witem = null;
@@ -107,6 +108,7 @@ public class WorkspaceItemRestRepository extends DSpaceRestRepository<WorkspaceI
return converter.fromModel(witem); return converter.fromModel(witem);
} }
//TODO @PreAuthorize("hasAuthority('ADMIN')")
@Override @Override
public Page<WorkspaceItemRest> findAll(Context context, Pageable pageable) { public Page<WorkspaceItemRest> findAll(Context context, Pageable pageable) {
List<WorkspaceItem> witems = null; List<WorkspaceItem> witems = null;
@@ -121,6 +123,7 @@ public class WorkspaceItemRestRepository extends DSpaceRestRepository<WorkspaceI
return page; return page;
} }
//TODO @PreAuthorize("hasPermission(#submitterID, 'EPERSON', 'READ')")
@SearchRestMethod(name = "findBySubmitter") @SearchRestMethod(name = "findBySubmitter")
public Page<WorkspaceItemRest> findBySubmitter(@Parameter(value = "uuid", required = true) UUID submitterID, public Page<WorkspaceItemRest> findBySubmitter(@Parameter(value = "uuid", required = true) UUID submitterID,
Pageable pageable) { Pageable pageable) {
@@ -194,6 +197,7 @@ public class WorkspaceItemRestRepository extends DSpaceRestRepository<WorkspaceI
return new WorkspaceItemResource(witem, utils, rels); return new WorkspaceItemResource(witem, utils, rels);
} }
//TODO @PreAuthorize("hasPermission(#id, 'WORKSPACEITEM', 'WRITE')")
@Override @Override
public UploadBitstreamRest upload(HttpServletRequest request, String apiCategory, String model, Integer id, public UploadBitstreamRest upload(HttpServletRequest request, String apiCategory, String model, Integer id,
String extraField, MultipartFile file) throws Exception { String extraField, MultipartFile file) throws Exception {
@@ -243,6 +247,7 @@ public class WorkspaceItemRestRepository extends DSpaceRestRepository<WorkspaceI
return result; return result;
} }
//TODO @PreAuthorize("hasPermission(#id, 'WORKSPACEITEM', 'WRITE')")
@Override @Override
public void patch(Context context, HttpServletRequest request, String apiCategory, String model, Integer id, public void patch(Context context, HttpServletRequest request, String apiCategory, String model, Integer id,
Patch patch) throws SQLException, AuthorizeException { Patch patch) throws SQLException, AuthorizeException {
@@ -304,6 +309,7 @@ public class WorkspaceItemRestRepository extends DSpaceRestRepository<WorkspaceI
} }
} }
//TODO @PreAuthorize("hasPermission(#id, 'WORKSPACEITEM', 'DELETE')")
@Override @Override
protected void delete(Context context, Integer id) throws AuthorizeException { protected void delete(Context context, Integer id) throws AuthorizeException {
WorkspaceItem witem = null; WorkspaceItem witem = null;

View File

@@ -0,0 +1,74 @@
/**
* 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.dspace.app.rest.utils.ContextUtil;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.service.EPersonService;
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;
/**
* Administrators are always allowed to perform any action on any DSpace object. This plugin will check if
* the authenticated EPerson is an administrator of the provided target DSpace Object. If that is the case,
* the authenticated EPerson is allowed to perform the requested action.
*/
@Component
public class AdminRestPermissionEvaluatorPlugin extends DSpaceObjectPermissionEvaluatorPlugin {
private static final Logger log = LoggerFactory.getLogger(DSpaceObjectPermissionEvaluatorPlugin.class);
@Autowired
private AuthorizeService authorizeService;
@Autowired
private RequestService requestService;
@Autowired
private EPersonService ePersonService;
@Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType,
Object permission) {
//We do not check the "permission" object here because administrators are allowed to do everything
Request request = requestService.getCurrentRequest();
Context context = ContextUtil.obtainContext(request.getServletRequest());
EPerson ePerson = null;
try {
ePerson = ePersonService.findByEmail(context, (String) authentication.getPrincipal());
if (ePerson != null) {
//Check if user is a repository admin
if (authorizeService.isAdmin(context, ePerson)) {
return true;
}
//We don't check the DSO Admin level here as this is action specific.
//For example see org.dspace.authorize.AuthorizeConfiguration.canCollectionAdminPerformItemDeletion
}
} catch (SQLException e) {
log.error(e.getMessage(), e);
}
return false;
}
}

View File

@@ -0,0 +1,91 @@
/**
* 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 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.factory.ContentServiceFactory;
import org.dspace.content.service.DSpaceObjectService;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.service.EPersonService;
import org.dspace.services.RequestService;
import org.dspace.services.model.Request;
import org.dspace.util.UUIDUtils;
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;
/**
* DSpaceObjectPermissionEvaluatorPlugin will check persmissions based on the DSpace {@link AuthorizeService}.
* This service will validate if the authenticated user is allowed to perform an action on the given DSpace Object
* based on the resource policies attached to that DSpace object.
*/
@Component
public class AuthorizeServicePermissionEvaluatorPlugin extends DSpaceObjectPermissionEvaluatorPlugin {
private static final Logger log = LoggerFactory.getLogger(AuthorizeServicePermissionEvaluatorPlugin.class);
@Autowired
private AuthorizeService authorizeService;
@Autowired
private RequestService requestService;
@Autowired
private EPersonService ePersonService;
@Autowired
private ContentServiceFactory contentServiceFactory;
@Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType,
Object permission) {
DSpaceRestPermission restPermission = DSpaceRestPermission.convert(permission);
if (restPermission == null) {
return false;
}
Request request = requestService.getCurrentRequest();
Context context = ContextUtil.obtainContext(request.getServletRequest());
EPerson ePerson = null;
try {
ePerson = ePersonService.findByEmail(context, (String) authentication.getPrincipal());
UUID dsoId = UUIDUtils.fromString(targetId.toString());
DSpaceObjectService dSpaceObjectService =
contentServiceFactory.getDSpaceObjectService(Constants.getTypeID(targetType));
if (dSpaceObjectService != null && dsoId != null) {
DSpaceObject dSpaceObject = dSpaceObjectService.find(context, dsoId);
//If the dso is null then we give permission so we can throw another status code instead
if (dSpaceObject == null) {
return true;
}
return authorizeService.authorizeActionBoolean(context, ePerson, dSpaceObject,
restPermission.getDspaceApiActionId(), false);
}
} catch (SQLException e) {
log.error(e.getMessage(), e);
}
return false;
}
}

View File

@@ -0,0 +1,41 @@
/**
* 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.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
/**
* Spring security authentication entry point to return a 401 response for unauthorized requests
* This class is used in the {@link WebSecurityConfiguration} class.
*/
public class DSpace401AuthenticationEntryPoint implements AuthenticationEntryPoint {
private RestAuthenticationService restAuthenticationService;
public DSpace401AuthenticationEntryPoint(RestAuthenticationService restAuthenticationService) {
this.restAuthenticationService = restAuthenticationService;
}
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
response.setHeader("WWW-Authenticate",
restAuthenticationService.getWwwAuthenticateHeaderValue(request, response));
response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
authException.getMessage());
}
}

View File

@@ -0,0 +1,38 @@
/**
* 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 org.dspace.app.rest.model.DSpaceObjectRest;
import org.springframework.security.core.Authentication;
/**
* Abstract {@link RestPermissionEvaluatorPlugin} class that contains utility methods to
* evaluate permissions for a DSpace Object.
*/
public abstract class DSpaceObjectPermissionEvaluatorPlugin implements RestPermissionEvaluatorPlugin {
/**
* Utility implementation to make the implementation of DSpace Object Permission evaluator plugins more easy.
*
* @param authentication represents the user in question. Should not be null.
* @param targetDomainObject the DSpace object for which permissions should be
* checked. May be null in which case implementations should return false, as the null
* condition can be checked explicitly in the expression.
* @param permission a representation of the DSpace action as supplied by the
* expression system. This corresponds to the DSpace action. Not null.
* @return true if the permission is granted by one of the plugins, false otherwise
*/
public boolean hasPermission(Authentication authentication, Object targetDomainObject,
Object permission) {
DSpaceObjectRest dSpaceObject = (DSpaceObjectRest) targetDomainObject;
return hasPermission(authentication, dSpaceObject.getId(), dSpaceObject.getType(), permission);
}
}

View File

@@ -0,0 +1,71 @@
/**
* 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.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
/**
* DSpace permission evaluator.
* To check if a user has permission to a target object, a list of permissionEvaluatorPlugins will be checked.
*
* The following list of plugins exists: EPersonRestPermissionEvaluatorPlugin, AdminRestPermissionEvaluatorPlugin,
* AuthorizeServicePermissionEvaluatorPlugin, GroupRestPermissionEvaluatorPlugin
*/
@Component
public class DSpacePermissionEvaluator implements PermissionEvaluator {
@Autowired
private List<RestPermissionEvaluatorPlugin> permissionEvaluatorPluginList;
/**
*
* @param authentication represents the user in question. Should not be null.
* @param targetDomainObject the DSpace object for which permissions should be
* checked. May be null in which case implementations should return false, as the null
* condition can be checked explicitly in the expression.
* @param permission a representation of the DSpace action as supplied by the
* expression system. This corresponds to the DSpace action. Not null.
* @return true if the permission is granted by one of the plugins, false otherwise
*/
public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
for (RestPermissionEvaluatorPlugin permissionEvaluatorPlugin : permissionEvaluatorPluginList) {
if (permissionEvaluatorPlugin.hasPermission(authentication, targetDomainObject, permission)) {
return true;
}
}
return false;
}
/**
* Alternative method for evaluating a permission where only the identifier of the
* target object is available, rather than the target instance itself.
*
* @param authentication represents the user in question. Should not be null.
* @param targetId the UUID for the DSpace object
* @param targetType represents the DSpace object type of the target object. Not null.
* @param permission a representation of the permission object as supplied by the
* expression system. This corresponds to the DSpace action. Not null.
* @return true if the permission is granted by one of the plugins, false otherwise
*/
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType,
Object permission) {
for (RestPermissionEvaluatorPlugin permissionEvaluatorPlugin : permissionEvaluatorPluginList) {
if (permissionEvaluatorPlugin.hasPermission(authentication, targetId, targetType, permission)) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,52 @@
/**
* 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 org.dspace.core.Constants;
/**
* Enum that lists all available "permissions" an authenticated user can have on a specific REST endpoint.
*/
public enum DSpaceRestPermission {
READ(Constants.READ),
WRITE(Constants.WRITE),
DELETE(Constants.DELETE);
private int dspaceApiActionId;
DSpaceRestPermission(int dspaceApiActionId) {
this.dspaceApiActionId = dspaceApiActionId;
}
public int getDspaceApiActionId() {
return dspaceApiActionId;
}
/**
* Convert a given object to a {@link DSpaceRestPermission} if possible.
* @param object The object to convert
* @return A DSpaceRestPersmission value if the conversion succeeded, null otherwise
*/
public static DSpaceRestPermission convert(Object object) {
if (object == null) {
return null;
} else if (object instanceof DSpaceRestPermission) {
return (DSpaceRestPermission) object;
} else if (object instanceof String) {
try {
return DSpaceRestPermission.valueOf((String) object);
} catch (IllegalArgumentException ex) {
return null;
}
} else {
return null;
}
}
}

View File

@@ -8,7 +8,7 @@
package org.dspace.app.rest.security; package org.dspace.app.rest.security;
import static org.dspace.app.rest.security.WebSecurityConfiguration.ADMIN_GRANT; import static org.dspace.app.rest.security.WebSecurityConfiguration.ADMIN_GRANT;
import static org.dspace.app.rest.security.WebSecurityConfiguration.EPERSON_GRANT; import static org.dspace.app.rest.security.WebSecurityConfiguration.AUTHENTICATED_GRANT;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.LinkedList; import java.util.LinkedList;
@@ -150,7 +150,7 @@ public class EPersonRestAuthenticationProvider implements AuthenticationProvider
authorities.add(new SimpleGrantedAuthority(ADMIN_GRANT)); authorities.add(new SimpleGrantedAuthority(ADMIN_GRANT));
} }
authorities.add(new SimpleGrantedAuthority(EPERSON_GRANT)); authorities.add(new SimpleGrantedAuthority(AUTHENTICATED_GRANT));
} }
return authorities; return authorities;

View File

@@ -0,0 +1,73 @@
/**
* 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 java.util.UUID;
import org.dspace.app.rest.utils.ContextUtil;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.service.EPersonService;
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;
/**
* An authenicated user is allowed to view, update or delete his or her own data. This {@link RestPermissionEvaluatorPlugin}
* implemenents that requirement.
*/
@Component
public class EPersonRestPermissionEvaluatorPlugin extends DSpaceObjectPermissionEvaluatorPlugin {
private static final Logger log = LoggerFactory.getLogger(EPersonRestPermissionEvaluatorPlugin.class);
@Autowired
private RequestService requestService;
@Autowired
private EPersonService ePersonService;
@Override
public boolean hasPermission(Authentication authentication, Serializable targetId,
String targetType, Object permission) {
//For now this plugin only evaluates READ access
DSpaceRestPermission restPermission = DSpaceRestPermission.convert(permission);
if (!DSpaceRestPermission.READ.equals(restPermission)
&& !DSpaceRestPermission.WRITE.equals(restPermission)
&& !DSpaceRestPermission.DELETE.equals(restPermission)) {
return false;
}
if (Constants.getTypeID(targetType) != Constants.EPERSON) {
return false;
}
Request request = requestService.getCurrentRequest();
Context context = ContextUtil.obtainContext(request.getServletRequest());
EPerson ePerson = null;
try {
ePerson = ePersonService.findByEmail(context, (String) authentication.getPrincipal());
UUID dsoId = UUID.fromString(targetId.toString());
if (dsoId.equals(ePerson.getID())) {
return true;
}
} catch (SQLException e) {
log.error(e.getMessage(), e);
}
return false;
}
}

View File

@@ -0,0 +1,76 @@
/**
* 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 java.util.UUID;
import org.dspace.app.rest.utils.ContextUtil;
import org.dspace.core.Constants;
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.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;
/**
* An authenticated user is allowed to view information on all the groups he or she is a member of (READ permission).
* This {@link RestPermissionEvaluatorPlugin} implements that requirement by validating the group membership.
*/
@Component
public class GroupRestPermissionEvaluatorPlugin extends DSpaceObjectPermissionEvaluatorPlugin {
private static final Logger log = LoggerFactory.getLogger(GroupRestPermissionEvaluatorPlugin.class);
@Autowired
private RequestService requestService;
@Autowired
private GroupService groupService;
@Autowired
private EPersonService ePersonService;
@Override
public boolean hasPermission(Authentication authentication, Serializable targetId,
String targetType, Object permission) {
//This plugin only evaluates READ access
DSpaceRestPermission restPermission = DSpaceRestPermission.convert(permission);
if (!DSpaceRestPermission.READ.equals(restPermission)
|| Constants.getTypeID(targetType) != Constants.GROUP) {
return false;
}
Request request = requestService.getCurrentRequest();
Context context = ContextUtil.obtainContext(request.getServletRequest());
EPerson ePerson = null;
try {
ePerson = ePersonService.findByEmail(context, (String) authentication.getPrincipal());
UUID dsoId = UUID.fromString(targetId.toString());
Group group = groupService.find(context, dsoId);
if (groupService.isMember(context, ePerson, group)) {
return true;
}
} catch (SQLException e) {
log.error(e.getMessage(), e);
}
return false;
}
}

View File

@@ -0,0 +1,32 @@
/**
* 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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
@Autowired
private PermissionEvaluator dSpacePermissionEvaluator;
@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler =
new DefaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(dSpacePermissionEvaluator);
return expressionHandler;
}
}

View File

@@ -35,4 +35,13 @@ public interface RestAuthenticationService {
void invalidateAuthenticationData(HttpServletRequest request, Context context) throws Exception; void invalidateAuthenticationData(HttpServletRequest request, Context context) throws Exception;
AuthenticationService getAuthenticationService(); AuthenticationService getAuthenticationService();
/**
* Return the value that should be passed in the WWWW-Authenticate header for 4xx responses to the client
* @param request The current client request
* @param response The response being build for the client
* @return A string value that should be set in the WWWW-Authenticate header
*/
String getWwwAuthenticateHeaderValue(HttpServletRequest request, HttpServletResponse response);
} }

View File

@@ -0,0 +1,47 @@
/**
* 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 org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.core.Authentication;
/**
* Interface to define a permission evaluator plugin. These plugins are used in the DSpace {@link PermissionEvaluator}
* implementation {@link DSpacePermissionEvaluator} to check if an authenticated user has permission to perform a
* certain action on a certain object.
*
* If you implement a this interface in a Spring bean, it will be automatically taken into account when evaluating
* permissions.
*/
public interface RestPermissionEvaluatorPlugin {
/**
* Check in the authenticated user (provided by the {@link Authentication} object) has the specified permission on
* the provided target object.
* @param authentication Authentication object providing user details of the authenticated user
* @param targetDomainObject The target object that the authenticated user wants to see or manipulate
* @param permission Permission object that describes the action the user wants to perform on the target object
* @return true if the user is allowed to perform the action described by the permission. False otherwise.
*/
boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission);
/**
* Check in the authenticated user (provided by the {@link Authentication} object) has the specified permission on
* the target object with the provided identifier.
* @param authentication Authentication object providing user details of the authenticated user
* @param targetId Unique identifier of the target object the user wants to view or manipulate
* @param targetType Type of the target object the users wants to view or manipulate
* @param permission Permission object that describes the action the user wants to perform on the target object
* @return true if the user is allowed to perform the action described by the permission. False otherwise.
*/
boolean hasPermission(Authentication authentication, Serializable targetId, String targetType,
Object permission);
}

View File

@@ -9,17 +9,11 @@ package org.dspace.app.rest.security;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator;
import javax.servlet.FilterChain; import javax.servlet.FilterChain;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.dspace.app.rest.utils.ContextUtil;
import org.dspace.authenticate.AuthenticationMethod;
import org.dspace.authenticate.service.AuthenticationService;
import org.dspace.core.Context;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.AuthenticationException;
@@ -77,31 +71,10 @@ public class StatelessLoginFilter extends AbstractAuthenticationProcessingFilter
HttpServletResponse response, AuthenticationException failed) HttpServletResponse response, AuthenticationException failed)
throws IOException, ServletException { throws IOException, ServletException {
AuthenticationService authenticationService = restAuthenticationService.getAuthenticationService(); String authenticateHeaderValue = restAuthenticationService.getWwwAuthenticateHeaderValue(request, response);
Iterator<AuthenticationMethod> authenticationMethodIterator response.setHeader("WWW-Authenticate", authenticateHeaderValue);
= authenticationService.authenticationMethodIterator();
Context context = ContextUtil.obtainContext(request);
StringBuilder wwwAuthenticate = new StringBuilder();
while (authenticationMethodIterator.hasNext()) {
AuthenticationMethod authenticationMethod = authenticationMethodIterator.next();
if (wwwAuthenticate.length() > 0) {
wwwAuthenticate.append(", ");
}
wwwAuthenticate.append(authenticationMethod.getName()).append(" realm=\"DSpace REST API\"");
String loginPageURL = authenticationMethod.loginPageURL(context, request, response);
if (StringUtils.isNotBlank(loginPageURL)) {
// We cannot reply with a 303 code because may browsers handle 3xx response codes transparently. This
// means that the JavaScript client code is not aware of the 303 status and fails to react accordingly.
wwwAuthenticate.append(", location=\"").append(loginPageURL).append("\"");
}
}
response.setHeader("WWW-Authenticate", wwwAuthenticate.toString());
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, failed.getMessage()); response.sendError(HttpServletResponse.SC_UNAUTHORIZED, failed.getMessage());
} }
} }

View File

@@ -15,12 +15,12 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler; import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutFilter; import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@@ -34,10 +34,11 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@EnableWebSecurity @EnableWebSecurity
@Configuration @Configuration
@EnableConfigurationProperties(SecurityProperties.class) @EnableConfigurationProperties(SecurityProperties.class)
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
public static final String ADMIN_GRANT = "ADMIN"; public static final String ADMIN_GRANT = "ADMIN";
public static final String EPERSON_GRANT = "EPERSON"; public static final String AUTHENTICATED_GRANT = "AUTHENTICATED";
public static final String ANONYMOUS_GRANT = "ANONYMOUS"; public static final String ANONYMOUS_GRANT = "ANONYMOUS";
@Autowired @Autowired
@@ -69,9 +70,6 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
http http
//Tell Spring to not create Sessions //Tell Spring to not create Sessions
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
//Return the login URL when having an access denied error
.exceptionHandling().authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/api/authn/login"))
.and()
//Anonymous requests should have the "ANONYMOUS" security grant //Anonymous requests should have the "ANONYMOUS" security grant
.anonymous().authorities(ANONYMOUS_GRANT).and() .anonymous().authorities(ANONYMOUS_GRANT).and()
//Wire up the HttpServletRequest with the current SecurityContext values //Wire up the HttpServletRequest with the current SecurityContext values
@@ -79,6 +77,10 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
//Disable CSRF as our API can be used by clients on an other domain, we are also protected against this, //Disable CSRF as our API can be used by clients on an other domain, we are also protected against this,
// since we pass the token in a header // since we pass the token in a header
.csrf().disable() .csrf().disable()
//Return 401 on authorization failures with a correct WWWW-Authenticate header
.exceptionHandling().authenticationEntryPoint(
new DSpace401AuthenticationEntryPoint(restAuthenticationService))
.and()
//Logout configuration //Logout configuration
.logout() .logout()

View File

@@ -10,6 +10,7 @@ package org.dspace.app.rest.security.jwt;
import java.io.IOException; import java.io.IOException;
import java.sql.SQLException; import java.sql.SQLException;
import java.text.ParseException; import java.text.ParseException;
import java.util.Iterator;
import java.util.List; import java.util.List;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@@ -19,6 +20,7 @@ import org.apache.commons.lang.StringUtils;
import org.dspace.app.rest.security.DSpaceAuthentication; import org.dspace.app.rest.security.DSpaceAuthentication;
import org.dspace.app.rest.security.RestAuthenticationService; import org.dspace.app.rest.security.RestAuthenticationService;
import org.dspace.app.rest.utils.ContextUtil; import org.dspace.app.rest.utils.ContextUtil;
import org.dspace.authenticate.AuthenticationMethod;
import org.dspace.authenticate.service.AuthenticationService; import org.dspace.authenticate.service.AuthenticationService;
import org.dspace.core.Context; import org.dspace.core.Context;
import org.dspace.eperson.EPerson; import org.dspace.eperson.EPerson;
@@ -111,6 +113,33 @@ public class JWTTokenRestAuthenticationServiceImpl implements RestAuthentication
return authenticationService; return authenticationService;
} }
@Override
public String getWwwAuthenticateHeaderValue(final HttpServletRequest request, final HttpServletResponse response) {
Iterator<AuthenticationMethod> authenticationMethodIterator
= authenticationService.authenticationMethodIterator();
Context context = ContextUtil.obtainContext(request);
StringBuilder wwwAuthenticate = new StringBuilder();
while (authenticationMethodIterator.hasNext()) {
AuthenticationMethod authenticationMethod = authenticationMethodIterator.next();
if (wwwAuthenticate.length() > 0) {
wwwAuthenticate.append(", ");
}
wwwAuthenticate.append(authenticationMethod.getName()).append(" realm=\"DSpace REST API\"");
String loginPageURL = authenticationMethod.loginPageURL(context, request, response);
if (org.apache.commons.lang3.StringUtils.isNotBlank(loginPageURL)) {
// We cannot reply with a 303 code because may browsers handle 3xx response codes transparently. This
// means that the JavaScript client code is not aware of the 303 status and fails to react accordingly.
wwwAuthenticate.append(", location=\"").append(loginPageURL).append("\"");
}
}
return wwwAuthenticate.toString();
}
private void addTokenToResponse(final HttpServletResponse response, final String token) throws IOException { private void addTokenToResponse(final HttpServletResponse response, final String token) throws IOException {
response.setHeader(AUTHORIZATION_HEADER, String.format("%s %s", AUTHORIZATION_TYPE, token)); response.setHeader(AUTHORIZATION_HEADER, String.format("%s %s", AUTHORIZATION_TYPE, token));
} }

View File

@@ -21,6 +21,7 @@ import org.dspace.app.rest.repository.LinkRestRepository;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.convert.ConversionException; import org.springframework.core.convert.ConversionException;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.TypeDescriptor;
@@ -60,7 +61,11 @@ public class RestRepositoryUtils {
*/ */
public boolean haveSearchMethods(DSpaceRestRepository repository) { public boolean haveSearchMethods(DSpaceRestRepository repository) {
for (Method method : repository.getClass().getMethods()) { for (Method method : repository.getClass().getMethods()) {
SearchRestMethod ann = method.getAnnotation(SearchRestMethod.class); // We need to use AnnotationUtils because the DSpaceRestRepository is possibly enhanced by a Spring AOP
// proxy. The regular "method.getAnnotation()" method would then search the proxy instead of the
// underlying actual class. The proxy does not inherit the annotations.
SearchRestMethod ann =
AnnotationUtils.findAnnotation(method, SearchRestMethod.class);
if (ann != null) { if (ann != null) {
return true; return true;
} }
@@ -75,7 +80,10 @@ public class RestRepositoryUtils {
public List<String> listSearchMethods(DSpaceRestRepository repository) { public List<String> listSearchMethods(DSpaceRestRepository repository) {
List<String> searchMethods = new LinkedList<String>(); List<String> searchMethods = new LinkedList<String>();
for (Method method : repository.getClass().getMethods()) { for (Method method : repository.getClass().getMethods()) {
SearchRestMethod ann = method.getAnnotation(SearchRestMethod.class); // We need to use AnnotationUtils because the DSpaceRestRepository is possibly enhanced by a Spring AOP
// proxy. The regular "method.getAnnotation()" method would then search the proxy instead of the
// underlying actual class. The proxy does not inherit the annotations.
SearchRestMethod ann = AnnotationUtils.findAnnotation(method, SearchRestMethod.class);
if (ann != null) { if (ann != null) {
String name = ann.name(); String name = ann.name();
if (name.isEmpty()) { if (name.isEmpty()) {
@@ -96,8 +104,15 @@ public class RestRepositoryUtils {
*/ */
public Method getSearchMethod(String searchMethodName, DSpaceRestRepository repository) { public Method getSearchMethod(String searchMethodName, DSpaceRestRepository repository) {
Method searchMethod = null; Method searchMethod = null;
for (Method method : repository.getClass().getMethods()) { // DSpaceRestRepository is possibly enhanced with a Spring AOP proxy. Therefor use ClassUtils to determine
SearchRestMethod ann = method.getAnnotation(SearchRestMethod.class); // the underlying implementation class.
Method[] methods = ClassUtils.getUserClass(repository.getClass()).getMethods();
for (Method method : methods) {
// We need to use AnnotationUtils because the DSpaceRestRepository is possibly enhanced by a Spring AOP
// proxy. The regular "method.getAnnotation()" method would then search the proxy instead of the
// underlying actual class. The proxy does not inherit the annotations.
SearchRestMethod ann =
AnnotationUtils.findAnnotation(method, SearchRestMethod.class);
if (ann != null) { if (ann != null) {
String name = ann.name(); String name = ann.name();
if (name.isEmpty()) { if (name.isEmpty()) {

View File

@@ -88,15 +88,15 @@ public class BitstreamRestRepositoryIT extends AbstractControllerIntegrationTest
.build(); .build();
} }
getClient().perform(get("/api/core/bitstreams/")) String token = getAuthToken(admin.getEmail(), password);
getClient(token).perform(get("/api/core/bitstreams/"))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(content().contentType(contentType)) .andExpect(content().contentType(contentType))
.andExpect(jsonPath("$._embedded.bitstreams", Matchers.containsInAnyOrder( .andExpect(jsonPath("$._embedded.bitstreams", Matchers.containsInAnyOrder(
BitstreamMatcher.matchBitstreamEntry(bitstream), BitstreamMatcher.matchBitstreamEntry(bitstream),
BitstreamMatcher.matchBitstreamEntry(bitstream1) BitstreamMatcher.matchBitstreamEntry(bitstream1)
))) )));
;
} }
@Test @Test
@@ -145,7 +145,9 @@ public class BitstreamRestRepositoryIT extends AbstractControllerIntegrationTest
.build(); .build();
} }
getClient().perform(get("/api/core/bitstreams/") String token = getAuthToken(admin.getEmail(), password);
getClient(token).perform(get("/api/core/bitstreams/")
.param("size", "1")) .param("size", "1"))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(content().contentType(contentType)) .andExpect(content().contentType(contentType))
@@ -160,7 +162,7 @@ public class BitstreamRestRepositoryIT extends AbstractControllerIntegrationTest
; ;
getClient().perform(get("/api/core/bitstreams/") getClient(token).perform(get("/api/core/bitstreams/")
.param("size", "1") .param("size", "1")
.param("page", "1")) .param("page", "1"))
.andExpect(status().isOk()) .andExpect(status().isOk())
@@ -172,9 +174,10 @@ public class BitstreamRestRepositoryIT extends AbstractControllerIntegrationTest
Matchers.contains( Matchers.contains(
BitstreamMatcher.matchBitstreamEntry(bitstream) BitstreamMatcher.matchBitstreamEntry(bitstream)
) )
))) )));
; getClient().perform(get("/api/core/bitstreams/"))
.andExpect(status().isUnauthorized());
} }
//TODO Re-enable test after https://jira.duraspace.org/browse/DS-3774 is fixed //TODO Re-enable test after https://jira.duraspace.org/browse/DS-3774 is fixed
@@ -413,7 +416,9 @@ public class BitstreamRestRepositoryIT extends AbstractControllerIntegrationTest
.build(); .build();
Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build();
getClient().perform(get("/api/core/bitstreams/" + UUID.randomUUID())) String token = getAuthToken(admin.getEmail(), password);
getClient(token).perform(get("/api/core/bitstreams/" + UUID.randomUUID()))
.andExpect(status().isNotFound()) .andExpect(status().isNotFound())
; ;
@@ -462,7 +467,7 @@ public class BitstreamRestRepositoryIT extends AbstractControllerIntegrationTest
.andExpect(status().is(204)); .andExpect(status().is(204));
// Verify 404 after delete // Verify 404 after delete
getClient().perform(get("/api/core/bitstreams/" + bitstream.getID())) getClient(token).perform(get("/api/core/bitstreams/" + bitstream.getID()))
.andExpect(status().isNotFound()); .andExpect(status().isNotFound());
} }

View File

@@ -322,7 +322,7 @@ public class BrowsesResourceControllerIT extends AbstractControllerIntegrationTe
.withIssueDate("2015-03-12") .withIssueDate("2015-03-12")
.withAuthor("Duck, Donald") .withAuthor("Duck, Donald")
.withSubject("Cartoons").withSubject("Ducks") .withSubject("Cartoons").withSubject("Ducks")
.makePrivate() .makeUnDiscoverable()
.build(); .build();
//4. An item with an item-level embargo //4. An item with an item-level embargo

View File

@@ -1735,7 +1735,7 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest
.withIssueDate("1990-02-13") .withIssueDate("1990-02-13")
.withAuthor("Smith, Maria").withAuthor("Doe, Jane").withAuthor("Testing, Works") .withAuthor("Smith, Maria").withAuthor("Doe, Jane").withAuthor("Testing, Works")
.withSubject("TestingForMore").withSubject("ExtraEntry") .withSubject("TestingForMore").withSubject("ExtraEntry")
.makePrivate() .makeUnDiscoverable()
.build(); .build();
Item publicItem3 = ItemBuilder.createItem(context, col2) Item publicItem3 = ItemBuilder.createItem(context, col2)
@@ -1987,7 +1987,7 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest
.withAuthor("test2, test2").withAuthor("Maybe, Maybe") .withAuthor("test2, test2").withAuthor("Maybe, Maybe")
.withSubject("AnotherTest").withSubject("TestingForMore") .withSubject("AnotherTest").withSubject("TestingForMore")
.withSubject("ExtraEntry") .withSubject("ExtraEntry")
.makePrivate() .makeUnDiscoverable()
.build(); .build();
UUID scope = col2.getID(); UUID scope = col2.getID();
@@ -2142,7 +2142,7 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest
.withIssueDate("2010-02-13") .withIssueDate("2010-02-13")
.withAuthor("Smith, Maria").withAuthor("Doe, Jane") .withAuthor("Smith, Maria").withAuthor("Doe, Jane")
.withSubject("AnotherTest").withSubject("ExtraEntry") .withSubject("AnotherTest").withSubject("ExtraEntry")
.makePrivate() .makeUnDiscoverable()
.build(); .build();

View File

@@ -95,12 +95,16 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
.andExpect(content().contentType(contentType)) .andExpect(content().contentType(contentType))
.andExpect(jsonPath("$._embedded.epersons", Matchers.containsInAnyOrder( .andExpect(jsonPath("$._embedded.epersons", Matchers.containsInAnyOrder(
EPersonMatcher.matchEPersonEntry(newUser), EPersonMatcher.matchEPersonEntry(newUser),
EPersonMatcher.matchDefaultTestEPerson(), EPersonMatcher.matchEPersonOnEmail(admin.getEmail()),
EPersonMatcher.matchDefaultTestEPerson() EPersonMatcher.matchEPersonOnEmail(eperson.getEmail())
))) )))
.andExpect(jsonPath("$.page.size", is(20))) .andExpect(jsonPath("$.page.size", is(20)))
.andExpect(jsonPath("$.page.totalElements", is(3))) .andExpect(jsonPath("$.page.totalElements", is(3)))
; ;
getClient().perform(get("/api/eperson/epersons"))
.andExpect(status().isUnauthorized())
;
} }
@Test @Test
@@ -134,24 +138,24 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
public void findAllPaginationTest() throws Exception { public void findAllPaginationTest() throws Exception {
context.turnOffAuthorisationSystem(); context.turnOffAuthorisationSystem();
EPerson ePerson = EPersonBuilder.createEPerson(context) EPerson testEPerson = EPersonBuilder.createEPerson(context)
.withNameInMetadata("John", "Doe") .withNameInMetadata("John", "Doe")
.withEmail("Johndoe@fake-email.com") .withEmail("Johndoe@fake-email.com")
.build(); .build();
String authToken = getAuthToken(admin.getEmail(), password); String authToken = getAuthToken(admin.getEmail(), password);
// using size = 2 the first page will contains our test user and admin // using size = 2 the first page will contains our test user and admin
getClient(authToken).perform(get("/api/eperson/eperson") getClient(authToken).perform(get("/api/eperson/epersons")
.param("size", "2")) .param("size", "2"))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(content().contentType(contentType)) .andExpect(content().contentType(contentType))
.andExpect(jsonPath("$._embedded.epersons", Matchers.containsInAnyOrder( .andExpect(jsonPath("$._embedded.epersons", Matchers.containsInAnyOrder(
EPersonMatcher.matchDefaultTestEPerson(), EPersonMatcher.matchEPersonEntry(admin),
EPersonMatcher.matchDefaultTestEPerson() EPersonMatcher.matchEPersonEntry(testEPerson)
))) )))
.andExpect(jsonPath("$._embedded.epersons", Matchers.not( .andExpect(jsonPath("$._embedded.epersons", Matchers.not(
Matchers.contains( Matchers.contains(
EPersonMatcher.matchEPersonEntry(ePerson) EPersonMatcher.matchEPersonEntry(admin)
) )
))) )))
.andExpect(jsonPath("$.page.size", is(2))) .andExpect(jsonPath("$.page.size", is(2)))
@@ -159,17 +163,22 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
; ;
// using size = 2 the first page will contains our test user and admin // using size = 2 the first page will contains our test user and admin
getClient(authToken).perform(get("/api/eperson/eperson") getClient(authToken).perform(get("/api/eperson/epersons")
.param("size", "2") .param("size", "2")
.param("page", "1")) .param("page", "1"))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(content().contentType(contentType)) .andExpect(content().contentType(contentType))
.andExpect(jsonPath("$._embedded.epersons", Matchers.contains( .andExpect(jsonPath("$._embedded.epersons", Matchers.contains(
EPersonMatcher.matchEPersonEntry(ePerson) EPersonMatcher.matchEPersonEntry(eperson)
))) )))
.andExpect(jsonPath("$._embedded.epersons", Matchers.hasSize(1)))
.andExpect(jsonPath("$.page.size", is(2))) .andExpect(jsonPath("$.page.size", is(2)))
.andExpect(jsonPath("$.page.totalElements", is(3))) .andExpect(jsonPath("$.page.totalElements", is(3)))
; ;
getClient().perform(get("/api/eperson/epersons"))
.andExpect(status().isUnauthorized())
;
} }
@@ -203,7 +212,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
} }
@Test @Test
public void findOneRelsTest() throws Exception { public void readEpersonAuthorizationTest() throws Exception {
context.turnOffAuthorisationSystem(); context.turnOffAuthorisationSystem();
EPerson ePerson = EPersonBuilder.createEPerson(context) EPerson ePerson = EPersonBuilder.createEPerson(context)
@@ -225,24 +234,41 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
))) )))
.andExpect(jsonPath("$", Matchers.not( .andExpect(jsonPath("$", Matchers.not(
is( is(
EPersonMatcher.matchEPersonEntry(ePerson) EPersonMatcher.matchEPersonEntry(eperson)
) )
))) )))
.andExpect(jsonPath("$._links.self.href", .andExpect(jsonPath("$._links.self.href",
Matchers.containsString("/api/eperson/epersons/" + ePerson2.getID()))); Matchers.containsString("/api/eperson/epersons/" + ePerson2.getID())));
}
//EPerson can only access himself
String epersonToken = getAuthToken(eperson.getEmail(), password);
getClient(epersonToken).perform(get("/api/eperson/epersons/" + ePerson2.getID()))
.andExpect(status().isForbidden());
getClient(epersonToken).perform(get("/api/eperson/epersons/" + eperson.getID()))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$", is(
EPersonMatcher.matchEPersonOnEmail(eperson.getEmail())
)))
.andExpect(jsonPath("$._links.self.href",
Matchers.containsString("/api/eperson/epersons/" + eperson.getID())));
}
@Test @Test
public void findOneTestWrongUUID() throws Exception { public void findOneTestWrongUUID() throws Exception {
context.turnOffAuthorisationSystem(); context.turnOffAuthorisationSystem();
EPerson ePerson = EPersonBuilder.createEPerson(context) EPerson testEPerson1 = EPersonBuilder.createEPerson(context)
.withNameInMetadata("John", "Doe") .withNameInMetadata("John", "Doe")
.withEmail("Johndoe@fake-email.com") .withEmail("Johndoe@fake-email.com")
.build(); .build();
EPerson ePerson2 = EPersonBuilder.createEPerson(context) EPerson testEPerson2 = EPersonBuilder.createEPerson(context)
.withNameInMetadata("Jane", "Smith") .withNameInMetadata("Jane", "Smith")
.withEmail("janesmith@fake-email.com") .withEmail("janesmith@fake-email.com")
.build(); .build();

View File

@@ -19,23 +19,25 @@ public class EmptyRestRepositoryIT extends AbstractControllerIntegrationTest {
@Test @Test
public void findAllTest() throws Exception { public void findAllTest() throws Exception {
String token = getAuthToken(admin.getEmail(), password);
//Test retrieval of all communities while none exist //Test retrieval of all communities while none exist
getClient().perform(get("/api/core/communities")) getClient(token).perform(get("/api/core/communities"))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$.page.totalElements", is(0))); .andExpect(jsonPath("$.page.totalElements", is(0)));
//Test retrieval of all collections while none exist //Test retrieval of all collections while none exist
getClient().perform(get("/api/core/collections")) getClient(token).perform(get("/api/core/collections"))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$.page.totalElements", is(0))); .andExpect(jsonPath("$.page.totalElements", is(0)));
//Test retrieval of all items while none exist //Test retrieval of all items while none exist
getClient().perform(get("/api/core/items")) getClient(token).perform(get("/api/core/items"))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$.page.totalElements", is(0))); .andExpect(jsonPath("$.page.totalElements", is(0)));
//Test retrieval of all bitstreams while none exist //Test retrieval of all bitstreams while none exist
getClient().perform(get("/api/core/bitstreams")) getClient(token).perform(get("/api/core/bitstreams"))
. andExpect(status().isOk()) . andExpect(status().isOk())
.andExpect(jsonPath("$.page.totalElements", is(0))); .andExpect(jsonPath("$.page.totalElements", is(0)));
} }

View File

@@ -31,8 +31,16 @@ public class GroupRestRepositoryIT extends AbstractControllerIntegrationTest {
@Test @Test
public void findAllTest() throws Exception { public void findAllTest() throws Exception {
//When we call the root endpoint
getClient().perform(get("/api/eperson/groups")) getClient().perform(get("/api/eperson/groups"))
//The status has to be 403 Not Authorized
.andExpect(status().isUnauthorized());
String token = getAuthToken(admin.getEmail(), password);
//When we call the root endpoint
getClient(token).perform(get("/api/eperson/groups"))
//The status has to be 200 OK //The status has to be 200 OK
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(content().contentType(contentType)) .andExpect(content().contentType(contentType))
@@ -48,8 +56,16 @@ public class GroupRestRepositoryIT extends AbstractControllerIntegrationTest {
@Test @Test
public void findAllPaginationTest() throws Exception { public void findAllPaginationTest() throws Exception {
context.turnOffAuthorisationSystem();
getClient().perform(get("/api/eperson/groups")) getClient().perform(get("/api/eperson/groups"))
//The status has to be 403 Not Authorized
.andExpect(status().isUnauthorized());
String token = getAuthToken(admin.getEmail(), password);
//When we call the root endpoint
getClient(token).perform(get("/api/eperson/groups"))
//The status has to be 200 OK //The status has to be 200 OK
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(content().contentType(contentType)) .andExpect(content().contentType(contentType))
@@ -69,9 +85,11 @@ public class GroupRestRepositoryIT extends AbstractControllerIntegrationTest {
.withName(testGroupName) .withName(testGroupName)
.build(); .build();
String token = getAuthToken(admin.getEmail(), password);
String generatedGroupId = group.getID().toString(); String generatedGroupId = group.getID().toString();
String groupIdCall = "/api/eperson/groups/" + generatedGroupId; String groupIdCall = "/api/eperson/groups/" + generatedGroupId;
getClient().perform(get(groupIdCall)) getClient(token).perform(get(groupIdCall))
//The status has to be 200 OK //The status has to be 200 OK
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(content().contentType(contentType)) .andExpect(content().contentType(contentType))
@@ -79,7 +97,7 @@ public class GroupRestRepositoryIT extends AbstractControllerIntegrationTest {
GroupMatcher.matchGroupEntry(group.getID(), group.getName()) GroupMatcher.matchGroupEntry(group.getID(), group.getName())
))) )))
; ;
getClient().perform(get("/api/eperson/groups")) getClient(token).perform(get("/api/eperson/groups"))
//The status has to be 200 OK //The status has to be 200 OK
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(content().contentType(contentType)) .andExpect(content().contentType(contentType))
@@ -89,7 +107,7 @@ public class GroupRestRepositoryIT extends AbstractControllerIntegrationTest {
} }
@Test @Test
public void findOneRelsTest() throws Exception { public void readGroupAuthorizationTest() throws Exception {
context.turnOffAuthorisationSystem(); context.turnOffAuthorisationSystem();
Group group = GroupBuilder.createGroup(context) Group group = GroupBuilder.createGroup(context)
@@ -98,21 +116,42 @@ public class GroupRestRepositoryIT extends AbstractControllerIntegrationTest {
Group group2 = GroupBuilder.createGroup(context) Group group2 = GroupBuilder.createGroup(context)
.withName("Group2") .withName("Group2")
.addMember(eperson)
.build(); .build();
getClient().perform(get("/api/eperson/groups/" + group2.getID())) //Admin can access
.andExpect(status().isOk()) String token = getAuthToken(admin.getEmail(), password);
.andExpect(content().contentType(contentType)) getClient(token).perform(get("/api/eperson/groups/" + group2.getID()))
.andExpect(jsonPath("$", Matchers.is( .andExpect(status().isOk())
GroupMatcher.matchGroupEntry(group2.getID(), group2.getName()) .andExpect(content().contentType(contentType))
))) .andExpect(jsonPath("$", Matchers.is(
.andExpect(jsonPath("$", Matchers.not( GroupMatcher.matchGroupEntry(group2.getID(), group2.getName())
Matchers.is( )))
GroupMatcher.matchGroupEntry(group.getID(), group.getName()) .andExpect(jsonPath("$", Matchers.not(
) Matchers.is(
))) GroupMatcher.matchGroupEntry(group.getID(), group.getName())
.andExpect(jsonPath("$._links.self.href", )
Matchers.containsString("/api/eperson/groups/" + group2.getID()))); )))
.andExpect(jsonPath("$._links.self.href",
Matchers.containsString("/api/eperson/groups/" + group2.getID())));
//People in group should be able to access token
token = getAuthToken(eperson.getEmail(), password);
getClient(token).perform(get("/api/eperson/groups/" + group2.getID()))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$", Matchers.is(
GroupMatcher.matchGroupEntry(group2.getID(), group2.getName())
)))
.andExpect(jsonPath("$", Matchers.not(
Matchers.is(
GroupMatcher.matchGroupEntry(group.getID(), group.getName())
)
)))
.andExpect(jsonPath("$._links.self.href",
Matchers.containsString("/api/eperson/groups/" + group2.getID())));
} }
@Test @Test

View File

@@ -26,6 +26,8 @@ import org.apache.commons.lang3.CharEncoding;
import org.dspace.app.rest.builder.BitstreamBuilder; import org.dspace.app.rest.builder.BitstreamBuilder;
import org.dspace.app.rest.builder.CollectionBuilder; import org.dspace.app.rest.builder.CollectionBuilder;
import org.dspace.app.rest.builder.CommunityBuilder; 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.builder.ItemBuilder;
import org.dspace.app.rest.builder.WorkspaceItemBuilder; import org.dspace.app.rest.builder.WorkspaceItemBuilder;
import org.dspace.app.rest.matcher.ItemMatcher; import org.dspace.app.rest.matcher.ItemMatcher;
@@ -37,6 +39,8 @@ import org.dspace.content.Collection;
import org.dspace.content.Community; import org.dspace.content.Community;
import org.dspace.content.Item; import org.dspace.content.Item;
import org.dspace.content.WorkspaceItem; import org.dspace.content.WorkspaceItem;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.junit.Test; import org.junit.Test;
@@ -80,7 +84,9 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest {
.withSubject("ExtraEntry") .withSubject("ExtraEntry")
.build(); .build();
getClient().perform(get("/api/core/items")) String token = getAuthToken(admin.getEmail(), password);
getClient(token).perform(get("/api/core/items"))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.items", Matchers.containsInAnyOrder( .andExpect(jsonPath("$._embedded.items", Matchers.containsInAnyOrder(
ItemMatcher.matchItemWithTitleAndDateIssued(publicItem1, "Public item 1", "2017-10-17"), ItemMatcher.matchItemWithTitleAndDateIssued(publicItem1, "Public item 1", "2017-10-17"),
@@ -131,7 +137,9 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest {
.withSubject("ExtraEntry") .withSubject("ExtraEntry")
.build(); .build();
getClient().perform(get("/api/core/items") String token = getAuthToken(admin.getEmail(), password);
getClient(token).perform(get("/api/core/items")
.param("size", "2")) .param("size", "2"))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.items", Matchers.containsInAnyOrder( .andExpect(jsonPath("$._embedded.items", Matchers.containsInAnyOrder(
@@ -146,7 +154,7 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest {
.andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/core/items"))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/core/items")))
; ;
getClient().perform(get("/api/core/items") getClient(token).perform(get("/api/core/items")
.param("size", "2") .param("size", "2")
.param("page", "1")) .param("page", "1"))
.andExpect(status().isOk()) .andExpect(status().isOk())
@@ -312,7 +320,9 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest {
Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build();
Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build();
getClient().perform(get("/api/core/items/" + UUID.randomUUID())) String token = getAuthToken(admin.getEmail(), password);
getClient(token).perform(get("/api/core/items/" + UUID.randomUUID()))
.andExpect(status().isNotFound()) .andExpect(status().isNotFound())
; ;
@@ -714,7 +724,7 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest {
.withIssueDate("2017-10-17") .withIssueDate("2017-10-17")
.withAuthor("Smith, Donald").withAuthor("Doe, John") .withAuthor("Smith, Donald").withAuthor("Doe, John")
.withSubject("ExtraEntry") .withSubject("ExtraEntry")
.makePrivate() .makeUnDiscoverable()
.build(); .build();
String token = getAuthToken(admin.getEmail(), password); String token = getAuthToken(admin.getEmail(), password);
@@ -767,7 +777,7 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest {
.withIssueDate("2017-10-17") .withIssueDate("2017-10-17")
.withAuthor("Smith, Donald").withAuthor("Doe, John") .withAuthor("Smith, Donald").withAuthor("Doe, John")
.withSubject("ExtraEntry") .withSubject("ExtraEntry")
.makePrivate() .makeUnDiscoverable()
.build(); .build();
String token = getAuthToken(admin.getEmail(), password); String token = getAuthToken(admin.getEmail(), password);
@@ -810,7 +820,7 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest {
.withIssueDate("2017-10-17") .withIssueDate("2017-10-17")
.withAuthor("Smith, Donald").withAuthor("Doe, John") .withAuthor("Smith, Donald").withAuthor("Doe, John")
.withSubject("ExtraEntry") .withSubject("ExtraEntry")
.makePrivate() .makeUnDiscoverable()
.build(); .build();
String token = getAuthToken(eperson.getEmail(), password); String token = getAuthToken(eperson.getEmail(), password);
@@ -835,7 +845,7 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest {
} }
@Test @Test
public void makePrivatePatchTest() throws Exception { public void makeUnDiscoverablePatchTest() throws Exception {
context.turnOffAuthorisationSystem(); context.turnOffAuthorisationSystem();
//** GIVEN ** //** GIVEN **
@@ -880,7 +890,7 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest {
} }
@Test @Test
public void makePrivatePatchUnauthorizedTest() throws Exception { public void makeUnDiscoverablePatchUnauthorizedTest() throws Exception {
context.turnOffAuthorisationSystem(); context.turnOffAuthorisationSystem();
//** GIVEN ** //** GIVEN **
@@ -922,7 +932,8 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest {
} }
public void makePrivatePatchForbiddenTest() throws Exception { @Test
public void makeUnDiscoverablePatchForbiddenTest() throws Exception {
context.turnOffAuthorisationSystem(); context.turnOffAuthorisationSystem();
//** GIVEN ** //** GIVEN **
@@ -994,7 +1005,7 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest {
.withIssueDate("2017-10-17") .withIssueDate("2017-10-17")
.withAuthor("Smith, Donald").withAuthor("Doe, John") .withAuthor("Smith, Donald").withAuthor("Doe, John")
.withSubject("ExtraEntry") .withSubject("ExtraEntry")
.makePrivate() .makeUnDiscoverable()
.build(); .build();
String token = getAuthToken(admin.getEmail(), password); String token = getAuthToken(admin.getEmail(), password);
@@ -1089,6 +1100,7 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest {
.andExpect(status().is(404)); .andExpect(status().is(404));
} }
@Test
public void deleteOneTemplateTest() throws Exception { public void deleteOneTemplateTest() throws Exception {
context.turnOffAuthorisationSystem(); context.turnOffAuthorisationSystem();
@@ -1113,10 +1125,11 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest {
.andExpect(status().is(422)); .andExpect(status().is(422));
//Check templateItem is available after failed deletion //Check templateItem is available after failed deletion
getClient().perform(get("/api/core/items/" + templateItem.getID())) getClient(token).perform(get("/api/core/items/" + templateItem.getID()))
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
@Test
public void deleteOneWorkspaceTest() throws Exception { public void deleteOneWorkspaceTest() throws Exception {
context.turnOffAuthorisationSystem(); context.turnOffAuthorisationSystem();
@@ -1138,8 +1151,198 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest {
getClient(token).perform(delete("/api/core/items/" + workspaceItem.getItem().getID())) getClient(token).perform(delete("/api/core/items/" + workspaceItem.getItem().getID()))
.andExpect(status().is(422)); .andExpect(status().is(422));
//Check templateItem is available after failed deletion //Check workspaceItem is available after failed deletion
getClient().perform(get("/api/core/items/" + workspaceItem.getID())) getClient(token).perform(get("/api/core/items/" + workspaceItem.getItem().getID()))
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
@Test
public void embargoAccessTest() throws Exception {
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();
//2. An embargoed item
Item embargoedItem1 = ItemBuilder.createItem(context, col1)
.withTitle("embargoed item 1")
.withIssueDate("2017-12-18")
.withAuthor("Smith, Donald").withAuthor("Doe, John")
.withSubject("ExtraEntry")
.withEmbargoPeriod("6 months")
.build();
//3. a public item
Item publicItem1 = ItemBuilder.createItem(context, col1)
.withTitle("Public item 1")
.withIssueDate("2017-10-17")
.withAuthor("Smith, Donald").withAuthor("Doe, John")
.withSubject("ExtraEntry")
.build();
context.restoreAuthSystemState();
//** THEN **
//An anonymous user can view public items
getClient().perform(get("/api/core/items/" + publicItem1.getID()))
.andExpect(status().isOk())
.andExpect(jsonPath("$", Matchers.is(
ItemMatcher.matchItemWithTitleAndDateIssued(
publicItem1, "Public item 1", "2017-10-17")
)))
.andExpect(jsonPath("$._links.self.href",
Matchers.containsString("/api/core/items")));
//An anonymous user is not allowed to view embargoed items
getClient().perform(get("/api/core/items/" + embargoedItem1.getID()))
.andExpect(status().isUnauthorized());
//An admin user is allowed to access the embargoed item
String token1 = getAuthToken(admin.getEmail(), password);
getClient(token1).perform(get("/api/core/items/" + embargoedItem1.getID()))
.andExpect(status().isOk())
.andExpect(jsonPath("$", Matchers.is(
ItemMatcher.matchItemWithTitleAndDateIssued(
embargoedItem1, "embargoed item 1", "2017-12-18")
)))
.andExpect(jsonPath("$._links.self.href",
Matchers.containsString("/api/core/items")));
}
@Test
public void undiscoverableAccessTest() throws Exception {
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();
//2. An undiscoverable item
Item unDiscoverableYetAccessibleItem1 = ItemBuilder.createItem(context, col1)
.withTitle("Undiscoverable item 1")
.withIssueDate("2017-10-17")
.withAuthor("Smith, Donald").withAuthor("Doe, John")
.withSubject("ExtraEntry")
.makeUnDiscoverable()
.build();
context.restoreAuthSystemState();
//Anonymous users are allowed to access undiscoverable items
getClient().perform(get("/api/core/items/" + unDiscoverableYetAccessibleItem1.getID()))
.andExpect(status().isOk())
.andExpect(jsonPath("$", Matchers.is(
ItemMatcher.matchItemWithTitleAndDateIssued(unDiscoverableYetAccessibleItem1,
"Undiscoverable item 1", "2017-10-17")
)));
//Admin users are allowed to acceess undiscoverable items
String token1 = getAuthToken(admin.getEmail(), password);
getClient(token1).perform(get("/api/core/items/" + unDiscoverableYetAccessibleItem1.getID()))
.andExpect(status().isOk())
.andExpect(jsonPath("$", Matchers.is(
ItemMatcher.matchItemWithTitleAndDateIssued(unDiscoverableYetAccessibleItem1,
"Undiscoverable item 1", "2017-10-17")
)));
}
@Test
public void privateGroupAccessTest() throws Exception {
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();
//2. An item restricted to a specific internal group
Group staffGroup = GroupBuilder.createGroup(context)
.withName("Staff")
.build();
Item restrictedItem1 = ItemBuilder.createItem(context, col1)
.withTitle("Restricted item 1")
.withIssueDate("2017-12-18")
.withReaderGroup(staffGroup)
.build();
//3. A public item
Item publicItem1 = ItemBuilder.createItem(context, col1)
.withTitle("Public item 1")
.withIssueDate("2017-10-17")
.withAuthor("Smith, Donald").withAuthor("Doe, John")
.withSubject("ExtraEntry")
.build();
//4. A member of the internal group
EPerson staffEPerson = EPersonBuilder.createEPerson(context)
.withEmail("professor@myuni.edu")
.withPassword("s3cr3t")
.withNameInMetadata("Doctor", "Professor")
.withGroupMembership(staffGroup)
.build();
context.restoreAuthSystemState();
//** THEN **
//An anonymous user can view the public item
getClient().perform(get("/api/core/items/" + publicItem1.getID()))
.andExpect(status().isOk())
.andExpect(jsonPath("$", Matchers.is(
ItemMatcher.matchItemWithTitleAndDateIssued(
publicItem1, "Public item 1", "2017-10-17")
)))
.andExpect(jsonPath("$._links.self.href",
Matchers.containsString("/api/core/items")));
//An anonymous user is not allowed to the restricted item
getClient().perform(get("/api/core/items/" + restrictedItem1.getID()))
.andExpect(status().isUnauthorized());
//An admin user is allowed to access the restricted item
String token1 = getAuthToken(admin.getEmail(), password);
getClient(token1).perform(get("/api/core/items/" + restrictedItem1.getID()))
.andExpect(status().isOk())
.andExpect(jsonPath("$", Matchers.is(
ItemMatcher.matchItemWithTitleAndDateIssued(
restrictedItem1, "Restricted item 1", "2017-12-18")
)))
.andExpect(jsonPath("$._links.self.href",
Matchers.containsString("/api/core/items")));
//A member of the internal group is also allowed to access the restricted item
String token2 = getAuthToken("professor@myuni.edu", "s3cr3t");
getClient(token2).perform(get("/api/core/items/" + restrictedItem1.getID()))
.andExpect(status().isOk())
.andExpect(jsonPath("$", Matchers.is(
ItemMatcher.matchItemWithTitleAndDateIssued(
restrictedItem1, "Restricted item 1", "2017-12-18")
)))
.andExpect(jsonPath("$._links.self.href",
Matchers.containsString("/api/core/items")));
}
} }

View File

@@ -34,8 +34,16 @@ public class SubmissionDefinitionsControllerIT extends AbstractControllerIntegra
@Test @Test
public void findAll() throws Exception { public void findAll() throws Exception {
//When we call the root endpoint //When we call the root endpoint as anonymous user
getClient().perform(get("/api/config/submissiondefinitions")) getClient().perform(get("/api/config/submissiondefinitions"))
//The status has to be 403 Not Authorized
.andExpect(status().isUnauthorized());
String token = getAuthToken(admin.getEmail(), password);
getClient(token).perform(get("/api/config/submissiondefinitions"))
//The status has to be 200 OK //The status has to be 200 OK
.andExpect(status().isOk()) .andExpect(status().isOk())
//We expect the content type to be "application/hal+json;charset=UTF-8" //We expect the content type to be "application/hal+json;charset=UTF-8"
@@ -56,7 +64,14 @@ public class SubmissionDefinitionsControllerIT extends AbstractControllerIntegra
@Test @Test
public void findDefault() throws Exception { public void findDefault() throws Exception {
getClient().perform(get("/api/config/submissiondefinitions/traditional")) getClient().perform(get("/api/config/submissiondefinitions/traditional"))
//The status has to be 403 Not Authorized
.andExpect(status().isUnauthorized());
String token = getAuthToken(admin.getEmail(), password);
getClient(token).perform(get("/api/config/submissiondefinitions/traditional"))
//The status has to be 200 OK //The status has to be 200 OK
.andExpect(status().isOk()) .andExpect(status().isOk())
//We expect the content type to be "application/hal+json;charset=UTF-8" //We expect the content type to be "application/hal+json;charset=UTF-8"
@@ -80,6 +95,16 @@ public class SubmissionDefinitionsControllerIT extends AbstractControllerIntegra
Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build();
getClient().perform(get("/api/config/submissiondefinitions/search/findByCollection") getClient().perform(get("/api/config/submissiondefinitions/search/findByCollection")
.param("uuid", col1.getID().toString()))
//** THEN **
//The status has to be 200
.andExpect(status().isUnauthorized());
String token = getAuthToken(admin.getEmail(), password);
getClient(token).perform(get("/api/config/submissiondefinitions/search/findByCollection")
.param("uuid", col1.getID().toString())) .param("uuid", col1.getID().toString()))
//** THEN ** //** THEN **
//The status has to be 200 //The status has to be 200
@@ -93,8 +118,17 @@ public class SubmissionDefinitionsControllerIT extends AbstractControllerIntegra
@Test @Test
public void findCollections() throws Exception { public void findCollections() throws Exception {
//Match only that a section exists with a submission configuration behind //Match only that a section exists with a submission configuration behind
getClient().perform(get("/api/config/submissiondefinitions/traditional/collections")) getClient().perform(get("/api/config/submissiondefinitions/traditional/collections"))
//The status has to be 403 Not Authorized
.andExpect(status().isUnauthorized());
String token = getAuthToken(admin.getEmail(), password);
//Match only that a section exists with a submission configuration behind
getClient(token).perform(get("/api/config/submissiondefinitions/traditional/collections"))
//TODO - this method should return an empty page //TODO - this method should return an empty page
.andExpect(status().isNoContent()); .andExpect(status().isNoContent());
//this is the expected result //this is the expected result
@@ -104,7 +138,14 @@ public class SubmissionDefinitionsControllerIT extends AbstractControllerIntegra
@Test @Test
public void findSections() throws Exception { public void findSections() throws Exception {
getClient().perform(get("/api/config/submissiondefinitions/traditional/sections")) getClient().perform(get("/api/config/submissiondefinitions/traditional/sections"))
//The status has to be 403 Not Authorized
.andExpect(status().isUnauthorized());
String token = getAuthToken(admin.getEmail(), password);
getClient(token).perform(get("/api/config/submissiondefinitions/traditional/sections"))
// The status has to be 200 OK // The status has to be 200 OK
.andExpect(status().isOk()) .andExpect(status().isOk())
// We expect the content type to be "application/hal+json;charset=UTF-8" // We expect the content type to be "application/hal+json;charset=UTF-8"

View File

@@ -28,8 +28,16 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
@Test @Test
public void findAll() throws Exception { public void findAll() throws Exception {
//When we call the root endpoint //When we call the root endpoint as anonymous user
getClient().perform(get("/api/config/submissionforms")) getClient().perform(get("/api/config/submissionforms"))
//The status has to be 403 Not Authorized
.andExpect(status().isUnauthorized());
String token = getAuthToken(admin.getEmail(), password);
//When we call the root endpoint
getClient(token).perform(get("/api/config/submissionforms"))
//The status has to be 200 OK //The status has to be 200 OK
.andExpect(status().isOk()) .andExpect(status().isOk())
//We expect the content type to be "application/hal+json;charset=UTF-8" //We expect the content type to be "application/hal+json;charset=UTF-8"
@@ -51,7 +59,15 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
@Test @Test
public void findTraditionalPageOne() throws Exception { public void findTraditionalPageOne() throws Exception {
//When we call the root endpoint as anonymous user
getClient().perform(get("/api/config/submissionforms/traditionalpageone")) getClient().perform(get("/api/config/submissionforms/traditionalpageone"))
//The status has to be 403 Not Authorized
.andExpect(status().isUnauthorized());
String token = getAuthToken(admin.getEmail(), password);
getClient(token).perform(get("/api/config/submissionforms/traditionalpageone"))
//The status has to be 200 OK //The status has to be 200 OK
.andExpect(status().isOk()) .andExpect(status().isOk())
//We expect the content type to be "application/hal+json;charset=UTF-8" //We expect the content type to be "application/hal+json;charset=UTF-8"

View File

@@ -27,8 +27,16 @@ public class SubmissionSectionsControllerIT extends AbstractControllerIntegratio
@Test @Test
public void findAll() throws Exception { public void findAll() throws Exception {
//When we call the root endpoint //When we call the root endpoint as anonymous user
getClient().perform(get("/api/config/submissionsections")) getClient().perform(get("/api/config/submissionsections"))
//The status has to be 403 Not Authorized
.andExpect(status().isUnauthorized());
String token = getAuthToken(admin.getEmail(), password);
//When we call the root endpoint
getClient(token).perform(get("/api/config/submissionsections"))
//The status has to be 200 OK //The status has to be 200 OK
.andExpect(status().isOk()) .andExpect(status().isOk())
//We expect the content type to be "application/hal+json;charset=UTF-8" //We expect the content type to be "application/hal+json;charset=UTF-8"

View File

@@ -27,8 +27,16 @@ public class SubmissionUploadsControllerIT extends AbstractControllerIntegration
@Test @Test
public void findAll() throws Exception { public void findAll() throws Exception {
//When we call the root endpoint //When we call the root endpoint as anonymous user
getClient().perform(get("/api/config/submissionuploads")) getClient().perform(get("/api/config/submissionuploads"))
//The status has to be 403 Not Authorized
.andExpect(status().isUnauthorized());
String token = getAuthToken(admin.getEmail(), password);
//When we call the root endpoint
getClient(token).perform(get("/api/config/submissionuploads"))
//The status has to be 200 OK //The status has to be 200 OK
.andExpect(status().isOk()) .andExpect(status().isOk())
//We expect the content type to be "application/hal+json;charset=UTF-8" //We expect the content type to be "application/hal+json;charset=UTF-8"

View File

@@ -14,6 +14,7 @@ import org.dspace.content.service.DSpaceObjectService;
import org.dspace.core.Context; import org.dspace.core.Context;
import org.dspace.discovery.SearchServiceException; import org.dspace.discovery.SearchServiceException;
import org.dspace.eperson.EPerson; import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group;
public class EPersonBuilder extends AbstractDSpaceObjectBuilder<EPerson> { public class EPersonBuilder extends AbstractDSpaceObjectBuilder<EPerson> {
@@ -71,4 +72,15 @@ public class EPersonBuilder extends AbstractDSpaceObjectBuilder<EPerson> {
ePerson.setEmail(name); ePerson.setEmail(name);
return this; return this;
} }
public EPersonBuilder withGroupMembership(Group group) {
groupService.addMember(context, group, ePerson);
return this;
}
public EPersonBuilder withPassword(final String password) {
ePerson.setCanLogIn(true);
ePersonService.setPassword(ePerson, password);
return this;
}
} }

View File

@@ -67,7 +67,7 @@ public class ItemBuilder extends AbstractDSpaceObjectBuilder<Item> {
return addMetadataValue(item, MetadataSchema.DC_SCHEMA, "subject", null, subject); return addMetadataValue(item, MetadataSchema.DC_SCHEMA, "subject", null, subject);
} }
public ItemBuilder makePrivate() { public ItemBuilder makeUnDiscoverable() {
item.setDiscoverable(false); item.setDiscoverable(false);
return this; return this;
} }

View File

@@ -29,7 +29,7 @@ public class EPersonMatcher {
hasJsonPath("$.type", is("eperson")), hasJsonPath("$.type", is("eperson")),
hasJsonPath("$.canLogIn", not(empty())), hasJsonPath("$.canLogIn", not(empty())),
hasJsonPath("$._links.self.href", containsString("/api/eperson/epersons/" + ePerson.getID().toString())), hasJsonPath("$._links.self.href", containsString("/api/eperson/epersons/" + ePerson.getID().toString())),
hasJsonPath("$.metadata", Matchers.containsInAnyOrder( hasJsonPath("$.metadata", Matchers.hasItems(
EPersonMetadataMatcher.matchFirstName(ePerson.getFirstName()), EPersonMetadataMatcher.matchFirstName(ePerson.getFirstName()),
EPersonMetadataMatcher.matchLastName(ePerson.getLastName()) EPersonMetadataMatcher.matchLastName(ePerson.getLastName())
)) ))
@@ -37,9 +37,16 @@ public class EPersonMatcher {
} }
public static Matcher<? super Object> matchEPersonOnEmail(String email) {
return allOf(
hasJsonPath("$.type", is("eperson")),
hasJsonPath("$.email", is(email))
);
}
public static Matcher<? super Object> matchDefaultTestEPerson() { public static Matcher<? super Object> matchDefaultTestEPerson() {
return allOf( return allOf(
hasJsonPath("$.type", is("eperson")) hasJsonPath("$.type", is("eperson"))
); );
} }
} }

View File

@@ -31,4 +31,10 @@ public class EPersonMetadataMatcher {
); );
} }
public static Matcher<? super Object> matchLanguage(String language) {
return allOf(
hasJsonPath("$.key", is("eperson.language")),
hasJsonPath("$.value", is(language))
);
}
} }

View File

@@ -47,8 +47,7 @@ public class ItemMatcher {
hasJsonPath("$._links.self.href", startsWith(REST_SERVER_URL)), hasJsonPath("$._links.self.href", startsWith(REST_SERVER_URL)),
hasJsonPath("$._links.bitstreams.href", startsWith(REST_SERVER_URL)), hasJsonPath("$._links.bitstreams.href", startsWith(REST_SERVER_URL)),
hasJsonPath("$._links.owningCollection.href", startsWith(REST_SERVER_URL)), hasJsonPath("$._links.owningCollection.href", startsWith(REST_SERVER_URL)),
hasJsonPath("$._links.templateItemOf.href", startsWith(REST_SERVER_URL)), hasJsonPath("$._links.templateItemOf.href", startsWith(REST_SERVER_URL))
hasJsonPath("$._links.self.href", startsWith(REST_SERVER_URL))
); );
} }

View File

@@ -50,7 +50,7 @@ public class EPersonRestAuthenticationProviderTest {
List<GrantedAuthority> authorities = ePersonRestAuthenticationProvider.getGrantedAuthorities(context, ePerson); List<GrantedAuthority> authorities = ePersonRestAuthenticationProvider.getGrantedAuthorities(context, ePerson);
assertThat(authorities.stream().map(a -> a.getAuthority()).collect(Collectors.toList()), containsInAnyOrder( assertThat(authorities.stream().map(a -> a.getAuthority()).collect(Collectors.toList()), containsInAnyOrder(
WebSecurityConfiguration.ADMIN_GRANT, WebSecurityConfiguration.EPERSON_GRANT)); WebSecurityConfiguration.ADMIN_GRANT, WebSecurityConfiguration.AUTHENTICATED_GRANT));
} }
@@ -61,7 +61,7 @@ public class EPersonRestAuthenticationProviderTest {
List<GrantedAuthority> authorities = ePersonRestAuthenticationProvider.getGrantedAuthorities(context, ePerson); List<GrantedAuthority> authorities = ePersonRestAuthenticationProvider.getGrantedAuthorities(context, ePerson);
assertThat(authorities.stream().map(a -> a.getAuthority()).collect(Collectors.toList()), containsInAnyOrder( assertThat(authorities.stream().map(a -> a.getAuthority()).collect(Collectors.toList()), containsInAnyOrder(
WebSecurityConfiguration.EPERSON_GRANT)); WebSecurityConfiguration.AUTHENTICATED_GRANT));
} }

View File

@@ -22,6 +22,7 @@ import org.apache.commons.io.Charsets;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.dspace.app.rest.Application; import org.dspace.app.rest.Application;
import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.model.patch.Operation;
import org.dspace.app.rest.security.MethodSecurityConfig;
import org.dspace.app.rest.security.WebSecurityConfiguration; import org.dspace.app.rest.security.WebSecurityConfiguration;
import org.dspace.app.rest.utils.ApplicationConfig; import org.dspace.app.rest.utils.ApplicationConfig;
import org.junit.Assert; import org.junit.Assert;
@@ -54,7 +55,8 @@ import org.springframework.web.context.WebApplicationContext;
* @author Tom Desair (tom dot desair at atmire dot com) * @author Tom Desair (tom dot desair at atmire dot com)
*/ */
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = {Application.class, ApplicationConfig.class, WebSecurityConfiguration.class}) @SpringBootTest(classes = {Application.class, ApplicationConfig.class, WebSecurityConfiguration.class,
MethodSecurityConfig.class})
@TestExecutionListeners( {DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class, @TestExecutionListeners( {DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class,
TransactionalTestExecutionListener.class}) TransactionalTestExecutionListener.class})
@DirtiesContext @DirtiesContext
@@ -62,7 +64,10 @@ import org.springframework.web.context.WebApplicationContext;
public class AbstractControllerIntegrationTest extends AbstractIntegrationTestWithDatabase { public class AbstractControllerIntegrationTest extends AbstractIntegrationTestWithDatabase {
protected static final String AUTHORIZATION_HEADER = "Authorization"; protected static final String AUTHORIZATION_HEADER = "Authorization";
protected static final String AUTHORIZATION_TYPE = "Bearer";
//The Authorization header contains a value like "Bearer TOKENVALUE". This constant string represents the part that
//sits before the actual authentication token and can be used to easily compose or parse the Authorization header.
protected static final String AUTHORIZATION_TYPE = "Bearer ";
public static final String REST_SERVER_URL = "http://localhost/api/"; public static final String REST_SERVER_URL = "http://localhost/api/";
@@ -105,7 +110,8 @@ public class AbstractControllerIntegrationTest extends AbstractIntegrationTestWi
.addFilters(requestFilters.toArray(new Filter[requestFilters.size()])); .addFilters(requestFilters.toArray(new Filter[requestFilters.size()]));
if (StringUtils.isNotBlank(authToken)) { if (StringUtils.isNotBlank(authToken)) {
mockMvcBuilder.defaultRequest(get("").header(AUTHORIZATION_HEADER, AUTHORIZATION_TYPE + " " + authToken)); mockMvcBuilder.defaultRequest(
get("").header(AUTHORIZATION_HEADER, AUTHORIZATION_TYPE + authToken));
} }
return mockMvcBuilder return mockMvcBuilder
@@ -120,7 +126,9 @@ public class AbstractControllerIntegrationTest extends AbstractIntegrationTestWi
} }
public String getAuthToken(String user, String password) throws Exception { public String getAuthToken(String user, String password) throws Exception {
return getAuthResponse(user, password).getHeader(AUTHORIZATION_HEADER); return StringUtils.substringAfter(
getAuthResponse(user, password).getHeader(AUTHORIZATION_HEADER),
AUTHORIZATION_TYPE);
} }
public String getPatchContent(List<Operation> ops) { public String getPatchContent(List<Operation> ops) {