From 55c2a2d55f42c0b65993f259ed14b176b83b3ec6 Mon Sep 17 00:00:00 2001 From: remillet Date: Fri, 1 Dec 2017 16:28:31 -0800 Subject: [PATCH] DRYD-186: permissionId values in permroles payload is now optional. Service layer can find Permission records using the resourceName/actionGroup tuple. Services layer will create new Permission records if the permroles payload refers to non-existent Permission records. This will allow clients to create new roles (and update existing roles) with permissions in one request. --- .../CollectionSpaceJaxRsApplication.java | 2 +- .../services/account/AccountResource.java | 4 +- .../client/PermissionActionFactory.java | 12 + .../services/client/PermissionClient.java | 120 ++++++- .../services/client/PermissionFactory.java | 32 +- .../test/RolePermissionServiceTest.java | 47 ++- .../driver/AuthorizationSeedDriver.java | 19 +- .../importer/AuthorizationGen.java | 20 +- services/authorization-mgt/service/pom.xml | 2 +- .../authorization/PermissionResource.java.txt | 254 ++++++++++++++ ...ava => PermissionDocumentHandler.java.txt} | 0 .../storage/PermissionValidatorHandler.java | 40 ++- .../storage/RoleDocumentHandler.java | 18 +- .../authorization/storage/RoleJpaFilter.java | 3 +- .../main/resources/authorization_common.xsd | 2 + .../resources/db/postgresql/authorization.sql | 2 +- services/common/pom.xml | 7 + .../authorization/PermissionResource.java | 17 + .../PermissionRoleSubResource.java | 5 +- .../storage/AuthorizationDelegate.java | 6 +- .../storage/PermissionDocumentHandler.java | 331 ++++++++++++++++++ .../storage/PermissionJpaFilter.java | 1 + .../PermissionRoleDocumentHandler.java | 5 +- .../storage/PermissionStorageConstants.java | 0 .../storage}/RoleStorageConstants.java | 2 +- .../services/common/ServiceMain.java | 8 +- .../AuthorizationCommon.java | 20 +- .../authorization_mgt/AuthorizationStore.java | 3 +- .../authorization_mgt/PermissionRoleUtil.java | 193 +++++++--- .../common/document/DocumentWrapper.java | 1 + .../common/document/DocumentWrapperImpl.java | 9 +- .../jpa/JpaRelationshipStorageClient.java | 2 +- .../storage/jpa/JpaStorageClientImpl.java | 26 +- .../common/storage/jpa/JpaStorageUtils.java | 14 +- .../src/main/resources/permissions.xsd | 9 +- 35 files changed, 1098 insertions(+), 138 deletions(-) create mode 100644 services/authorization-mgt/client/src/main/java/org/collectionspace/services/client/PermissionActionFactory.java create mode 100644 services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/PermissionResource.java.txt rename services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/{PermissionDocumentHandler.java => PermissionDocumentHandler.java.txt} (100%) rename services/{authorization-mgt/service => common}/src/main/java/org/collectionspace/services/authorization/PermissionResource.java (93%) rename services/{authorization-mgt/service => common}/src/main/java/org/collectionspace/services/authorization/PermissionRoleSubResource.java (97%) rename services/{authorization-mgt/service => common}/src/main/java/org/collectionspace/services/authorization/storage/AuthorizationDelegate.java (97%) create mode 100644 services/common/src/main/java/org/collectionspace/services/authorization/storage/PermissionDocumentHandler.java rename services/{authorization-mgt/service => common}/src/main/java/org/collectionspace/services/authorization/storage/PermissionJpaFilter.java (99%) rename services/{authorization-mgt/service => common}/src/main/java/org/collectionspace/services/authorization/storage/PermissionRoleDocumentHandler.java (99%) rename services/{authorization-mgt/service => common}/src/main/java/org/collectionspace/services/authorization/storage/PermissionStorageConstants.java (100%) rename services/common/src/main/java/org/collectionspace/services/{common/authorization_mgt => authorization/storage}/RoleStorageConstants.java (95%) diff --git a/services/JaxRsServiceProvider/src/main/java/org/collectionspace/services/jaxrs/CollectionSpaceJaxRsApplication.java b/services/JaxRsServiceProvider/src/main/java/org/collectionspace/services/jaxrs/CollectionSpaceJaxRsApplication.java index 067d2fae0..8fcb91877 100644 --- a/services/JaxRsServiceProvider/src/main/java/org/collectionspace/services/jaxrs/CollectionSpaceJaxRsApplication.java +++ b/services/JaxRsServiceProvider/src/main/java/org/collectionspace/services/jaxrs/CollectionSpaceJaxRsApplication.java @@ -58,6 +58,7 @@ import org.collectionspace.services.claim.ClaimResource; import org.collectionspace.services.exhibition.ExhibitionResource; import org.collectionspace.services.conditioncheck.ConditioncheckResource; import org.collectionspace.services.conservation.ConservationResource; +import org.collectionspace.services.authorization.PermissionResource; import javax.servlet.ServletContext; import javax.ws.rs.core.Application; @@ -68,7 +69,6 @@ import java.util.Set; //import org.collectionspace.services.common.FileUtils; -import org.collectionspace.services.authorization.PermissionResource; import org.collectionspace.services.authorization.RoleResource; import org.collectionspace.services.common.NuxeoBasedResource; import org.collectionspace.services.common.ResourceMap; diff --git a/services/account/service/src/main/java/org/collectionspace/services/account/AccountResource.java b/services/account/service/src/main/java/org/collectionspace/services/account/AccountResource.java index 090f8ab55..8ba98f756 100644 --- a/services/account/service/src/main/java/org/collectionspace/services/account/AccountResource.java +++ b/services/account/service/src/main/java/org/collectionspace/services/account/AccountResource.java @@ -86,8 +86,8 @@ public class AccountResource extends SecurityResourceBase { final Logger logger = LoggerFactory.getLogger(AccountResource.class); final StorageClient storageClient = new AccountStorageClient(); - private static final String PASSWORD_RESET_PATH = "requestpasswordreset"; - private static final String PROCESS_PASSWORD_RESET_PATH = "processpasswordreset"; + private static final String PASSWORD_RESET_PATH = "/requestpasswordreset"; + private static final String PROCESS_PASSWORD_RESET_PATH = "/processpasswordreset"; @Override protected String getVersionString() { diff --git a/services/authorization-mgt/client/src/main/java/org/collectionspace/services/client/PermissionActionFactory.java b/services/authorization-mgt/client/src/main/java/org/collectionspace/services/client/PermissionActionFactory.java new file mode 100644 index 000000000..8032174fa --- /dev/null +++ b/services/authorization-mgt/client/src/main/java/org/collectionspace/services/client/PermissionActionFactory.java @@ -0,0 +1,12 @@ +package org.collectionspace.services.client; + +import org.collectionspace.services.authorization.perms.ActionType; +import org.collectionspace.services.authorization.perms.PermissionAction; + +public class PermissionActionFactory { + public static PermissionAction create(ActionType actionType) { + PermissionAction result = new PermissionAction(); + result.setName(actionType); + return result; + } +} diff --git a/services/authorization-mgt/client/src/main/java/org/collectionspace/services/client/PermissionClient.java b/services/authorization-mgt/client/src/main/java/org/collectionspace/services/client/PermissionClient.java index 442c23b88..9ff74c0ec 100644 --- a/services/authorization-mgt/client/src/main/java/org/collectionspace/services/client/PermissionClient.java +++ b/services/authorization-mgt/client/src/main/java/org/collectionspace/services/client/PermissionClient.java @@ -26,10 +26,16 @@ */ package org.collectionspace.services.client; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + import javax.ws.rs.core.Response; import org.apache.http.HttpStatus; +import org.collectionspace.services.authorization.perms.ActionType; import org.collectionspace.services.authorization.perms.Permission; +import org.collectionspace.services.authorization.perms.PermissionAction; import org.collectionspace.services.authorization.perms.PermissionsList; import org.collectionspace.services.description.ServiceDescription; @@ -42,7 +48,11 @@ public class PermissionClient extends AbstractServiceClientImpl actionList) { + String result = null; + HashMap actionMap = getEmptyActionMap(); + + for (PermissionAction permAction : actionList) { + switch (permAction.getName()) { + case CREATE: + actionMap.put(ActionType.CREATE, "C"); + break; + case READ: + actionMap.put(ActionType.READ, "R"); + break; + case UPDATE: + actionMap.put(ActionType.UPDATE, "U"); + break; + case DELETE: + actionMap.put(ActionType.DELETE, "D"); + break; + case SEARCH: + actionMap.put(ActionType.SEARCH, "L"); + break; + default: + String msg = String.format("Unknown permission action '%s'.", permAction.getName().value()); + logger.error(null); + return result; + } + } + + result = String.format("%s%s%s%s%s", + actionMap.get(ActionType.CREATE), + actionMap.get(ActionType.READ), + actionMap.get(ActionType.UPDATE), + actionMap.get(ActionType.DELETE), + actionMap.get(ActionType.SEARCH)); + + return result; + } + + private static HashMap getEmptyActionMap() { + HashMap emptyActionMap = new HashMap(); + + emptyActionMap.put(ActionType.CREATE, ""); + emptyActionMap.put(ActionType.READ, ""); + emptyActionMap.put(ActionType.UPDATE, ""); + emptyActionMap.put(ActionType.DELETE, ""); + emptyActionMap.put(ActionType.SEARCH, ""); + + return emptyActionMap; + } + + public static List getActionList(String actionGroup) { + if (actionGroup == null || actionGroup.trim().isEmpty()) { + return null; + } + + List result = new ArrayList(); + for (char c : actionGroup.toUpperCase().toCharArray()) { + switch (c) { + case 'C': + result.add(PermissionActionFactory.create(ActionType.CREATE)); + break; + case 'R': + result.add(PermissionActionFactory.create(ActionType.READ)); + break; + case 'U': + result.add(PermissionActionFactory.create(ActionType.UPDATE)); + break; + case 'D': + result.add(PermissionActionFactory.create(ActionType.DELETE)); + break; + case 'L': + result.add(PermissionActionFactory.create(ActionType.SEARCH)); + break; + } + } + + return result; + } + + /* + * Validate that the permission's action group and action list are non-null, non-empty, and equivalent. + * Returns: + * -1 - Permission action group is empty or null + */ + public static ActionCompare validatePermActions(Permission permission) { + String actionGroup = permission.getActionGroup(); + List actionList = permission.getAction(); + + if ((actionGroup == null || actionGroup.trim().isEmpty() == true) && (actionList == null || actionList.size() < 1)) { + return ActionCompare.ACTIONS_MISSING; + } + + if (actionGroup == null || actionGroup.trim().isEmpty() == true) { + return ActionCompare.ACTION_GROUP_EMPTY; + } + + if (actionList == null || actionList.size() < 1) { + return ActionCompare.ACTION_LIST_EMPTY; + } + + String actionGroupFromActionList = getActionGroup(permission.getAction()); + if (actionGroupFromActionList == null || !actionGroupFromActionList.equalsIgnoreCase(actionGroup)) { + return ActionCompare.MISMATCHES; + } + + return ActionCompare.MATCHES; + } } diff --git a/services/authorization-mgt/client/src/main/java/org/collectionspace/services/client/PermissionFactory.java b/services/authorization-mgt/client/src/main/java/org/collectionspace/services/client/PermissionFactory.java index db52ed1c9..28326ddab 100644 --- a/services/authorization-mgt/client/src/main/java/org/collectionspace/services/client/PermissionFactory.java +++ b/services/authorization-mgt/client/src/main/java/org/collectionspace/services/client/PermissionFactory.java @@ -25,6 +25,7 @@ package org.collectionspace.services.client; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import org.collectionspace.services.authorization.perms.ActionType; import org.collectionspace.services.authorization.perms.EffectType; @@ -62,11 +63,14 @@ public class PermissionFactory { boolean useEffect) { Permission permission = new Permission(); + if (useResourceName) { permission.setResourceName(resourceName); } if (useAction) { permission.setAction(actionList); + String actionGroup = PermissionClient.getActionGroup(actionList); + permission.setActionGroup(actionGroup); } if (useEffect) { permission.setEffect(effect); @@ -74,28 +78,18 @@ public class PermissionFactory { return permission; } + public static String createDefaultActionGroup() { + return "CRUDL"; + } public static List createDefaultActions() { List actions = new ArrayList(); - PermissionAction create = new PermissionAction(); - create.setName(ActionType.CREATE); - actions.add(create); - - PermissionAction read = new PermissionAction(); - read.setName(ActionType.READ); - actions.add(read); - - PermissionAction update = new PermissionAction(); - update.setName(ActionType.UPDATE); - actions.add(update); - - PermissionAction delete = new PermissionAction(); - delete.setName(ActionType.DELETE); - actions.add(delete); - - PermissionAction search = new PermissionAction(); - search.setName(ActionType.SEARCH); - actions.add(search); + + actions.add(PermissionActionFactory.create(ActionType.CREATE)); + actions.add(PermissionActionFactory.create(ActionType.READ)); + actions.add(PermissionActionFactory.create(ActionType.UPDATE)); + actions.add(PermissionActionFactory.create(ActionType.DELETE)); + actions.add(PermissionActionFactory.create(ActionType.SEARCH)); return actions; } diff --git a/services/authorization-mgt/client/src/test/java/org/collectionspace/services/authorization/client/test/RolePermissionServiceTest.java b/services/authorization-mgt/client/src/test/java/org/collectionspace/services/authorization/client/test/RolePermissionServiceTest.java index d264fb28e..7ed710e04 100644 --- a/services/authorization-mgt/client/src/test/java/org/collectionspace/services/authorization/client/test/RolePermissionServiceTest.java +++ b/services/authorization-mgt/client/src/test/java/org/collectionspace/services/authorization/client/test/RolePermissionServiceTest.java @@ -106,7 +106,6 @@ public class RolePermissionServiceTest extends AbstractServiceTestImpl actions = PermissionFactory.createDefaultActions(); Permission permission = PermissionFactory.createPermissionInstance(resName, "default permissions for " + resName, - actions, effect, true, true, true); + actions, EffectType.PERMIT, true, true, true); Response res = null; String id = null; try { @@ -597,6 +600,32 @@ public class RolePermissionServiceTest extends AbstractServiceTestImpl permRoleRels = new ArrayList(); for (PermissionRole pr : authzGen.getDefaultPermissionRoles()) { - PermissionRoleUtil.buildPermissionRoleRel(em, pr, SubjectType.ROLE, permRoleRels, false /*not for delete*/); + String tenantId = getTenantId(pr); + PermissionRoleUtil.buildPermissionRoleRel(em, pr, SubjectType.ROLE, permRoleRels, false /*not for delete*/, tenantId); } for (PermissionRoleRel permRoleRel : permRoleRels) { authzStore.store(em, permRoleRel); @@ -254,7 +256,20 @@ public class AuthorizationSeedDriver { } } - private TransactionStatus beginTransaction(String name) { + /* + * Find the associated tenant ID for this permission role instance. Uses the tenant ID found in the first role. + */ + private String getTenantId(PermissionRole pr) { + String result = null; + + // Since all the role and permission values in a PermissionRole instance *must* have the same tenant ID, we + // can just get the tenant ID from the 0th (first) role. + result = pr.getRole().get(0).getTenantId(); + + return result; + } + + private TransactionStatus beginTransaction(String name) { DefaultTransactionDefinition def = new DefaultTransactionDefinition(); // explicitly setting the transaction name is something that can only be done programmatically def.setName(name); diff --git a/services/authorization-mgt/import/src/main/java/org/collectionspace/services/authorization/importer/AuthorizationGen.java b/services/authorization-mgt/import/src/main/java/org/collectionspace/services/authorization/importer/AuthorizationGen.java index b2ec4ed95..7ead52095 100644 --- a/services/authorization-mgt/import/src/main/java/org/collectionspace/services/authorization/importer/AuthorizationGen.java +++ b/services/authorization-mgt/import/src/main/java/org/collectionspace/services/authorization/importer/AuthorizationGen.java @@ -299,12 +299,12 @@ public class AuthorizationGen { public void associateDefaultPermissionsRoles() { for (Permission p : adminPermList) { - PermissionRole permAdmRole = associatePermissionRoles(p, adminRoles, true); + PermissionRole permAdmRole = associatePermissionToRoles(p, adminRoles, true); adminPermRoleList.add(permAdmRole); } for (Permission p : readerPermList) { - PermissionRole permRdrRole = associatePermissionRoles(p, readerRoles, true); + PermissionRole permRdrRole = associatePermissionToRoles(p, readerRoles, true); readerPermRoleList.add(permRdrRole); } @@ -320,17 +320,18 @@ public class AuthorizationGen { // Now associate the tenant management perms to the role for (Permission p : tenantMgmntPermList) { // Note we enforce tenant, as should all be tenant 0 (the special one) - PermissionRole permTMRole = associatePermissionRoles(p, roles, true); + PermissionRole permTMRole = associatePermissionToRoles(p, roles, true); tenantMgmntPermRoleList.add(permTMRole); } } + @Deprecated public List associatePermissionsRoles(List perms, List roles, boolean enforceTenancy) { List result = null; List permRoles = new ArrayList(); for (Permission perm : perms) { - PermissionRole permRole = associatePermissionRoles(perm, roles, enforceTenancy); + PermissionRole permRole = associatePermissionToRoles(perm, roles, enforceTenancy); if (permRole != null) { permRoles.add(permRole); } @@ -343,19 +344,21 @@ public class AuthorizationGen { return result; } - private PermissionRole associatePermissionRoles(Permission perm, + private PermissionRole associatePermissionToRoles(Permission perm, List roles, boolean enforceTenancy) { PermissionRole result = null; PermissionRole pr = new PermissionRole(); pr.setSubject(SubjectType.ROLE); - List permValues = new ArrayList(); - pr.setPermission(permValues); + List permValueList = new ArrayList(); + pr.setPermission(permValueList); + PermissionValue permValue = new PermissionValue(); permValue.setPermissionId(perm.getCsid()); permValue.setResourceName(perm.getResourceName().toLowerCase()); permValue.setActionGroup(perm.getActionGroup()); - permValues.add(permValue); + permValue.setTenantId(perm.getTenantId()); + permValueList.add(permValue); List roleValues = new ArrayList(); for (Role role : roles) { @@ -368,6 +371,7 @@ public class AuthorizationGen { // This needs to use the qualified name, not the display name rv.setRoleName(role.getRoleName().toUpperCase()); rv.setRoleId(role.getCsid()); + rv.setTenantId(role.getTenantId()); roleValues.add(rv); } else { if (logger.isTraceEnabled() == true) { diff --git a/services/authorization-mgt/service/pom.xml b/services/authorization-mgt/service/pom.xml index 775f66a17..40e1aa423 100644 --- a/services/authorization-mgt/service/pom.xml +++ b/services/authorization-mgt/service/pom.xml @@ -186,7 +186,7 @@ org.collectionspace.services org.collectionspace.services.common - + diff --git a/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/PermissionResource.java.txt b/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/PermissionResource.java.txt new file mode 100644 index 000000000..22ac5c503 --- /dev/null +++ b/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/PermissionResource.java.txt @@ -0,0 +1,254 @@ +/** + * This document is a part of the source code and related artifacts + * for CollectionSpace, an open source collections management system + * for museums and related institutions: + + * http://www.collectionspace.org + * http://wiki.collectionspace.org + + * Copyright 2009 University of California at Berkeley + + * Licensed under the Educational Community License (ECL), Version 2.0. + * You may not use this file except in compliance with this License. + + * You may obtain a copy of the ECL 2.0 License at + + * https://source.collectionspace.org/collection-space/LICENSE.txt + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.collectionspace.services.authorization; + +import org.collectionspace.services.authorization.perms.Permission; +import org.collectionspace.services.authorization.perms.PermissionsList; +import org.collectionspace.services.authorization.storage.AuthorizationDelegate; +import org.collectionspace.services.client.CollectionSpaceClientUtils; +import org.collectionspace.services.client.PayloadOutputPart; +import org.collectionspace.services.client.PermissionClient; +import org.collectionspace.services.common.SecurityResourceBase; +import org.collectionspace.services.common.ServiceMessages; +import org.collectionspace.services.common.context.RemoteServiceContextFactory; +import org.collectionspace.services.common.context.ServiceContext; +import org.collectionspace.services.common.context.ServiceContextFactory; +import org.collectionspace.services.common.storage.StorageClient; +import org.collectionspace.services.common.storage.jpa.JpaStorageClientImpl; +import org.jboss.resteasy.util.HttpResponseCodes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriBuilder; +import javax.ws.rs.core.UriInfo; + +@Path(PermissionClient.SERVICE_PATH) +@Consumes("application/xml") +@Produces("application/xml") +public class PermissionResource extends SecurityResourceBase { + + final Logger logger = LoggerFactory.getLogger(PermissionResource.class); + final StorageClient storageClient = new JpaStorageClientImpl(); + + @Override + protected String getVersionString() { + return "$LastChangedRevision: 1165 $"; + } + + @Override + public String getServiceName() { + return PermissionClient.SERVICE_NAME; + } + + @Override + public Class getCommonPartClass() { + return Permission.class; + } + + @Override + public ServiceContextFactory getServiceContextFactory() { + return RemoteServiceContextFactory.get(); + } + + @Override + public StorageClient getStorageClient(ServiceContext ctx) { + //FIXME use ctx to identify storage client + return storageClient; + } + + @POST + public Response createPermission(Permission input) { + return create(input); + } + + public Permission createPermissionFromInstance(Permission input) { + Permission result = null; + + String permCsid = null; + Response response = createPermission(input); + if (response.getStatus() == Response.Status.CREATED.getStatusCode()) { + permCsid = CollectionSpaceClientUtils.extractId(response); + result = getPermission(permCsid); + } + + return result; + } + + @GET + @Path("{csid}") + public Permission getPermission(@PathParam("csid") String csid) { + return (Permission)get(csid, Permission.class); + } + + @GET + @Produces("application/xml") + public PermissionsList getPermissionList(@Context UriInfo ui) { + PermissionsList result = (PermissionsList)getList(ui, Permission.class); + if(logger.isTraceEnabled()) { + PayloadOutputPart ppo = new PayloadOutputPart(PermissionsList.class.getName(), result); + System.out.println(ppo.asXML()); + } + + return result; + } + + @PUT + @Path("{csid}") + public Permission updatePermission(@PathParam("csid") String csid,Permission theUpdate) { + return (Permission)update(csid, theUpdate, Permission.class); + } + + @DELETE + @Path("{csid}") + public Response deletePermission(@PathParam("csid") String csid) { + logger.debug("deletePermission with csid=" + csid); + ensureCSID(csid, ServiceMessages.DELETE_FAILED + "permission "); + try { + //FIXME ideally the following two ops should be in the same tx CSPACE-658 + //delete all relationships for this permission + PermissionRoleSubResource subResource = + new PermissionRoleSubResource(PermissionRoleSubResource.PERMISSION_PERMROLE_SERVICE); + subResource.deletePermissionRole(csid, SubjectType.ROLE); + //NOTE for delete permissions in the authz provider + //at the PermissionRoleSubResource/DocHandler level, there is no visibility + //if permission is deleted, so do it here, however, + //this is a very dangerous operation as it deletes the Spring ACL instead of ACE(s) + //the ACL might be needed for other ACEs roles... + AuthorizationDelegate.deletePermissions(csid); + + ServiceContext ctx = createServiceContext((Permission) null, Permission.class); + getStorageClient(ctx).delete(ctx, csid); + return Response.status(HttpResponseCodes.SC_OK).build(); + } catch (Exception e) { + throw bigReThrow(e, ServiceMessages.DELETE_FAILED, csid); + } + } + + @POST + @Path("{csid}/permroles") + public Response createPermissionRole(@QueryParam("_method") String method, + @PathParam("csid") String permCsid, + PermissionRole input) { + if (method != null) { + if ("delete".equalsIgnoreCase(method)) { + return deletePermissionRole(permCsid, input); + } + } + logger.debug("createPermissionRole with permCsid=" + permCsid); + ensureCSID(permCsid, ServiceMessages.POST_FAILED + "permroles permission "); + try { + PermissionRoleSubResource subResource = + new PermissionRoleSubResource(PermissionRoleSubResource.PERMISSION_PERMROLE_SERVICE); + String permrolecsid = subResource.createPermissionRole(input, SubjectType.ROLE); + UriBuilder path = UriBuilder.fromResource(PermissionResource.class); + path.path(permCsid + "/permroles/" + permrolecsid); + Response response = Response.created(path.build()).build(); + return response; + } catch (Exception e) { + throw bigReThrow(e, ServiceMessages.POST_FAILED, permCsid); + } + } + + @GET + @Path("{csid}/permroles/{id}") + public PermissionRoleRel getPermissionRole( + @PathParam("csid") String permCsid, + @PathParam("id") String permrolecsid) { + logger.debug("getPermissionRole with permCsid=" + permCsid); + ensureCSID(permCsid, ServiceMessages.GET_FAILED + "permroles permission "); + PermissionRoleRel result = null; + try { + PermissionRoleSubResource subResource = + new PermissionRoleSubResource(PermissionRoleSubResource.PERMISSION_PERMROLE_SERVICE); + //get relationships for a permission + result = subResource.getPermissionRoleRel(permCsid, SubjectType.ROLE, permrolecsid); + } catch (Exception e) { + throw bigReThrow(e, ServiceMessages.GET_FAILED, permCsid); + } + checkResult(result, permCsid, ServiceMessages.GET_FAILED); + return result; + } + + @GET + @Path("{csid}/permroles") + public PermissionRole getPermissionRole( + @PathParam("csid") String permCsid) { + logger.debug("getPermissionRole with permCsid=" + permCsid); + ensureCSID(permCsid, ServiceMessages.GET_FAILED + "permroles permission "); + PermissionRole result = null; + try { + PermissionRoleSubResource subResource = + new PermissionRoleSubResource(PermissionRoleSubResource.PERMISSION_PERMROLE_SERVICE); + //get relationships for a permission + result = subResource.getPermissionRole(permCsid, SubjectType.ROLE); + } catch (Exception e) { + throw bigReThrow(e, ServiceMessages.GET_FAILED, permCsid); + } + checkResult(result, permCsid, ServiceMessages.GET_FAILED); + return result; + } + + public Response deletePermissionRole(String permCsid, PermissionRole input) { + logger.debug("Delete payload of permrole relationships with permission permCsid=" + permCsid); + ensureCSID(permCsid, ServiceMessages.DELETE_FAILED + "permroles permission "); + try { + PermissionRoleSubResource subResource = + new PermissionRoleSubResource(PermissionRoleSubResource.PERMISSION_PERMROLE_SERVICE); + //delete all relationships for a permission + subResource.deletePermissionRole(permCsid, SubjectType.ROLE, input); + return Response.status(HttpResponseCodes.SC_OK).build(); + } catch (Exception e) { + throw bigReThrow(e, ServiceMessages.DELETE_FAILED, permCsid); + } + } + + @DELETE + @Path("{csid}/permroles") + public Response deletePermissionRole( + @PathParam("csid") String permCsid) { + logger.debug("Delete all the role relationships of the permissions with permCsid=" + permCsid); + ensureCSID(permCsid, ServiceMessages.DELETE_FAILED + "permroles permission "); + try { + PermissionRoleSubResource subResource = + new PermissionRoleSubResource(PermissionRoleSubResource.PERMISSION_PERMROLE_SERVICE); + //delete all relationships for a permission + subResource.deletePermissionRole(permCsid, SubjectType.ROLE); + return Response.status(HttpResponseCodes.SC_OK).build(); + } catch (Exception e) { + throw bigReThrow(e, ServiceMessages.DELETE_FAILED, permCsid); + } + } + +} diff --git a/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/PermissionDocumentHandler.java b/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/PermissionDocumentHandler.java.txt similarity index 100% rename from services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/PermissionDocumentHandler.java rename to services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/PermissionDocumentHandler.java.txt diff --git a/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/PermissionValidatorHandler.java b/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/PermissionValidatorHandler.java index ee1d43b71..c9628487c 100644 --- a/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/PermissionValidatorHandler.java +++ b/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/PermissionValidatorHandler.java @@ -24,7 +24,11 @@ package org.collectionspace.services.authorization.storage; +import java.util.List; + import org.collectionspace.services.authorization.perms.Permission; +import org.collectionspace.services.authorization.perms.PermissionAction; +import org.collectionspace.services.client.PermissionClient; import org.collectionspace.services.common.ServiceMessages; import org.collectionspace.services.common.context.ServiceContext; import org.collectionspace.services.common.document.DocumentHandler.Action; @@ -53,19 +57,23 @@ public class PermissionValidatorHandler implements ValidatorHandler { boolean invalid = false; if (action.equals(Action.CREATE)) { - //create specific validation here if (permission.getResourceName() == null || permission.getResourceName().isEmpty()) { invalid = true; - msgBldr.append("\nresourceName : missing or empty"); + msgBldr.append("\nThe resource name for creating a new permission resource is missing or empty."); + } else { + invalid = !validateActionFields(permission); } } else if (action.equals(Action.UPDATE)) { //update specific validation here if (permission.getResourceName() == null || permission.getResourceName().isEmpty()) { invalid = true; - msgBldr.append("\nresourceName : cannot be missing or empty"); + msgBldr.append("\nThe resource name for updating an existing permission is missing or empty."); + } else { + invalid = !validateActionFields(permission); } } + if (invalid) { String msg = msgBldr.toString(); logger.error(msg); @@ -78,4 +86,30 @@ public class PermissionValidatorHandler implements ValidatorHandler { } } + private boolean validateActionFields(Permission permission) { + boolean result = true; + + List permActionList = permission.getAction(); + boolean isPermActionListSet = (permActionList != null && permActionList.size() > 0); + + String permActionGroup = permission.getActionGroup(); + boolean isPermActionGroupSet = (permActionGroup != null && !permActionGroup.trim().isEmpty()); + + if (isPermActionListSet && isPermActionGroupSet) { + // the two action fields need to match + String derivedActionGroup = PermissionClient.getActionGroup(permActionList); + result = derivedActionGroup.equalsIgnoreCase(permActionGroup); + } else if (isPermActionListSet && !isPermActionGroupSet) { + // if Action list field is set but actionGroup field is not set then set the actionGroup by deriving it from the Action list + permission.setActionGroup(PermissionClient.getActionGroup(permActionList)); + } else if (!isPermActionListSet && isPermActionGroupSet) { + // if the action list field is not set, but the action group is set then set the action actionL + permission.setAction(PermissionClient.getActionList(permActionGroup)); + } else { + // both action fields are not set, we don't care. + } + + return result; + } + } diff --git a/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/RoleDocumentHandler.java b/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/RoleDocumentHandler.java index daeb5ecd7..a7f2b2fc1 100644 --- a/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/RoleDocumentHandler.java +++ b/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/RoleDocumentHandler.java @@ -87,34 +87,36 @@ public class RoleDocumentHandler } /** - * merge manually merges the from from to the to role + * Merge fields manually from 'from' to the 'to' role * -this method is created due to inefficiency of JPA EM merge * @param from * @param to * @return merged role */ private Role merge(Role from, Role to) throws Exception { - //role name cannot be changed + // A role's name cannot be changed if (!(from.getRoleName().equalsIgnoreCase(to.getRoleName()))) { String msg = "Role name cannot be changed " + to.getRoleName(); logger.error(msg); throw new BadRequestException(msg); } - if (from.getDisplayName() != null) { + + if (from.getDisplayName() != null && !from.getDisplayName().trim().isEmpty() ) { to.setDisplayName(from.getDisplayName()); } - if (from.getRoleGroup() != null) { + if (from.getRoleGroup() != null && !from.getRoleGroup().trim().isEmpty()) { to.setRoleGroup(from.getRoleGroup()); } - if (from.getDescription() != null) { + if (from.getDescription() != null && !from.getDescription().trim().isEmpty()) { to.setDescription(from.getDescription()); } - // Note that we do not allow update of locks + if (logger.isDebugEnabled()) { org.collectionspace.services.authorization.ObjectFactory objectFactory = new org.collectionspace.services.authorization.ObjectFactory(); - logger.debug("merged role=" + JaxbUtils.toString(objectFactory.createRole(to) ,Role.class)); + logger.debug("Merged role on update=" + JaxbUtils.toString(objectFactory.createRole(to), Role.class)); } + return to; } @@ -206,7 +208,7 @@ public class RoleDocumentHandler */ private void sanitize(Role role) { if (!SecurityUtils.isCSpaceAdmin()) { - role.setTenantId(null); + role.setTenantId(null); // REM - See no reason for hiding the tenant ID? } } diff --git a/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/RoleJpaFilter.java b/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/RoleJpaFilter.java index e40621830..25ba7f316 100644 --- a/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/RoleJpaFilter.java +++ b/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/RoleJpaFilter.java @@ -25,10 +25,11 @@ package org.collectionspace.services.authorization.storage; import java.util.ArrayList; import java.util.List; + import org.collectionspace.services.common.storage.jpa.JpaDocumentFilter; -import org.collectionspace.services.common.authorization_mgt.RoleStorageConstants; import org.collectionspace.services.common.context.ServiceContext; import org.collectionspace.services.common.security.SecurityUtils; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/services/authorization/jaxb/src/main/resources/authorization_common.xsd b/services/authorization/jaxb/src/main/resources/authorization_common.xsd index cadd94c4a..aedbb2ffc 100644 --- a/services/authorization/jaxb/src/main/resources/authorization_common.xsd +++ b/services/authorization/jaxb/src/main/resources/authorization_common.xsd @@ -55,6 +55,7 @@ + @@ -72,6 +73,7 @@ + diff --git a/services/authorization/pstore/src/main/resources/db/postgresql/authorization.sql b/services/authorization/pstore/src/main/resources/db/postgresql/authorization.sql index ac9aa3efb..c5cb4b10f 100644 --- a/services/authorization/pstore/src/main/resources/db/postgresql/authorization.sql +++ b/services/authorization/pstore/src/main/resources/db/postgresql/authorization.sql @@ -6,7 +6,7 @@ DROP TABLE IF EXISTS permissions_roles CASCADE; DROP TABLE IF EXISTS roles CASCADE; DROP SEQUENCE IF EXISTS hibernate_sequence; create table accounts_roles (HJID int8 not null, account_id varchar(128) not null, created_at timestamp not null, role_id varchar(128) not null, role_name varchar(255), screen_name varchar(255), user_id varchar(128) not null, primary key (HJID), unique (account_id, role_id)); -create table permissions (csid varchar(128) not null, action_group varchar(128), attribute_name varchar(128), created_at timestamp not null, description varchar(255), effect varchar(32) not null, resource_name varchar(128) not null, tenant_id varchar(128) not null, updated_at timestamp, primary key (csid)); +create table permissions (csid varchar(128) not null, action_group varchar(128), attribute_name varchar(128), created_at timestamp not null, description varchar(255), effect varchar(32) not null, resource_name varchar(128) not null, tenant_id varchar(128) not null, updated_at timestamp, primary key (csid), unique (resource_name, action_group, tenant_id)); create table permissions_actions (HJID int8 not null, name varchar(128) not null, objectIdentity varchar(128) not null, objectIdentityResource varchar(128) not null, ACTION__PERMISSION_CSID varchar(128), primary key (HJID)); create table permissions_roles (HJID int8 not null, actionGroup varchar(255), created_at timestamp not null, permission_id varchar(128) not null, permission_resource varchar(255), role_id varchar(128) not null, role_name varchar(255), primary key (HJID), unique (permission_id, role_id)); create table roles (csid varchar(128) not null, created_at timestamp not null, description varchar(255), displayname varchar(200) not null, rolegroup varchar(255), rolename varchar(200) not null, tenant_id varchar(128) not null, metadata_protection varchar(255), perms_protection varchar(255), updated_at timestamp, primary key (csid), unique (rolename, tenant_id), unique (displayname, tenant_id)); diff --git a/services/common/pom.xml b/services/common/pom.xml index fe74be5e6..f4220b5a9 100644 --- a/services/common/pom.xml +++ b/services/common/pom.xml @@ -98,6 +98,13 @@ org.collectionspace.services.authorization.service ${project.version} + diff --git a/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/PermissionResource.java b/services/common/src/main/java/org/collectionspace/services/authorization/PermissionResource.java similarity index 93% rename from services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/PermissionResource.java rename to services/common/src/main/java/org/collectionspace/services/authorization/PermissionResource.java index 9cb736d81..825657420 100644 --- a/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/PermissionResource.java +++ b/services/common/src/main/java/org/collectionspace/services/authorization/PermissionResource.java @@ -23,9 +23,13 @@ */ package org.collectionspace.services.authorization; +import org.collectionspace.services.authorization.PermissionRole; +import org.collectionspace.services.authorization.PermissionRoleRel; +import org.collectionspace.services.authorization.SubjectType; import org.collectionspace.services.authorization.perms.Permission; import org.collectionspace.services.authorization.perms.PermissionsList; import org.collectionspace.services.authorization.storage.AuthorizationDelegate; +import org.collectionspace.services.client.CollectionSpaceClientUtils; import org.collectionspace.services.client.PayloadOutputPart; import org.collectionspace.services.client.PermissionClient; import org.collectionspace.services.common.SecurityResourceBase; @@ -91,6 +95,19 @@ public class PermissionResource extends SecurityResourceBase { public Response createPermission(Permission input) { return create(input); } + + public Permission createPermissionFromInstance(Permission input) { + Permission result = null; + + String permCsid = null; + Response response = createPermission(input); + if (response.getStatus() == Response.Status.CREATED.getStatusCode()) { + permCsid = CollectionSpaceClientUtils.extractId(response); + result = getPermission(permCsid); + } + + return result; + } @GET @Path("{csid}") diff --git a/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/PermissionRoleSubResource.java b/services/common/src/main/java/org/collectionspace/services/authorization/PermissionRoleSubResource.java similarity index 97% rename from services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/PermissionRoleSubResource.java rename to services/common/src/main/java/org/collectionspace/services/authorization/PermissionRoleSubResource.java index 425a4d1f0..a410eb4d5 100644 --- a/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/PermissionRoleSubResource.java +++ b/services/common/src/main/java/org/collectionspace/services/authorization/PermissionRoleSubResource.java @@ -23,9 +23,12 @@ */ package org.collectionspace.services.authorization; +import org.collectionspace.services.authorization.PermissionRole; +import org.collectionspace.services.authorization.PermissionRoleRel; +import org.collectionspace.services.authorization.Role; +import org.collectionspace.services.authorization.SubjectType; import org.collectionspace.services.authorization.perms.Permission; import org.collectionspace.services.authorization.storage.PermissionRoleDocumentHandler; - import org.collectionspace.services.common.AbstractCollectionSpaceResourceImpl; import org.collectionspace.services.common.context.RemoteServiceContextFactory; import org.collectionspace.services.common.context.ServiceContext; diff --git a/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/AuthorizationDelegate.java b/services/common/src/main/java/org/collectionspace/services/authorization/storage/AuthorizationDelegate.java similarity index 97% rename from services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/AuthorizationDelegate.java rename to services/common/src/main/java/org/collectionspace/services/authorization/storage/AuthorizationDelegate.java index 2f97a8a4c..c8f863aab 100644 --- a/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/AuthorizationDelegate.java +++ b/services/common/src/main/java/org/collectionspace/services/authorization/storage/AuthorizationDelegate.java @@ -65,7 +65,7 @@ public class AuthorizationDelegate { * @throws Exception * @see PermissionRole */ - static void addPermissions(ServiceContext ctx, PermissionRole pr) throws Exception { + public static void addPermissions(ServiceContext ctx, PermissionRole pr) throws Exception { SubjectType subject = PermissionRoleUtil.getRelationSubject(ctx, pr); AuthZ authz = AuthZ.get(); if (subject.equals(SubjectType.ROLE)) { @@ -116,7 +116,7 @@ public class AuthorizationDelegate { * @param pr permissionrole * @throws Exception */ - static void deletePermissions(ServiceContext ctx, PermissionRole pr) + public static void deletePermissions(ServiceContext ctx, PermissionRole pr) throws Exception { SubjectType subject = PermissionRoleUtil.getRelationSubject(ctx, pr); AuthZ authz = AuthZ.get(); @@ -225,7 +225,7 @@ public class AuthorizationDelegate { * @see PermissionValue * @see CSpaceResource */ - private static CSpaceResource[] getResources(Permission p) { + private static CSpaceResource[] getResources(Permission p) { // REM - We could use PermissionValue instead -would save the caller from needing to go to the DB for the Permission instance List rl = new ArrayList(); for (PermissionAction pa : p.getAction()) { diff --git a/services/common/src/main/java/org/collectionspace/services/authorization/storage/PermissionDocumentHandler.java b/services/common/src/main/java/org/collectionspace/services/authorization/storage/PermissionDocumentHandler.java new file mode 100644 index 000000000..5aed20f46 --- /dev/null +++ b/services/common/src/main/java/org/collectionspace/services/authorization/storage/PermissionDocumentHandler.java @@ -0,0 +1,331 @@ +/** + * This document is a part of the source code and related artifacts + * for CollectionSpace, an open source collections management system + * for museums and related institutions: + + * http://www.collectionspace.org + * http://wiki.collectionspace.org + + * Copyright 2009 University of California at Berkeley + + * Licensed under the Educational Community License (ECL), Version 2.0. + * You may not use this file except in compliance with this License. + + * You may obtain a copy of the ECL 2.0 License at + + * https://source.collectionspace.org/collection-space/LICENSE.txt + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.collectionspace.services.authorization.storage; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import javax.persistence.EntityExistsException; +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.NoResultException; + +import org.collectionspace.services.authorization.perms.ActionType; +import org.collectionspace.services.authorization.CSpaceAction; +import org.collectionspace.services.authorization.perms.Permission; +import org.collectionspace.services.authorization.perms.PermissionAction; +import org.collectionspace.services.authorization.perms.PermissionsList; +import org.collectionspace.services.client.PermissionClient; +import org.collectionspace.services.client.PermissionClient.ActionCompare; +import org.collectionspace.services.authorization.URIResourceImpl; +import org.collectionspace.services.common.authorization_mgt.AuthorizationStore; +import org.collectionspace.services.common.document.BadRequestException; +import org.collectionspace.services.common.document.DocumentException; +import org.collectionspace.services.common.document.DocumentFilter; +import org.collectionspace.services.common.document.DocumentWrapper; +import org.collectionspace.services.common.document.JaxbUtils; +import org.collectionspace.services.common.security.SecurityUtils; +import org.collectionspace.services.common.storage.jpa.JpaDocumentHandler; +import org.collectionspace.services.common.storage.jpa.JpaStorageUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Document handler for Permission + * @author + */ +public class PermissionDocumentHandler + extends JpaDocumentHandler { + + private final Logger logger = LoggerFactory.getLogger(PermissionDocumentHandler.class); + private Permission permission; + private PermissionsList permissionsList; + + public CSpaceAction getAction(ActionType action) { + if (ActionType.CREATE.name().equals(action.name())) { + return CSpaceAction.CREATE; + } else if (ActionType.READ.equals(action)) { + return CSpaceAction.READ; + } else if (ActionType.UPDATE.equals(action)) { + return CSpaceAction.UPDATE; + } else if (ActionType.DELETE.equals(action)) { + return CSpaceAction.DELETE; + } else if (ActionType.SEARCH.equals(action)) { + return CSpaceAction.SEARCH; + } else if (ActionType.ADMIN.equals(action)) { + return CSpaceAction.ADMIN; + } else if (ActionType.START.equals(action)) { + return CSpaceAction.START; + } else if (ActionType.STOP.equals(action)) { + return CSpaceAction.STOP; + } + // + // We could not find a match, so we need to throw an exception. + // + throw new IllegalArgumentException("action = " + action.toString()); + } + + /* + * Add the ACE hashed ID to the permission action so we can map the permission to the Spring Security + * tables. + */ + private void handlePermissionActions(Permission perm) throws DocumentException { + // + // Verify the permission actions. If the action group is missing, create it from the action list and vice versa. + // + ActionCompare compareResult = PermissionClient.validatePermActions(perm); + switch (compareResult) { + case ACTIONS_MISSING: + String msg = "Permission resource encountered with missing action group and action list."; + throw new DocumentException(msg); + + case ACTION_GROUP_EMPTY: + String actionGroup = PermissionClient.getActionGroup(perm.getAction()); + perm.setActionGroup(actionGroup); + break; + + case ACTION_LIST_EMPTY: + List actionList = PermissionClient.getActionList(perm.getActionGroup()); + perm.setAction(actionList); + break; + + case MATCHES: + // all good + break; + + case MISMATCHES: + msg = String.format("Permission has mismatching action group and action list. Action group='%s' and Action list = '%s'.", + perm.getActionGroup(), PermissionClient.getActionGroup(perm.getAction())); + throw new DocumentException(msg); + } + + List permActions = perm.getAction(); + for (PermissionAction permAction : permActions) { + CSpaceAction action = getAction(permAction.getName()); + URIResourceImpl uriRes = new URIResourceImpl(perm.getTenantId(), perm.getResourceName(), action); + permAction.setObjectIdentity(uriRes.getHashedId().toString()); + permAction.setObjectIdentityResource(uriRes.getId()); + //PermissionActionUtil.update(perm, permAction); + } + } + + private Permission findExistingPermission(EntityManager em, Permission perm) { + Permission result = null; + String tenantId = getServiceContext().getTenantId(); // we need a tenant ID + + try { + result = (Permission)JpaStorageUtils.getEntityByDualKeys(em, + Permission.class.getName(), + PermissionStorageConstants.RESOURCE_NAME, perm.getResourceName(), + PermissionStorageConstants.ACTION_GROUP, perm.getActionGroup(), + tenantId); + } catch (NoResultException e) { + if (logger.isTraceEnabled()) { + String msg = String.format("Looked for but could not find permission with resource name = '%s', action group = '%s', tenat ID = '%s'.", + perm.getResourceName(), perm.getActionGroup(), tenantId); + logger.trace(msg); + } + } + + return result; + } + + @Override + public void handleCreate(DocumentWrapper wrapDoc) throws EntityExistsException, DocumentException { + // + // First check to see if an equivalent permission exists + // + Permission permission = wrapDoc.getWrappedObject(); + + EntityManager em = (EntityManager) this.getServiceContext().getProperty(AuthorizationStore.ENTITY_MANAGER_PROP_KEY); + Permission existingPermission = findExistingPermission(em, permission); + + if (existingPermission == null) { + String id = UUID.randomUUID().toString(); + permission.setCsid(id); + setTenant(permission); + handlePermissionActions(permission); + } else { + String msg = String.format("Found existing permission with resource name = '%s', action group = '%s', and tenant ID = '%s'.", + existingPermission.getResourceName(), existingPermission.getActionGroup(), existingPermission.getTenantId()); + wrapDoc.resetWrapperObject(existingPermission); // update the wrapped document with the existing permission instance + throw new EntityExistsException(msg); + } + } + + @Override + public void completeCreate(DocumentWrapper wrapDoc) throws Exception { + } + + @Override + public void handleUpdate(DocumentWrapper wrapDoc) throws Exception { + Permission permissionFound = wrapDoc.getWrappedObject(); + Permission permissionReceived = getCommonPart(); + merge(permissionReceived, permissionFound); + } + + /** + * merge manually merges the from from to the to permission + * -this method is created due to inefficiency of JPA EM merge + * @param from + * @param to + * @return merged permission + */ + private Permission merge(Permission from, Permission to) throws Exception { + if (!(from.getResourceName().equalsIgnoreCase(to.getResourceName()))) { + String msg = "Resource name cannot be changed " + to.getResourceName(); + logger.error(msg); + throw new BadRequestException(msg); + } + //resource name, attribute cannot be changed + + if (from.getDescription() != null) { + to.setDescription(from.getDescription()); + } + if (from.getEffect() != null) { + to.setEffect(from.getEffect()); + } + List fromActions = from.getAction(); + if (!fromActions.isEmpty()) { + // Override the whole list, no reconciliation by design + to.setAction(fromActions); + // Update the actionGroup field to reflect the new action list + to.setActionGroup(PermissionClient.getActionGroup(fromActions)); + } + + if (logger.isDebugEnabled()) { + logger.debug("merged permission=" + JaxbUtils.toString(to, Permission.class)); + } + + handlePermissionActions(to); + return to; + } + + @Override + public void completeUpdate(DocumentWrapper wrapDoc) throws Exception { + Permission upAcc = wrapDoc.getWrappedObject(); + getServiceContext().setOutput(upAcc); + sanitize(upAcc); + //FIXME update lower-layer authorization (acls) + //will require deleting old permissions for this resource and adding + //new based on new actions and effect + } + + @Override + public void handleGet(DocumentWrapper wrapDoc) throws Exception { + setCommonPart(extractCommonPart(wrapDoc)); + sanitize(getCommonPart()); + getServiceContext().setOutput(permission); + } + + @Override + public void handleGetAll(DocumentWrapper wrapDoc) throws Exception { + PermissionsList permissionsList = extractCommonPartList(wrapDoc); + setCommonPartList(permissionsList); + getServiceContext().setOutput(getCommonPartList()); + } + + @Override + public void completeDelete(DocumentWrapper wrapDoc) throws Exception { + } + + @Override + public Permission extractCommonPart( + DocumentWrapper wrapDoc) + throws Exception { + return wrapDoc.getWrappedObject(); + } + + @Override + public void fillCommonPart(Permission obj, DocumentWrapper wrapDoc) + throws Exception { + throw new UnsupportedOperationException("operation not relevant for AccountDocumentHandler"); + } + + @Override + public PermissionsList extractCommonPartList( + DocumentWrapper wrapDoc) + throws Exception { + + PermissionsList permissionsList = new PermissionsList(); + List list = new ArrayList(); + permissionsList.setPermission(list); + for (Object obj : wrapDoc.getWrappedObject()) { + Permission permission = (Permission) obj; + sanitize(permission); + list.add(permission); + } + return permissionsList; + } + + @Override + public Permission getCommonPart() { + return permission; + } + + @Override + public void setCommonPart(Permission permission) { + this.permission = permission; + } + + @Override + public PermissionsList getCommonPartList() { + return permissionsList; + } + + @Override + public void setCommonPartList(PermissionsList permissionsList) { + this.permissionsList = permissionsList; + } + + @Override + public String getQProperty( + String prop) { + return null; + } + + @Override + public DocumentFilter createDocumentFilter() { + DocumentFilter filter = new PermissionJpaFilter(this.getServiceContext()); + return filter; + } + + /** + * Sanitize removes data not needed to be sent to the consumer + * @param permission + */ + private void sanitize(Permission permission) { + if (!SecurityUtils.isCSpaceAdmin()) { + // permission.setTenantId(null); // REM - Why are we removing the tenant ID from the payload? Commenting out this line for now. + } + } + + private void setTenant(Permission permission) { + //set tenant only if not available from input + if (permission.getTenantId() == null || permission.getTenantId().isEmpty()) { + permission.setTenantId(getServiceContext().getTenantId()); + } + } +} diff --git a/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/PermissionJpaFilter.java b/services/common/src/main/java/org/collectionspace/services/authorization/storage/PermissionJpaFilter.java similarity index 99% rename from services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/PermissionJpaFilter.java rename to services/common/src/main/java/org/collectionspace/services/authorization/storage/PermissionJpaFilter.java index 92f8cfacc..374f9721a 100644 --- a/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/PermissionJpaFilter.java +++ b/services/common/src/main/java/org/collectionspace/services/authorization/storage/PermissionJpaFilter.java @@ -26,6 +26,7 @@ package org.collectionspace.services.authorization.storage; import java.util.ArrayList; import java.util.List; + import org.collectionspace.services.common.context.ServiceContext; import org.collectionspace.services.common.security.SecurityUtils; import org.collectionspace.services.common.storage.jpa.JpaDocumentFilter; diff --git a/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/PermissionRoleDocumentHandler.java b/services/common/src/main/java/org/collectionspace/services/authorization/storage/PermissionRoleDocumentHandler.java similarity index 99% rename from services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/PermissionRoleDocumentHandler.java rename to services/common/src/main/java/org/collectionspace/services/authorization/storage/PermissionRoleDocumentHandler.java index 5ed610a13..c394a0eee 100644 --- a/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/PermissionRoleDocumentHandler.java +++ b/services/common/src/main/java/org/collectionspace/services/authorization/storage/PermissionRoleDocumentHandler.java @@ -32,6 +32,7 @@ import org.collectionspace.services.authorization.PermissionValue; import org.collectionspace.services.authorization.PermissionsRolesList; import org.collectionspace.services.authorization.RoleValue; import org.collectionspace.services.authorization.SubjectType; + import org.collectionspace.services.common.authorization_mgt.AuthorizationRoleRel; import org.collectionspace.services.common.authorization_mgt.PermissionRoleUtil; import org.collectionspace.services.common.document.DocumentFilter; @@ -206,7 +207,9 @@ public class PermissionRoleDocumentHandler } else { //subject mismatch should have been checked during validation } - PermissionRoleUtil.buildPermissionRoleRel(pr, subject, prrl, handleDelete); + + String tenantId = this.getServiceContext().getTenantId(); + PermissionRoleUtil.buildPermissionRoleRel(pr, subject, prrl, handleDelete, tenantId); } /* (non-Javadoc) diff --git a/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/PermissionStorageConstants.java b/services/common/src/main/java/org/collectionspace/services/authorization/storage/PermissionStorageConstants.java similarity index 100% rename from services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/PermissionStorageConstants.java rename to services/common/src/main/java/org/collectionspace/services/authorization/storage/PermissionStorageConstants.java diff --git a/services/common/src/main/java/org/collectionspace/services/common/authorization_mgt/RoleStorageConstants.java b/services/common/src/main/java/org/collectionspace/services/authorization/storage/RoleStorageConstants.java similarity index 95% rename from services/common/src/main/java/org/collectionspace/services/common/authorization_mgt/RoleStorageConstants.java rename to services/common/src/main/java/org/collectionspace/services/authorization/storage/RoleStorageConstants.java index 7a1b47157..ffc216f1c 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/authorization_mgt/RoleStorageConstants.java +++ b/services/common/src/main/java/org/collectionspace/services/authorization/storage/RoleStorageConstants.java @@ -22,7 +22,7 @@ * limitations under the License. */ -package org.collectionspace.services.common.authorization_mgt; +package org.collectionspace.services.authorization.storage; /** * RoleStorageConstants declares query params, etc. diff --git a/services/common/src/main/java/org/collectionspace/services/common/ServiceMain.java b/services/common/src/main/java/org/collectionspace/services/common/ServiceMain.java index 0eeda956a..ccb118ef9 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/ServiceMain.java +++ b/services/common/src/main/java/org/collectionspace/services/common/ServiceMain.java @@ -18,8 +18,11 @@ import javax.sql.DataSource; import org.apache.commons.io.FileUtils; import org.apache.tomcat.dbcp.dbcp.BasicDataSource; + import org.collectionspace.authentication.AuthN; + import org.collectionspace.services.client.XmlTools; + import org.collectionspace.services.common.api.JEEServerDeployment; import org.collectionspace.services.common.api.FileTools; import org.collectionspace.services.common.api.Tools; @@ -30,10 +33,11 @@ import org.collectionspace.services.common.config.ServicesConfigReaderImpl; import org.collectionspace.services.common.config.TenantBindingConfigReaderImpl; import org.collectionspace.services.common.context.ServiceBindingUtils; import org.collectionspace.services.common.init.AddIndices; -import org.collectionspace.services.config.service.InitHandler.Params.Field; import org.collectionspace.services.common.init.IInitHandler; import org.collectionspace.services.common.storage.DatabaseProductType; import org.collectionspace.services.common.storage.JDBCTools; + +import org.collectionspace.services.config.service.InitHandler.Params.Field; import org.collectionspace.services.config.ClientType; import org.collectionspace.services.config.ServiceConfig; import org.collectionspace.services.config.service.ServiceBindingType; @@ -43,10 +47,12 @@ import org.collectionspace.services.config.tenant.RepositoryDomainType; import org.collectionspace.services.config.tenant.TenantBindingType; import org.collectionspace.services.config.types.PropertyItemType; import org.collectionspace.services.config.types.PropertyType; + import org.collectionspace.services.nuxeo.client.java.NuxeoConnectorEmbedded; import org.collectionspace.services.nuxeo.client.java.TenantRepository; import org.collectionspace.services.nuxeo.listener.CSEventListener; import org.collectionspace.services.nuxeo.listener.AbstractCSEventListenerImpl; + import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.dom4j.Document; import org.slf4j.Logger; diff --git a/services/common/src/main/java/org/collectionspace/services/common/authorization_mgt/AuthorizationCommon.java b/services/common/src/main/java/org/collectionspace/services/common/authorization_mgt/AuthorizationCommon.java index c9376c268..6e686f064 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/authorization_mgt/AuthorizationCommon.java +++ b/services/common/src/main/java/org/collectionspace/services/common/authorization_mgt/AuthorizationCommon.java @@ -41,6 +41,7 @@ import org.collectionspace.services.authorization.perms.PermissionAction; import org.collectionspace.services.client.Profiler; import org.collectionspace.services.client.RoleClient; import org.collectionspace.services.client.workflow.WorkflowClient; + import org.collectionspace.services.common.config.ServiceConfigUtils; import org.collectionspace.services.common.config.TenantBindingConfigReaderImpl; import org.collectionspace.services.common.context.ServiceBindingUtils; @@ -49,10 +50,12 @@ import org.collectionspace.services.common.security.SecurityUtils; import org.collectionspace.services.common.storage.DatabaseProductType; import org.collectionspace.services.common.storage.JDBCTools; import org.collectionspace.services.common.storage.jpa.JpaStorageUtils; + import org.collectionspace.services.config.service.ServiceBindingType; import org.collectionspace.services.config.tenant.EmailConfig; import org.collectionspace.services.config.tenant.PasswordResetConfig; import org.collectionspace.services.config.tenant.TenantBindingType; + import org.collectionspace.services.lifecycle.Lifecycle; import org.collectionspace.services.lifecycle.TransitionDef; import org.collectionspace.services.lifecycle.TransitionDefList; @@ -143,6 +146,8 @@ public class AuthorizationCommon { final private static String GET_TENANT_MGR_ROLE_SQL = "SELECT csid from roles WHERE tenant_id='" + AuthN.ALL_TENANTS_MANAGER_TENANT_ID + "' and rolename=?"; + public static final String IGNORE_TENANT_ID = null; // A null constant to indicate an empty/unused value for the tenant ID + public static Role getRole(String tenantId, String displayName) { Role role = null; @@ -1066,6 +1071,8 @@ public class AuthorizationCommon { public static void createDefaultWorkflowPermissions(TenantBindingConfigReaderImpl tenantBindingConfigReader) throws Exception //FIXME: REM - 4/11/2012 - Rename to createWorkflowPermissions { + java.util.logging.Logger logger = java.util.logging.Logger.getAnonymousLogger(); + AuthZ.get().login(); //login to Spring Security manager EntityManagerFactory emf = JpaStorageUtils.getEntityManagerFactory(JpaStorageUtils.CS_PERSISTENCE_UNIT); @@ -1084,7 +1091,7 @@ public class AuthorizationCommon { if (adminRole == null || readonlyRole == null) { String msg = String.format("One or more of the required default CollectionSpace administrator roles is missing or was never created. If you're setting up a new instance of CollectionSpace, shutdown the Tomcat server and run the 'ant import' command from the root/top level CollectionSpace 'Services' source directory. Then try restarting Tomcat."); - logger.error(msg); + logger.info(msg); throw new RuntimeException("One or more of the required default CollectionSpace administrator roles is missing or was never created."); } @@ -1107,10 +1114,10 @@ public class AuthorizationCommon { } em.getTransaction().commit(); } catch (IllegalStateException e) { - logger.debug(e.getLocalizedMessage(), e); //We end up here if there is no document handler for the service -this is ok for some of the services. + logger.fine(e.getLocalizedMessage()); //We end up here if there is no document handler for the service -this is ok for some of the services. } } else { - logger.warn("AuthZ refresh service binding property is set to FALSE so default permissions will NOT be refreshed for: " + logger.warning("AuthZ refresh service binding property is set to FALSE so default permissions will NOT be refreshed for: " + serviceBinding.getName()); } } @@ -1120,9 +1127,7 @@ public class AuthorizationCommon { if (em != null && em.getTransaction().isActive()) { em.getTransaction().rollback(); } - if (logger.isDebugEnabled()) { - logger.debug("Caught exception and rolling back permission creation: ", e); - } + logger.fine("Caught exception and rolling back permission creation: " + e.getMessage()); throw e; } finally { if (em != null) { @@ -1163,7 +1168,8 @@ public class AuthorizationCommon { if (permRoleRel == null) { PermissionRole permRole = createPermissionRole(em, permission, role, enforceTenancy); List permRoleRels = new ArrayList(); - PermissionRoleUtil.buildPermissionRoleRel(em, permRole, SubjectType.ROLE, permRoleRels, false /*not for delete*/); + PermissionRoleUtil.buildPermissionRoleRel(em, permRole, SubjectType.ROLE, permRoleRels, + false /*not for delete*/, role.getTenantId()); for (PermissionRoleRel prr : permRoleRels) { authzStore.store(em, prr); } diff --git a/services/common/src/main/java/org/collectionspace/services/common/authorization_mgt/AuthorizationStore.java b/services/common/src/main/java/org/collectionspace/services/common/authorization_mgt/AuthorizationStore.java index 7aff76a86..be9c907af 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/authorization_mgt/AuthorizationStore.java +++ b/services/common/src/main/java/org/collectionspace/services/common/authorization_mgt/AuthorizationStore.java @@ -34,7 +34,7 @@ import javax.persistence.EntityManagerFactory; import org.collectionspace.services.authorization.Role; import org.collectionspace.services.authorization.PermissionRoleRel; import org.collectionspace.services.authorization.perms.Permission; -import org.collectionspace.services.common.authorization_mgt.RoleStorageConstants; +import org.collectionspace.services.authorization.storage.RoleStorageConstants; import org.collectionspace.services.common.document.JaxbUtils; import org.collectionspace.services.common.storage.jpa.JpaStorageUtils; import org.slf4j.Logger; @@ -48,6 +48,7 @@ public class AuthorizationStore { private static final Logger logger = LoggerFactory.getLogger(AuthorizationStore.class); private final static String PERSISTENCE_UNIT = "org.collectionspace.services.authorization"; + public final static String ENTITY_MANAGER_PROP_KEY = EntityManager.class.getCanonicalName(); static public Role getRoleByName(String roleName, String tenantId) { Role theRole = null; diff --git a/services/common/src/main/java/org/collectionspace/services/common/authorization_mgt/PermissionRoleUtil.java b/services/common/src/main/java/org/collectionspace/services/common/authorization_mgt/PermissionRoleUtil.java index f55daf08d..125662130 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/authorization_mgt/PermissionRoleUtil.java +++ b/services/common/src/main/java/org/collectionspace/services/common/authorization_mgt/PermissionRoleUtil.java @@ -23,23 +23,28 @@ */ package org.collectionspace.services.common.authorization_mgt; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; - import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; +import javax.persistence.NoResultException; +import org.collectionspace.services.common.document.DocumentException; import org.collectionspace.services.common.document.DocumentNotFoundException; import org.collectionspace.services.common.context.ServiceContext; import org.collectionspace.services.common.context.ServiceContextProperties; -import org.collectionspace.services.common.storage.jpa.JpaRelationshipStorageClient; import org.collectionspace.services.common.storage.jpa.JpaStorageUtils; +import org.collectionspace.services.authorization.perms.ActionType; +import org.collectionspace.services.authorization.perms.EffectType; import org.collectionspace.services.authorization.perms.Permission; +import org.collectionspace.services.authorization.perms.PermissionAction; +import org.collectionspace.services.authorization.storage.PermissionStorageConstants; +import org.collectionspace.services.authorization.PermissionResource; import org.collectionspace.services.authorization.PermissionRole; import org.collectionspace.services.authorization.PermissionRoleRel; import org.collectionspace.services.authorization.PermissionValue; -import org.collectionspace.services.authorization.Role; import org.collectionspace.services.authorization.RoleValue; import org.collectionspace.services.authorization.SubjectType; @@ -101,13 +106,15 @@ public class PermissionRoleUtil { PermissionRole pr, SubjectType subject, List prrl, - boolean handleDelete) throws Exception { + boolean handleDelete, + String tenantId) throws Exception { + if (subject.equals(SubjectType.ROLE)) { List permissionValues = pr.getPermission(); if (permissionValues != null && permissionValues.size() > 0) { PermissionValue pv = permissionValues.get(0); for (RoleValue rv : pr.getRole()) { - PermissionRoleRel prr = buildPermissonRoleRel(em, pv, rv, subject, handleDelete); + PermissionRoleRel prr = buildPermissonRoleRel(em, pv, rv, subject, handleDelete, tenantId); prrl.add(prr); } } @@ -116,7 +123,7 @@ public class PermissionRoleUtil { if (roleValues != null && roleValues.size() > 0) { RoleValue rv = roleValues.get(0); for (PermissionValue pv : pr.getPermission()) { - PermissionRoleRel prr = buildPermissonRoleRel(em, pv, rv, subject, handleDelete); + PermissionRoleRel prr = buildPermissonRoleRel(em, pv, rv, subject, handleDelete, tenantId); prrl.add(prr); } } @@ -127,7 +134,8 @@ public class PermissionRoleUtil { PermissionRole pr, SubjectType subject, List prrl, - boolean handleDelete) throws Exception { + boolean handleDelete, + String tenantId) throws Exception { EntityManagerFactory emf = null; EntityManager em = null; try { @@ -135,7 +143,7 @@ public class PermissionRoleUtil { em = emf.createEntityManager(); em.getTransaction().begin(); - buildPermissionRoleRel(em, pr, subject, prrl, handleDelete); + buildPermissionRoleRel(em, pr, subject, prrl, handleDelete, tenantId); em.getTransaction().commit(); em.close(); @@ -154,6 +162,52 @@ public class PermissionRoleUtil { } } + /* + * Try to find a persisted Permission record using a PermissionValue instance. + * + */ + static private Permission lookupPermission(EntityManager em, PermissionValue permissionValue, String tenantId) { + Permission result = null; + + String actionGroup = permissionValue.getActionGroup() != null ? permissionValue.getActionGroup().trim() : null; + String resourceName = permissionValue.getResourceName() != null ? permissionValue.getResourceName().trim() : null; + String permissionId = permissionValue.getPermissionId() != null ? permissionValue.getPermissionId().trim() : null; + // + // If we have a permission ID, use it to try to lookup the persisted permission + // + if (permissionId != null && !permissionId.isEmpty()) { + try { + result = (Permission)JpaStorageUtils.getEntity(em, permissionId, Permission.class); + } catch (Throwable e) { + String msg = String.format("Searched for but couldn't find a permission with CSID='%s'.", + permissionId); + logger.trace(msg); + } + } else if ((resourceName != null && !resourceName.isEmpty()) && + (actionGroup != null && !actionGroup.isEmpty())) { + // + // If there was no permission ID, then we can try to find the permission with the resource name and action group tuple + // + try { + result = (Permission)JpaStorageUtils.getEntityByDualKeys(em, + Permission.class.getName(), + PermissionStorageConstants.RESOURCE_NAME, permissionValue.getResourceName(), + PermissionStorageConstants.ACTION_GROUP, permissionValue.getActionGroup(), + tenantId); + } catch (NoResultException e) { + String msg = String.format("Searched for but couldn't find a permission for resource='%s', action group='%s', and tenant ID='%s'.", + permissionValue.getResourceName(), permissionValue.getActionGroup(), tenantId); + logger.trace(msg); + } + } else { + String errMsg = String.format("Couldn't perform lookup of permission. Not enough information provided. Lookups requires a permission CSID or a resource name and action group tuple. The provided information was permission ID='%s', resourceName='%s', and actionGroup='%s'.", + permissionId, resourceName, actionGroup); + logger.warn(errMsg); + } + + return result; + } + /** * Builds a permisson role relationship for either 'create' or 'delete' * @@ -161,65 +215,45 @@ public class PermissionRoleUtil { * @param rv the rv (currently using only the ID) * @param handleDelete the handle delete * @return the permission role rel + * @throws DocumentException */ static private PermissionRoleRel buildPermissonRoleRel(EntityManager em, PermissionValue permissionValue, RoleValue roleValue, SubjectType subject, - boolean handleDelete) - throws DocumentNotFoundException { + boolean handleDelete, // if 'true' then we're deleting not building a permission-role record + String tenantId) throws DocumentException { PermissionRoleRel result = null; + Permission permission = lookupPermission(em, permissionValue, tenantId); // - // Ensure we can find both the Permission and Role to relate. - // FIXME: REM - This is a workaround until the Import utility creates Perm/Role relationships - // correctly. The import utility should create and store the permissions and roles BEFORE creating the relationships + // If we couldn't find an existing permission and we're not processing a DELETE request, we need to create + // a new permission. // - PermissionValue pv = permissionValue; - - // - // This lookup is slow, do we really need it? - // - /* - try { - Permission permission = (Permission)JpaStorageUtils.getEntity(em, pv.getPermissionId(), //FIXME: REM 4/5/2012 - To improve performance, we should use a passed in Permission instance - Permission.class); - if (permission != null) { - // If the permission already exists, then use it to fill our the relation record - pv = JpaRelationshipStorageClient.createPermissionValue(permission); - } - } catch (DocumentNotFoundException e) { - // ignore this exception, pv is set to permissionValue; + if (permission == null && handleDelete == false) { + permission = new Permission(); + permission.setResourceName(permissionValue.getResourceName()); + permission.setActionGroup(permissionValue.getActionGroup()); + permission.setEffect(EffectType.PERMIT); // By default, CollectionSpace currently (11/2017) supports only PERMIT + List actionList = createPermActionList(permissionValue.getActionGroup()); + permission.setAction(actionList); + permission = createPermission(permission); + if (permission == null) { + String errMsg = "Could not create new permission for new permission-role relationship."; + throw new DocumentException(errMsg); + } } - */ // - // Ensure we can find both the Permission and Role to relate. - // FIXME: REM - This is a workaround until the Import utility creates Perm/Role relationships - // correctly. The import utility should create and store the permissions and roles BEFORE creating the relationships + // Create the permission-role to persist // - RoleValue rv = roleValue; - - /* - * This lookup is slow, can we avoid it? - try { - Role role = (Role)JpaStorageUtils.getEntity(em, rv.getRoleId(), - Role.class); - if (role != null) { - // If the role already exists, then use it to fill out the relation record - rv = JpaRelationshipStorageClient.createRoleValue(role); - } - } catch (DocumentNotFoundException e) { - // ignore this exception, rv is set to roleValue - } - */ - result = new PermissionRoleRel(); - result.setPermissionId(pv.getPermissionId()); - result.setPermissionResource(pv.getResourceName()); - result.setActionGroup(pv.getActionGroup()); - result.setRoleId(rv.getRoleId()); - result.setRoleName(rv.getRoleName()); + result.setPermissionId(permission.getCsid()); + result.setPermissionResource(permission.getResourceName()); + result.setActionGroup(permission.getActionGroup()); + result.setRoleId(roleValue.getRoleId()); + result.setRoleName(roleValue.getRoleName()); + // // For 'delete' we need to set the hjid of the existing relstionship // @@ -236,7 +270,58 @@ public class PermissionRoleUtil { return result; } - /** + private static Permission createPermission(Permission permission) { + Permission result = null; + + PermissionResource permissionResource = new PermissionResource(); // Get the PermissionResource singleton instance (RESTEasy ensures it is a singleton) + result = permissionResource.createPermissionFromInstance(permission); + + return result; + } + + private static List createPermActionList(String actionGroup) throws DocumentException { + ArrayList result = new ArrayList(); + + for (char c : actionGroup.toUpperCase().toCharArray()) { + PermissionAction permAction = new PermissionAction(); + switch (c) { + case 'C': + permAction.setName(ActionType.CREATE); + break; + + case 'R': + permAction.setName(ActionType.READ); + break; + + case 'U': + permAction.setName(ActionType.UPDATE); + break; + + case 'D': + permAction.setName(ActionType.DELETE); + break; + + case 'L': + permAction.setName(ActionType.SEARCH); + break; + + default: + String errMsg = String.format("Illegal action group token '%c' in permission action group '%s'.", + c, actionGroup); + throw new DocumentException(errMsg); + } + + if (result.add(permAction) == false) { + String warnMsg = String.format("Illegal or duplicate action group token '%c' in permission action group '%s'.", + c, actionGroup); + logger.warn(warnMsg); + } + } + + return result; + } + + /** * Checks if is invalid tenant. * * @param tenantId the tenant id diff --git a/services/common/src/main/java/org/collectionspace/services/common/document/DocumentWrapper.java b/services/common/src/main/java/org/collectionspace/services/common/document/DocumentWrapper.java index b5ca45f1e..315d3bf40 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/document/DocumentWrapper.java +++ b/services/common/src/main/java/org/collectionspace/services/common/document/DocumentWrapper.java @@ -36,5 +36,6 @@ public interface DocumentWrapper { * @return wrapped object */ public T getWrappedObject(); + public T resetWrapperObject(T newObject); } diff --git a/services/common/src/main/java/org/collectionspace/services/common/document/DocumentWrapperImpl.java b/services/common/src/main/java/org/collectionspace/services/common/document/DocumentWrapperImpl.java index e3083b79e..5f7de8972 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/document/DocumentWrapperImpl.java +++ b/services/common/src/main/java/org/collectionspace/services/common/document/DocumentWrapperImpl.java @@ -32,7 +32,14 @@ public class DocumentWrapperImpl implements DocumentWrapper{ this.wrappedObject = obj; } - public T getWrappedObject() { + @Override + public T getWrappedObject() { return wrappedObject; } + + @Override + public T resetWrapperObject(T newObject) { + wrappedObject = newObject; + return wrappedObject; + } } \ No newline at end of file diff --git a/services/common/src/main/java/org/collectionspace/services/common/storage/jpa/JpaRelationshipStorageClient.java b/services/common/src/main/java/org/collectionspace/services/common/storage/jpa/JpaRelationshipStorageClient.java index 4c52dba95..ad86f0088 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/storage/jpa/JpaRelationshipStorageClient.java +++ b/services/common/src/main/java/org/collectionspace/services/common/storage/jpa/JpaRelationshipStorageClient.java @@ -413,7 +413,7 @@ public class JpaRelationshipStorageClient extends JpaStorageClientImpl { em.remove(getRelationship(em, r)); } em.getTransaction().commit(); - handler.complete(Action.DELETE, wrapDoc); + handler.complete(Action.DELETE, wrapDoc); // Delete from the Spring Security tables. Would be better if this was part of the earlier transaction. } catch (DocumentException de) { if (em != null && em.getTransaction().isActive()) { em.getTransaction().rollback(); diff --git a/services/common/src/main/java/org/collectionspace/services/common/storage/jpa/JpaStorageClientImpl.java b/services/common/src/main/java/org/collectionspace/services/common/storage/jpa/JpaStorageClientImpl.java index 759719ffb..334ce0650 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/storage/jpa/JpaStorageClientImpl.java +++ b/services/common/src/main/java/org/collectionspace/services/common/storage/jpa/JpaStorageClientImpl.java @@ -24,6 +24,7 @@ import javax.persistence.RollbackException; import java.sql.BatchUpdateException; +import javax.persistence.EntityExistsException; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.Query; @@ -42,6 +43,7 @@ import org.collectionspace.services.common.storage.StorageClient; import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.AuthorityItemSpecifier; import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.Specifier; import org.collectionspace.services.common.context.ServiceContextProperties; +import org.collectionspace.services.common.authorization_mgt.AuthorizationStore; import org.collectionspace.services.common.context.ServiceContext; import org.collectionspace.services.common.query.QueryContext; import org.collectionspace.services.lifecycle.TransitionDef; @@ -105,7 +107,6 @@ public class JpaStorageClientImpl implements StorageClient { public String create(ServiceContext ctx, DocumentHandler handler) throws BadRequestException, DocumentException { - boolean rollbackTransaction = false; if (ctx == null) { throw new IllegalArgumentException( "create: ctx is missing"); @@ -114,18 +115,29 @@ public class JpaStorageClientImpl implements StorageClient { throw new IllegalArgumentException( "create: handler is missing"); } + + boolean rollbackTransaction = false; EntityManagerFactory emf = null; EntityManager em = null; try { handler.prepare(Action.CREATE); Object entity = handler.getCommonPart(); DocumentWrapper wrapDoc = new DocumentWrapperImpl(entity); - handler.handle(Action.CREATE, wrapDoc); - JaxbUtils.setValue(entity, "setCreatedAtItem", Date.class, new Date()); - emf = JpaStorageUtils.getEntityManagerFactory(); + + emf = JpaStorageUtils.getEntityManagerFactory(); em = emf.createEntityManager(); - em.getTransaction().begin(); { //begin of transaction block - em.persist(entity); + em.getTransaction().begin(); { //begin of transaction block + ctx.setProperty(AuthorizationStore.ENTITY_MANAGER_PROP_KEY, em); + try { + handler.handle(Action.CREATE, wrapDoc); + JaxbUtils.setValue(entity, "setCreatedAtItem", Date.class, new Date()); + em.persist(entity); + } catch (EntityExistsException ee) { + // + // We found an existing matching entity in the store, so we don't need to create one. Just update the transient 'entity' instance with the existing persisted entity we found. + // + entity = wrapDoc.getWrappedObject(); // the handler should have reset the wrapped transient object with the existing persisted entity we just found. + } } em.getTransaction().commit(); handler.complete(Action.CREATE, wrapDoc); @@ -143,6 +155,7 @@ public class JpaStorageClientImpl implements StorageClient { } throw DocumentException.createDocumentException(e); } finally { + ctx.setProperty(AuthorizationStore.ENTITY_MANAGER_PROP_KEY, null); if (em != null) { if (rollbackTransaction == true) { if (em.getTransaction().isActive() == true) { @@ -493,7 +506,6 @@ public class JpaStorageClientImpl implements StorageClient { handler.handle(Action.DELETE, wrapDoc); em.remove(entityFound); em.getTransaction().commit(); - handler.complete(Action.DELETE, wrapDoc); } catch (DocumentException de) { if (em != null && em.getTransaction().isActive()) { diff --git a/services/common/src/main/java/org/collectionspace/services/common/storage/jpa/JpaStorageUtils.java b/services/common/src/main/java/org/collectionspace/services/common/storage/jpa/JpaStorageUtils.java index 5e9d750fc..fa459de0b 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/storage/jpa/JpaStorageUtils.java +++ b/services/common/src/main/java/org/collectionspace/services/common/storage/jpa/JpaStorageUtils.java @@ -75,15 +75,21 @@ public class JpaStorageUtils { boolean result = true; boolean csAdmin = SecurityUtils.isCSpaceAdmin(); - if (csAdmin == true || tenantId == null) { + if (csAdmin == true) { + logger.trace("Running as the CSAdmin user."); + //Thread.dumpStack(); + } + + if (tenantId == null) { result = false; + logger.trace("Ignoring tenant ID during ."); + //Thread.dumpStack(); } return result; } - public static Object getEntity(String id, Class entityClazz) - throws DocumentNotFoundException { + public static Object getEntity(String id, Class entityClazz) { EntityManagerFactory emf = null; EntityManager em = null; Object entityFound = null; @@ -92,6 +98,8 @@ public class JpaStorageUtils { em = emf.createEntityManager(); //FIXME: it would be nice to verify tenantid as well entityFound = em.find(entityClazz, id); + } catch (Throwable t) { + throw t; } finally { if (em != null) { releaseEntityManagerFactory(emf); diff --git a/services/hyperjaxb/src/main/resources/permissions.xsd b/services/hyperjaxb/src/main/resources/permissions.xsd index 35161ad46..7b3051feb 100644 --- a/services/hyperjaxb/src/main/resources/permissions.xsd +++ b/services/hyperjaxb/src/main/resources/permissions.xsd @@ -36,7 +36,14 @@ - + + + + resource_name + action_group + tenant_id + + -- 2.47.3