From 12421bf430cb432e3bbe41dd8898d1f2aaea98b2 Mon Sep 17 00:00:00 2001 From: Richard Millet Date: Tue, 22 Oct 2019 16:56:13 -0700 Subject: [PATCH] DRYD-732: Initial support for adding 'invoke' subresource for batch and report resources. --- .../services/authorization/AuthZ.java | 11 +++ .../tenants/tenant-bindings-proto-unified.xml | 44 +++++++++ .../common/security/SecurityInterceptor.java | 34 ++++--- .../common/security/SecurityUtils.java | 18 +++- .../services/report/ReportResource.java | 92 +++++++++++++++++-- 5 files changed, 172 insertions(+), 27 deletions(-) diff --git a/services/authorization/service/src/main/java/org/collectionspace/services/authorization/AuthZ.java b/services/authorization/service/src/main/java/org/collectionspace/services/authorization/AuthZ.java index 1d18a994e..18fcffa3f 100644 --- a/services/authorization/service/src/main/java/org/collectionspace/services/authorization/AuthZ.java +++ b/services/authorization/service/src/main/java/org/collectionspace/services/authorization/AuthZ.java @@ -59,6 +59,17 @@ public class AuthZ { private AuthZ() { setupProvider(); } + + // + // URI paths that require special handling + // + public static final String REPORTS_MIME_OUTPUTS = "reports/mimetypes"; + public static final String ACCOUNT_PERMISSIONS = "accounts/*/accountperms"; + public static final String STRUCTURED_DATE_REQUEST = "structureddate"; + public static final String PASSWORD_RESET = "accounts/requestpasswordreset"; + public static final String PROCESS_PASSWORD_RESET = "accounts/processpasswordreset"; + public static final String REPORTS_INVOKE = "reports/*/invoke"; + public static final String BATCH_INVOKE = "batch/*/invoke"; /** * diff --git a/services/common/src/main/cspace/config/services/tenants/tenant-bindings-proto-unified.xml b/services/common/src/main/cspace/config/services/tenants/tenant-bindings-proto-unified.xml index c3543c8e0..9a62f51a7 100644 --- a/services/common/src/main/cspace/config/services/tenants/tenant-bindings-proto-unified.xml +++ b/services/common/src/main/cspace/config/services/tenants/tenant-bindings-proto-unified.xml @@ -255,6 +255,28 @@ + + + + + org.collectionspace.services.batch.nuxeo.BatchDocumentModelHandler + + + + + + + + + + + + default-domain @@ -414,6 +436,28 @@ + + + + + org.collectionspace.services.report.nuxeo.ReportDocumentModelHandler + + + + + + + + + + + + diff --git a/services/common/src/main/java/org/collectionspace/services/common/security/SecurityInterceptor.java b/services/common/src/main/java/org/collectionspace/services/common/security/SecurityInterceptor.java index 73a0997ff..de25f3441 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/security/SecurityInterceptor.java +++ b/services/common/src/main/java/org/collectionspace/services/common/security/SecurityInterceptor.java @@ -84,11 +84,7 @@ public class SecurityInterceptor implements PreProcessInterceptor, PostProcessIn /** The Constant logger. */ private static final Logger logger = LoggerFactory.getLogger(SecurityInterceptor.class); - private static final String REPORTS_MIME_OUTPUTS = "reports/mimetypes"; - private static final String ACCOUNT_PERMISSIONS = "accounts/*/accountperms"; - private static final String STRUCTURED_DATE_REQUEST = "structureddate"; - private static final String PASSWORD_RESET = "accounts/requestpasswordreset"; - private static final String PROCESS_PASSWORD_RESET = "accounts/processpasswordreset"; + private static final String SYSTEM_INFO = SystemInfoClient.SERVICE_NAME; private static final String NUXEO_ADMIN = null; // @@ -107,8 +103,8 @@ public class SecurityInterceptor implements PreProcessInterceptor, PostProcessIn String resName = SecurityUtils.getResourceName(request.getUri()).toLowerCase(); switch (resName) { - case PASSWORD_RESET: - case PROCESS_PASSWORD_RESET: + case AuthZ.PASSWORD_RESET: + case AuthZ.PROCESS_PASSWORD_RESET: case SYSTEM_INFO: return true; } @@ -139,9 +135,9 @@ public class SecurityInterceptor implements PreProcessInterceptor, PostProcessIn // STRUCTURED_DATE_REQUEST: All user can request the parsing of a structured date string. // switch (resName) { - case STRUCTURED_DATE_REQUEST: - case ACCOUNT_PERMISSIONS: - case REPORTS_MIME_OUTPUTS: + case AuthZ.STRUCTURED_DATE_REQUEST: + case AuthZ.ACCOUNT_PERMISSIONS: + case AuthZ.REPORTS_MIME_OUTPUTS: result = false; break; default: @@ -186,14 +182,24 @@ public class SecurityInterceptor implements PreProcessInterceptor, PostProcessIn String resEntity = SecurityUtils.getResourceEntity(resName); // - // If the resource entity is acting as a proxy then all sub-resource will map to the resource itself. - // This essentially means that the sub-resource inherit all the authz permissions of the entity. + // If the resource entity is acting as a proxy then all sub-resources will map to the resource itself. + // This essentially means sub-resources inherit all the authz permissions of the entity. // - if (SecurityUtils.isEntityProxy() == true && !resName.equalsIgnoreCase(ACCOUNT_PERMISSIONS)) { + if (SecurityUtils.isResourceProxied(resName) == true) { resName = resEntity; + } else { + // + // If our resName is not proxied, we may need to tweak it. + // + switch (resName) { + case AuthZ.REPORTS_INVOKE: + case AuthZ.BATCH_INVOKE: { + resName = resName.replace("/*/", "/"); + } + } } // - // Make sure the account is current and active + // Make sure the account of the user making the request is current and active // checkActive(); diff --git a/services/common/src/main/java/org/collectionspace/services/common/security/SecurityUtils.java b/services/common/src/main/java/org/collectionspace/services/common/security/SecurityUtils.java index 37d7c6cd3..0f20ebcf2 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/security/SecurityUtils.java +++ b/services/common/src/main/java/org/collectionspace/services/common/security/SecurityUtils.java @@ -86,6 +86,7 @@ class CSpacePasswordEncoder extends BasePasswordEncoder { public class SecurityUtils { private static final Logger logger = LoggerFactory.getLogger(SecurityUtils.class); + public static final String URI_PATH_SEPARATOR = "/"; public static final int MIN_PASSWORD_LENGTH = 8; public static final int MAX_PASSWORD_LENGTH = 24; @@ -231,6 +232,7 @@ public class SecurityUtils { uriPath = uriPath.replace("//", "/"); // replace duplicate '/' characters uriPath = uriPath.startsWith("/") ? uriPath.substring(1) : uriPath; // if present, strip the leading '/' character + return uriPath; } @@ -309,10 +311,18 @@ public class SecurityUtils { * * @return true, if is entity proxy is acting as a proxy for all sub-resources */ - public static final boolean isEntityProxy() { - // - // should be getting this information from the cspace config settings (tenent bindings file). - return true; + public static final boolean isResourceProxied(String resName) { + boolean result = true; + + switch (resName) { + case AuthZ.REPORTS_INVOKE: + case AuthZ.BATCH_INVOKE: + case AuthZ.ACCOUNT_PERMISSIONS: + result = false; + break; + } + + return result; } diff --git a/services/report/service/src/main/java/org/collectionspace/services/report/ReportResource.java b/services/report/service/src/main/java/org/collectionspace/services/report/ReportResource.java index c87c2cfba..cbf26cd96 100644 --- a/services/report/service/src/main/java/org/collectionspace/services/report/ReportResource.java +++ b/services/report/service/src/main/java/org/collectionspace/services/report/ReportResource.java @@ -29,6 +29,10 @@ import java.util.List; import org.collectionspace.services.jaxb.AbstractCommonList; import org.collectionspace.services.report.nuxeo.ReportDocumentModelHandler; import org.collectionspace.services.publicitem.PublicitemsCommon; +import org.collectionspace.services.authorization.AuthZ; +import org.collectionspace.services.authorization.CSpaceResource; +import org.collectionspace.services.authorization.URIResourceImpl; +import org.collectionspace.services.authorization.perms.ActionType; import org.collectionspace.services.client.IQueryManager; import org.collectionspace.services.client.PayloadPart; import org.collectionspace.services.client.PoxPayloadIn; @@ -62,6 +66,7 @@ import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.Response.ResponseBuilder; +import javax.ws.rs.core.Response.Status; @Path(ReportClient.SERVICE_PATH) @Consumes("application/xml") @@ -77,6 +82,7 @@ public class ReportResource extends NuxeoBasedResource { private static String REPORTS_STD_GROUPCSID_PARAM = "groupcsid"; private static String REPORTS_STD_CSIDLIST_PARAM = "csidlist"; private static String REPORTS_STD_TENANTID_PARAM = "tenantid"; + private static String REPORT_INVOKE_RESNAME = "reports/invoke"; @Override protected String getVersionString() { @@ -188,30 +194,98 @@ public class ReportResource extends NuxeoBasedResource { StringBuffer outMimeType = new StringBuffer(); StringBuffer outReportFileName = new StringBuffer(); ServiceContext ctx = createServiceContext(); - InputStream reportInputStream = invokeReport(ctx, csid, invContext, outMimeType, outReportFileName); - response = PublicItemUtil.publishToRepository( - (PublicitemsCommon)null, - resourceMap, - uriInfo, - getRepositoryClient(ctx), - ctx, - reportInputStream, - outReportFileName.toString()); + + if (isAuthorizedToInvokeReports(ctx) == true) { + InputStream reportInputStream = invokeReport(ctx, csid, invContext, outMimeType, outReportFileName); + response = PublicItemUtil.publishToRepository( + (PublicitemsCommon)null, + resourceMap, + uriInfo, + getRepositoryClient(ctx), + ctx, + reportInputStream, + outReportFileName.toString()); + } else { + ResponseBuilder builder = Response.status(Status.FORBIDDEN); + response = builder.build(); + } } catch (Exception e) { throw bigReThrow(e, ServiceMessages.POST_FAILED); } return response; } + + /* + * This method allows backward compatibility with the old API for running reports. + */ + private boolean isAuthorizedToInvokeReports(ServiceContext ctx) { + boolean result = true; + + // + // Until we enforce a user having POST perms on "/reports/*/invoke", we will continue to allow users with + // POST perms on "/reports" to run reports -see JIRA issue https://collectionspace.atlassian.net/browse/DRYD-732 + // + // To start enforcing POST perms on "/reports/*/invoke", uncomment the following block of code + // + + /* + CSpaceResource res = new URIResourceImpl(ctx.getTenantId(), REPORT_INVOKE_RESNAME, AuthZ.getMethod(ActionType.CREATE)); + if (AuthZ.get().isAccessAllowed(res) == false) { + result = false; + } + */ + + return result; + } + /** + * This method is deprecated at of CollectionSpace v5.3. + * @param ui + * @param csid + * @param invContext + * @return + */ @POST @Path("{csid}") + @Deprecated public Response invokeReport( @Context UriInfo ui, @PathParam("csid") String csid, InvocationContext invContext) { Response response = null; + try { + StringBuffer outMimeType = new StringBuffer(); + StringBuffer outFileName = new StringBuffer(); + ServiceContext ctx = createServiceContext(); + + if (isAuthorizedToInvokeReports(ctx) == true) { + InputStream reportInputStream = invokeReport(ctx, csid, invContext, outMimeType, outFileName); + // Need to set response type for what is requested... + ResponseBuilder builder = Response.ok(reportInputStream, outMimeType.toString()); + builder = builder.header("Content-Disposition","inline;filename=\""+ outFileName.toString() +"\""); + response = builder.build(); + } else { + ResponseBuilder builder = Response.status(Status.FORBIDDEN); + response = builder.build(); + } + } catch (Exception e) { + String msg = e.getMessage(); + throw bigReThrow(e, ServiceMessages.POST_FAILED + msg != null ? msg : ""); + } + + return response; + } + + @POST + @Path("{csid}/invoke") + public Response invokeReportNew( + @Context UriInfo ui, + @PathParam("csid") String csid, + InvocationContext invContext) { + Response response = null; + try { StringBuffer outMimeType = new StringBuffer(); StringBuffer outFileName = new StringBuffer(); -- 2.47.3