diff --git a/pom.xml b/pom.xml index 539758091..fd816d729 100644 --- a/pom.xml +++ b/pom.xml @@ -46,7 +46,7 @@ 11 0.0.0-MASTER-SNAPSHOT - 0.0.0-MASTER-SNAPSHOT + 0.0.0-SED-4536-SNAPSHOT 5.0.4 diff --git a/step-controller/step-controller-remote-client/src/main/java/step/client/resources/RemoteResourceManager.java b/step-controller/step-controller-remote-client/src/main/java/step/client/resources/RemoteResourceManager.java index d0d5dcee8..c44b486f2 100644 --- a/step-controller/step-controller-remote-client/src/main/java/step/client/resources/RemoteResourceManager.java +++ b/step-controller/step-controller-remote-client/src/main/java/step/client/resources/RemoteResourceManager.java @@ -228,6 +228,11 @@ public String getResourceName() { return resource.getResourceName(); } + @Override + public Resource getResource() { + return resource; + } + @Override public void close() throws IOException { // TODO Auto-generated method stub diff --git a/step-controller/step-controller-server/src/main/java/step/core/access/RoleProvider.java b/step-controller/step-controller-server/src/main/java/step/core/access/RoleProvider.java index 3d9c5a279..2afd45e8e 100644 --- a/step-controller/step-controller-server/src/main/java/step/core/access/RoleProvider.java +++ b/step-controller/step-controller-server/src/main/java/step/core/access/RoleProvider.java @@ -19,8 +19,11 @@ package step.core.access; import java.util.List; +import java.util.Set; public interface RoleProvider { - public List getRoles(); + List getRoles(); + + Set getAllRights(); } diff --git a/step-controller/step-controller-server/src/main/java/step/core/deployment/AbstractStepServices.java b/step-controller/step-controller-server/src/main/java/step/core/deployment/AbstractStepServices.java index 1b2189649..cc347c457 100644 --- a/step-controller/step-controller-server/src/main/java/step/core/deployment/AbstractStepServices.java +++ b/step-controller/step-controller-server/src/main/java/step/core/deployment/AbstractStepServices.java @@ -40,6 +40,12 @@ public abstract class AbstractStepServices extends AbstractServices { public static final String SESSION = "session"; + public static final String RIGHT_SEPARATOR = "-"; + public static final String READ_RIGHT = "read"; + public static final String WRITE_RIGHT = "write"; + public static final String DELETE_RIGHT = "delete"; + public static final String EXECUTE_RIGHT = "execute"; + protected Configuration configuration; @@ -118,6 +124,19 @@ protected void checkRightsOnBehalfOf(String right, String userOnBehalfOf) { } } + protected void checkRightIfDefined(String right) { + Session session = getSession(); + try { + if (!getAuthorizationManager().checkRightInContextIfDefined(session, right)) { + User user = session.getUser(); + throw new AuthorizationException("User " + (user == null ? "" : user.getUsername()) + " has no permission on '" + right + "'"); + } + } catch (NotMemberOfProjectException ex) { + // if user is not a member of the project, we want to return 'access denied' error + throw new AuthorizationException(ex.getMessage()); + } + } + /** * The ObjectHookInterceptor.aroundReadFrom can only be used for POST request passing an entity as request BODY * This method can be used as helper for all other cases where checking if the entity is editable in given context (i.e. DELETE request...( diff --git a/step-controller/step-controller-server/src/main/java/step/resources/ResourceServices.java b/step-controller/step-controller-server/src/main/java/step/resources/ResourceServices.java index 91dd0d45e..247d8c2ab 100644 --- a/step-controller/step-controller-server/src/main/java/step/resources/ResourceServices.java +++ b/step-controller/step-controller-server/src/main/java/step/resources/ResourceServices.java @@ -45,11 +45,16 @@ import java.io.InputStream; import java.util.*; import java.util.function.Consumer; +import java.util.stream.Collectors; @Path("/resources") @Tag(name = "Resources") public class ResourceServices extends AbstractStepAsyncServices { + private static final String RESOURCE_RIGHT_NAME = "resource"; + public static final String INVALID_ARGUMENTS_A_NON_NULL_RESOURCE_MUST_BE_PROVIDED = "Invalid arguments: a non null resource must be provided."; + public static final String INVALID_RESOURCE_THE_RESOURCE_HAS_NO_RESOURCE_TYPE_SET = "Invalid resource: the resource has no resourceType set."; + protected ResourceManager resourceManager; private TableService tableService; @@ -74,10 +79,25 @@ private void auditLog(String operation, Resource resource) { AuditLogger.logEntityModification(getSession(), operation, "resources", resource.getId().toHexString(), entityName, attributes); } + private void checkResourceTypeRight(String resourceType, String right) { + if (resourceType == null || resourceType.isEmpty()) { + throw new ControllerServiceException(INVALID_RESOURCE_THE_RESOURCE_HAS_NO_RESOURCE_TYPE_SET); + } + checkRightIfDefined(RESOURCE_RIGHT_NAME + RIGHT_SEPARATOR + resourceType + RIGHT_SEPARATOR + right); + } + + private void checkResourceTypeRight(Resource resource, String right) { + //If a resource is null, right is granted too, it's not the role of this function to perform integrity check + if (resource == null) { + throw new ControllerServiceException(INVALID_ARGUMENTS_A_NON_NULL_RESOURCE_MUST_BE_PROVIDED); + } else { + checkResourceTypeRight(resource.getResourceType(), right); + } + } @POST @Path("/content") - @Secured(right = "resource-write") + @Secured(right = RESOURCE_RIGHT_NAME + RIGHT_SEPARATOR + WRITE_RIGHT) @Consumes(MediaType.MULTIPART_FORM_DATA) @Produces(MediaType.APPLICATION_JSON) public ResourceUploadResponse createResource(@FormDataParam("file") InputStream uploadedInputStream, @@ -91,9 +111,10 @@ public ResourceUploadResponse createResource(@FormDataParam("file") InputStream if (uploadedInputStream == null || fileDetail == null) throw new RuntimeException("Invalid arguments"); - if (resourceType == null || resourceType.length() == 0) + if (resourceType == null || resourceType.isEmpty()) throw new RuntimeException("Missing resource type query parameter 'type'"); + checkResourceTypeRight(resourceType, WRITE_RIGHT); try { Resource resource = resourceManager.createTrackedResource( resourceType, isDirectory, uploadedInputStream, fileDetail.getFileName(), objectEnricher, @@ -113,17 +134,27 @@ private ControllerServiceException uploadFileNotAnArchive() { } @POST - @Secured(right = "resource-write") + @Secured(right = RESOURCE_RIGHT_NAME + RIGHT_SEPARATOR + WRITE_RIGHT) @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Resource saveResource(Resource resource) throws IOException { + // Always check that we have write access to the new resource type, this automatically ensure the resource and its resourceType are not null + checkResourceTypeRight(resource, WRITE_RIGHT); + // When updating a resource, we need write access to both the original and new types + String resourceId = resource.getId().toHexString(); + if (resourceManager.resourceExists(resourceId)) { + String originalResourceType = resourceManager.getResource(resourceId).getResourceType(); + if (!resource.getResourceType().equals(originalResourceType)) { + checkResourceTypeRight(originalResourceType, WRITE_RIGHT); + } + } auditLog("save", resource); return resourceManager.saveResource(resource); } @POST @Path("/{id}/content") - @Secured(right = "resource-write") + @Secured(right = RESOURCE_RIGHT_NAME + RIGHT_SEPARATOR + WRITE_RIGHT) @Consumes(MediaType.MULTIPART_FORM_DATA) @Produces(MediaType.APPLICATION_JSON) public ResourceUploadResponse saveResourceContent(@PathParam("id") String resourceId, @FormDataParam("file") InputStream uploadedInputStream, @@ -131,6 +162,7 @@ public ResourceUploadResponse saveResourceContent(@PathParam("id") String resour if (uploadedInputStream == null || fileDetail == null) throw new RuntimeException("Invalid arguments"); + checkResourceTypeRight(resourceManager.getResource(resourceId), WRITE_RIGHT); try { Resource resource = resourceManager.saveResourceContent(resourceId, uploadedInputStream, fileDetail.getFileName(), null, getSession().getUser().getUsername()); auditLog("save-content", resource); @@ -143,11 +175,13 @@ public ResourceUploadResponse saveResourceContent(@PathParam("id") String resour @GET @Secured @Path("/{id}") - @Secured(right = "resource-read") + @Secured(right = RESOURCE_RIGHT_NAME + RIGHT_SEPARATOR + READ_RIGHT) @Produces(MediaType.APPLICATION_JSON) public Resource getResource(@PathParam("id") String resourceId) throws IOException { try { - return resourceManager.getResource(resourceId); + Resource resource = resourceManager.getResource(resourceId); + checkResourceTypeRight(resource, READ_RIGHT); + return resource; } catch (ResourceMissingException e) { throw new ControllerServiceException(404, e.getMessage()); } @@ -155,19 +189,21 @@ public Resource getResource(@PathParam("id") String resourceId) throws IOExcepti @GET @Path("/{id}/content") - @Secured(right = "resource-read") + @Secured(right = RESOURCE_RIGHT_NAME + RIGHT_SEPARATOR + READ_RIGHT) @Produces(MediaType.APPLICATION_JSON) public Response getResourceContent(@PathParam("id") String resourceId, @QueryParam("inline") boolean inline) throws IOException { ResourceRevisionContent resourceContent = resourceManager.getResourceContent(resourceId); + checkResourceTypeRight(resourceContent.getResource(), READ_RIGHT); return getResponseForResourceRevisionContent(resourceContent, inline); } @DELETE @Secured @Path("/{id}") - @Secured(right = "resource-delete") + @Secured(right = RESOURCE_RIGHT_NAME + RIGHT_SEPARATOR + DELETE_RIGHT) public void deleteResource(@PathParam("id") String resourceId) { Resource resource = resourceManager.getResource(resourceId); + checkResourceTypeRight(resource, DELETE_RIGHT); assertEntityIsEditableInContext(resource); auditLog("delete", resource); resourceManager.deleteResource(resourceId); @@ -176,9 +212,10 @@ public void deleteResource(@PathParam("id") String resourceId) { @DELETE @Secured @Path("/{id}/revisions") - @Secured(right = "resource-delete") + @Secured(right = RESOURCE_RIGHT_NAME + RIGHT_SEPARATOR + DELETE_RIGHT) public void deleteResourceRevisions(@PathParam("id") String resourceId) { Resource resource = resourceManager.getResource(resourceId); + checkResourceTypeRight(resource, DELETE_RIGHT); assertEntityIsEditableInContext(resource); auditLog("delete-revisions", resource); resourceManager.deleteResourceRevisionContent(resourceId); @@ -207,9 +244,10 @@ public AsyncTaskStatus bulkDelete(TableBulkOperationRe @GET @Produces(MediaType.APPLICATION_JSON) @Path("/revision/{id}/content") - @Secured(right = "resource-read") + @Secured(right = RESOURCE_RIGHT_NAME + RIGHT_SEPARATOR + READ_RIGHT) public Response getResourceRevisionContent(@PathParam("id") String resourceRevisionId, @QueryParam("inline") boolean inline) throws IOException { ResourceRevisionContentImpl resourceContent = resourceManager.getResourceRevisionContent(resourceRevisionId); + checkResourceTypeRight(resourceContent.getResource(), READ_RIGHT); return getResponseForResourceRevisionContent(resourceContent, inline); } @@ -217,9 +255,17 @@ public Response getResourceRevisionContent(@PathParam("id") String resourceRevis @Path("/find") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) - @Secured(right = "resource-read") + @Secured(right = RESOURCE_RIGHT_NAME + RIGHT_SEPARATOR + READ_RIGHT) public List findManyByCriteria(Map criteria) { - return resourceManager.findManyByCriteria(criteria); + return resourceManager.findManyByCriteria(criteria).stream().filter(r -> { + try { + checkResourceTypeRight(r, READ_RIGHT); + return true; + } catch (Exception e) { + //we filter out the resources for which the user has no access + return false; + } + }).collect(Collectors.toList()); } protected Response getResponseForResourceRevisionContent(ResourceRevisionContent resourceContent, boolean inline) { diff --git a/step-core/src/main/java/step/resources/ResourceRevisionContent.java b/step-core/src/main/java/step/resources/ResourceRevisionContent.java index 55f219b8a..c8873afde 100644 --- a/step-core/src/main/java/step/resources/ResourceRevisionContent.java +++ b/step-core/src/main/java/step/resources/ResourceRevisionContent.java @@ -27,6 +27,8 @@ public interface ResourceRevisionContent { String getResourceName(); + Resource getResource(); + void close() throws IOException; } diff --git a/step-core/src/main/java/step/resources/ResourceRevisionContentImpl.java b/step-core/src/main/java/step/resources/ResourceRevisionContentImpl.java index c3aece366..d2c2a1053 100644 --- a/step-core/src/main/java/step/resources/ResourceRevisionContentImpl.java +++ b/step-core/src/main/java/step/resources/ResourceRevisionContentImpl.java @@ -48,6 +48,10 @@ public String getResourceName() { return resourceName; } + public Resource getResource() { + return resource; + } + @Override public void close() throws IOException { resourceStream.close();