2 * This document is a part of the source code and related artifacts
3 * for CollectionSpace, an open source collections management system
4 * for museums and related institutions:
6 * http://www.collectionspace.org
7 * http://wiki.collectionspace.org
9 * Copyright 2009 University of California at Berkeley
11 * Licensed under the Educational Community License (ECL), Version 2.0.
12 * You may not use this file except in compliance with this License.
14 * You may obtain a copy of the ECL 2.0 License at
16 * https://source.collectionspace.org/collection-space/LICENSE.txt
18 * Unless required by applicable law or agreed to in writing, software
19 * distributed under the License is distributed on an "AS IS" BASIS,
20 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21 * See the License for the specific language governing permissions and
22 * limitations under the License.
24 package org.collectionspace.services.common.authorization_mgt;
26 import java.util.ArrayList;
27 import java.util.HashMap;
28 import java.util.List;
29 import javax.persistence.NoResultException;
31 import org.collectionspace.services.common.document.DocumentException;
32 import org.collectionspace.services.common.document.DocumentNotFoundException;
33 import org.collectionspace.services.common.document.JaxbUtils;
34 import org.collectionspace.services.common.api.Tools;
35 import org.collectionspace.services.common.context.ServiceContext;
36 import org.collectionspace.services.common.context.ServiceContextProperties;
37 import org.collectionspace.services.common.storage.jpa.JPATransactionContext;
38 import org.collectionspace.services.common.storage.jpa.JpaStorageUtils;
40 import org.collectionspace.services.client.RoleClient;
41 import org.collectionspace.authentication.AuthN;
43 import org.collectionspace.services.authorization.perms.ActionType;
44 import org.collectionspace.services.authorization.perms.EffectType;
45 import org.collectionspace.services.authorization.perms.Permission;
46 import org.collectionspace.services.authorization.perms.PermissionAction;
47 import org.collectionspace.services.authorization.storage.PermissionStorageConstants;
48 import org.collectionspace.services.authorization.storage.RoleStorageConstants;
49 import org.collectionspace.services.authorization.PermissionResource;
50 import org.collectionspace.services.authorization.PermissionRole;
51 import org.collectionspace.services.authorization.PermissionRoleRel;
52 import org.collectionspace.services.authorization.PermissionValue;
53 import org.collectionspace.services.authorization.Role;
54 import org.collectionspace.services.authorization.RoleValue;
55 import org.collectionspace.services.authorization.SubjectType;
57 import org.slf4j.Logger;
58 import org.slf4j.LoggerFactory;
60 // TODO: Auto-generated Javadoc
62 * The Class PermissionRoleUtil.
66 public class PermissionRoleUtil {
68 static final Logger logger = LoggerFactory.getLogger(PermissionRoleUtil.class);
71 * Gets the relation subject.
74 * @return the relation subject
76 static public SubjectType getRelationSubject(ServiceContext<?, ?> ctx) {
77 Object o = ctx.getProperty(ServiceContextProperties.SUBJECT);
79 throw new IllegalArgumentException(ServiceContextProperties.SUBJECT
80 + " property is missing in context "
83 return (SubjectType) o;
87 * Gets the relation subject.
91 * @return the relation subject
93 static public SubjectType getRelationSubject(ServiceContext<?, ?> ctx, PermissionRole pr) {
94 SubjectType subject = pr.getSubject();
95 if (subject == null) {
96 //it is not required to give subject as URI determines the subject
97 subject = getRelationSubject(ctx);
103 * buildPermissionRoleRel builds persistent relationship entities from given
106 * @param pr permissionrole
107 * @param subject the subject
108 * @param permRoleRelationshipList persistent entities built are inserted into this list
109 * @param toDelete the to delete
111 static public void buildPermissionRoleRel(JPATransactionContext jpaTransactionContext,
114 List<PermissionRoleRel> permRoleRelationshipList,
115 boolean handleDelete,
116 String tenantId) throws Exception {
118 if (subject.equals(SubjectType.ROLE)) {
119 List<PermissionValue> permissionValues = pr.getPermission();
120 if (permissionValues != null && permissionValues.size() == 1) {
121 PermissionValue pv = permissionValues.get(0);
122 for (RoleValue rv : pr.getRole()) {
123 PermissionRoleRel permRoleRelationship = buildPermissonRoleRel(jpaTransactionContext, pv, rv, subject, handleDelete, tenantId);
124 permRoleRelationshipList.add(permRoleRelationship);
127 String msg = "There must be one and only one Permission supplied in the payload when creating this Permission-Roles relationshiop.";
128 throw new DocumentException(msg);
130 } else if (subject.equals(SubjectType.PERMISSION)) {
131 List<RoleValue> roleValues = pr.getRole();
132 if (roleValues != null && roleValues.size() == 1) {
133 RoleValue rv = roleValues.get(0);
134 for (PermissionValue pv : pr.getPermission()) {
135 PermissionRoleRel prr = buildPermissonRoleRel(jpaTransactionContext, pv, rv, subject, handleDelete, tenantId);
136 permRoleRelationshipList.add(prr);
139 String msg = "There must be one and only one Role supplied in the payload when creating this Role-Permissions relationshiop.";
140 throw new DocumentException(msg);
145 static public void buildPermissionRoleRel(
146 ServiceContext<?, ?> ctx,
149 List<PermissionRoleRel> prrl,
150 boolean handleDelete,
151 String tenantId) throws Exception {
153 JPATransactionContext jpaTransactionContext = (JPATransactionContext)ctx.openConnection();
155 jpaTransactionContext.beginTransaction();
156 buildPermissionRoleRel(jpaTransactionContext, pr, subject, prrl, handleDelete, tenantId);
157 jpaTransactionContext.commitTransaction();
158 } catch (Exception e) {
159 jpaTransactionContext.markForRollback();
160 if (logger.isDebugEnabled()) {
161 logger.debug("Caught exception ", e);
165 ctx.closeConnection();
170 * Try to find a persisted Permission record using a PermissionValue instance.
173 static private Permission lookupPermission(JPATransactionContext jpaTransactionContext, PermissionValue permissionValue, String tenantId) throws DocumentException {
174 Permission result = null;
176 String actionGroup = permissionValue.getActionGroup() != null ? permissionValue.getActionGroup().trim() : null;
177 String resourceName = permissionValue.getResourceName() != null ? permissionValue.getResourceName().trim() : null;
178 String permissionId = permissionValue.getPermissionId() != null ? permissionValue.getPermissionId().trim() : null;
180 // If we have a permission ID, use it to try to lookup the persisted permission
182 if (permissionId != null && !permissionId.isEmpty()) {
184 result = (Permission)JpaStorageUtils.getEntityByDualKeys(
185 jpaTransactionContext,
186 Permission.class.getName(),
187 PermissionStorageConstants.ID, permissionId,
188 PermissionStorageConstants.TENANT_ID, tenantId);
189 } catch (Throwable e) {
190 String msg = String.format("Searched for but couldn't find a permission with CSID='%s'.",
194 } else if (Tools.notBlank(resourceName) && Tools.notBlank(actionGroup)) {
196 // If there was no permission ID, then we can try to find the permission with the resource name and action group tuple
199 result = (Permission)JpaStorageUtils.getEntityByDualKeys(
200 jpaTransactionContext,
201 Permission.class.getName(),
202 PermissionStorageConstants.RESOURCE_NAME, permissionValue.getResourceName(),
203 PermissionStorageConstants.ACTION_GROUP, permissionValue.getActionGroup(),
205 } catch (NoResultException e) {
206 String msg = String.format("Searched for but couldn't find a permission for resource='%s', action group='%s', and tenant ID='%s'.",
207 permissionValue.getResourceName(), permissionValue.getActionGroup(), tenantId);
211 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'.",
212 permissionId, resourceName, actionGroup);
213 throw new DocumentException(errMsg);
216 if (result == null) {
217 throw new DocumentNotFoundException(String.format("Could not find Permission resource with CSID='%s', actionGroup='%s', resourceName='%s'.",
218 permissionId, actionGroup, resourceName));
224 * Ensure the Role's permission relationships can be changed.
229 static private boolean canRoleRelatedTo(Role role) {
230 boolean result = true;
232 if (RoleClient.IMMUTABLE.equals(role.getPermsProtection()) && !AuthN.get().isSystemAdmin()) {
240 * Builds a permisson role relationship for either 'create' or 'delete'
242 * @param pv the pv (currently using only the ID)
243 * @param rv the rv (currently using only the ID)
244 * @param handleDelete the handle delete
245 * @return the permission role rel
246 * @throws DocumentException
248 static private PermissionRoleRel buildPermissonRoleRel(JPATransactionContext jpaTransactionContext, PermissionValue permissionValue,
251 boolean handleDelete, // if 'true' then we're deleting not building a permission-role record
252 String tenantId) throws DocumentException {
254 PermissionRoleRel result = null;
255 Role role = lookupRole(jpaTransactionContext, roleValue, tenantId);
257 // Ensure we can change the Role's permissions-related relationships.
259 if (canRoleRelatedTo(role) == false) {
260 String msg = String.format("Role with CSID='%s' cannot have its associated permissions changed.", role.getCsid());
261 throw new DocumentException(msg);
264 // Get the permission info
266 Permission permission = lookupPermission(jpaTransactionContext, permissionValue, tenantId);
268 // If we couldn't find an existing permission and we're not processing a DELETE request, we need to create
271 if (permission == null && handleDelete == false) {
272 permission = new Permission();
273 permission.setResourceName(permissionValue.getResourceName());
274 permission.setActionGroup(permissionValue.getActionGroup());
275 permission.setEffect(EffectType.PERMIT); // By default, CollectionSpace currently (11/2017) supports only PERMIT
276 List<PermissionAction> actionList = createPermActionList(permissionValue.getActionGroup());
277 permission.setAction(actionList);
278 permission = createPermission(jpaTransactionContext, permission);
279 if (permission == null) {
280 String errMsg = "Could not create new permission for new permission-role relationship.";
281 throw new DocumentException(errMsg);
283 } else if (permission == null && handleDelete == true) {
284 String msg = String.format("Could not find an existing permission that matches this: %s",
285 JaxbUtils.toString(permissionValue, PermissionValue.class));
286 throw new DocumentException(msg);
290 // Since our permissionValue may not have been supplied by the client with an ID, we need
293 if (permissionValue.getPermissionId() == null || permissionValue.getPermissionId().trim().isEmpty()) {
294 permissionValue.setPermissionId(permission.getCsid());
298 // Create the permission-role to persist
300 result = new PermissionRoleRel();
301 result.setPermissionId(permission.getCsid());
302 result.setPermissionResource(permission.getResourceName());
303 result.setActionGroup(permission.getActionGroup());
304 result.setRoleId(roleValue.getRoleId());
305 result.setRoleName(roleValue.getRoleName());
308 // For 'delete' we need to set the hjid of the existing relstionship
310 String relationshipId = null;
311 if (subject.equals(SubjectType.ROLE) == true) {
312 relationshipId = roleValue.getRoleRelationshipId();
313 } else if (subject.equals(SubjectType.PERMISSION) == true) {
314 relationshipId = permissionValue.getPermRelationshipId();
316 if (relationshipId != null && handleDelete == true) {
317 result.setHjid(Long.parseLong(relationshipId)); // set this so we can convince JPA to del the relation
323 public static RoleValue fetchRoleValue(ServiceContext<?, ?> ctx, String roleId) throws DocumentNotFoundException {
324 RoleValue result = null;
326 JPATransactionContext jpaTransactionContext = (JPATransactionContext) ctx.getCurrentTransactionContext();
327 Role role = lookupRole(jpaTransactionContext, roleId, ctx.getTenantId());
328 result = AuthorizationRoleRel.buildRoleValue(role);
333 private static Role lookupRole(JPATransactionContext jpaTransactionContext, RoleValue roleValue, String tenantId) throws DocumentNotFoundException {
334 return lookupRole(jpaTransactionContext, roleValue.getRoleId(), tenantId);
337 private static Role lookupRole(JPATransactionContext jpaTransactionContext, String roleId, String tenantId) throws DocumentNotFoundException {
341 result = (Role)JpaStorageUtils.getEntityByDualKeys(
342 jpaTransactionContext,
343 Role.class.getName(),
344 RoleStorageConstants.ROLE_ID, roleId,
345 RoleStorageConstants.ROLE_TENANT_ID, tenantId);
346 } catch (Throwable e) {
347 String msg = String.format("Searched for but couldn't find a role with CSID='%s'.",
352 if (result == null) {
353 String msg = String.format("Could not find Role resource with CSID='%s'", roleId);
354 throw new DocumentNotFoundException(msg);
361 private static Permission createPermission(JPATransactionContext jpaTransactionContext, Permission permission) {
362 Permission result = null;
364 PermissionResource permissionResource = new PermissionResource(); // Get the PermissionResource singleton instance (RESTEasy ensures it is a singleton)
365 result = permissionResource.createPermissionFromInstance(jpaTransactionContext, permission);
370 private static List<PermissionAction> createPermActionList(String actionGroup) throws DocumentException {
371 ArrayList<PermissionAction> result = new ArrayList<PermissionAction>();
373 for (char c : actionGroup.toUpperCase().toCharArray()) {
374 PermissionAction permAction = new PermissionAction();
377 permAction.setName(ActionType.CREATE);
381 permAction.setName(ActionType.READ);
385 permAction.setName(ActionType.UPDATE);
389 permAction.setName(ActionType.DELETE);
393 permAction.setName(ActionType.SEARCH);
397 String errMsg = String.format("Illegal action group token '%c' in permission action group '%s'.",
399 throw new DocumentException(errMsg);
402 if (result.add(permAction) == false) {
403 String warnMsg = String.format("Illegal or duplicate action group token '%c' in permission action group '%s'.",
405 logger.warn(warnMsg);
412 static public boolean isEmpty(PermissionRole permRole) {
413 boolean result = true;
415 if (permRole != null && !Tools.isEmpty(permRole.getPermission())
416 && !Tools.isEmpty(permRole.getRole()) && permRole.getSubject() != null) {
424 * Checks if is invalid tenant.
426 * @param tenantId the tenant id
427 * @param msgBldr the msg bldr
428 * @return true, if is invalid tenant
430 static boolean isInvalidTenant(String tenantId, StringBuilder msgBldr) {
431 boolean invalid = false;
433 if (tenantId == null || tenantId.isEmpty()) {
435 msgBldr.append("\n tenant : tenantId is missing");
437 String whereClause = "where id = :id";
438 HashMap<String, Object> params = new HashMap<String, Object>();
439 params.put("id", tenantId);
441 Object tenantFound = JpaStorageUtils.getEntity(
442 "org.collectionspace.services.account.Tenant", whereClause, params);
443 if (tenantFound == null) {
445 msgBldr.append("\n tenant : tenantId=" + tenantId