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.authorization.storage;
26 import java.util.ArrayList;
27 import java.util.List;
28 import java.util.UUID;
30 import javax.persistence.EntityExistsException;
31 import javax.persistence.NoResultException;
33 import org.collectionspace.services.client.PermissionClient;
34 import org.collectionspace.services.client.PermissionClient.ActionCompare;
36 import org.collectionspace.services.authorization.perms.ActionType;
37 import org.collectionspace.services.authorization.CSpaceAction;
38 import org.collectionspace.services.authorization.perms.Permission;
39 import org.collectionspace.services.authorization.perms.PermissionAction;
40 import org.collectionspace.services.authorization.perms.PermissionsList;
41 import org.collectionspace.services.authorization.URIResourceImpl;
43 import org.collectionspace.services.common.context.ServiceContext;
44 import org.collectionspace.services.common.document.BadRequestException;
45 import org.collectionspace.services.common.document.DocumentException;
46 import org.collectionspace.services.common.document.DocumentFilter;
47 import org.collectionspace.services.common.document.DocumentWrapper;
48 import org.collectionspace.services.common.document.JaxbUtils;
49 import org.collectionspace.services.common.document.TransactionException;
50 import org.collectionspace.services.common.security.SecurityUtils;
51 import org.collectionspace.services.common.storage.jpa.JPATransactionContext;
52 import org.collectionspace.services.common.storage.jpa.JpaDocumentHandler;
53 import org.collectionspace.services.common.storage.jpa.JpaStorageUtils;
55 import org.slf4j.Logger;
56 import org.slf4j.LoggerFactory;
59 * Document handler for Permission
62 @SuppressWarnings("rawtypes")
63 public class PermissionDocumentHandler
64 extends JpaDocumentHandler<Permission, PermissionsList, Permission, List<Permission>> {
66 private final Logger logger = LoggerFactory.getLogger(PermissionDocumentHandler.class);
67 private Permission permission;
68 private PermissionsList permissionsList;
70 public CSpaceAction getAction(ActionType action) {
71 if (ActionType.CREATE.name().equals(action.name())) {
72 return CSpaceAction.CREATE;
73 } else if (ActionType.READ.equals(action)) {
74 return CSpaceAction.READ;
75 } else if (ActionType.UPDATE.equals(action)) {
76 return CSpaceAction.UPDATE;
77 } else if (ActionType.DELETE.equals(action)) {
78 return CSpaceAction.DELETE;
79 } else if (ActionType.SEARCH.equals(action)) {
80 return CSpaceAction.SEARCH;
81 } else if (ActionType.ADMIN.equals(action)) {
82 return CSpaceAction.ADMIN;
83 } else if (ActionType.START.equals(action)) {
84 return CSpaceAction.START;
85 } else if (ActionType.STOP.equals(action)) {
86 return CSpaceAction.STOP;
89 // We could not find a match, so we need to throw an exception.
91 throw new IllegalArgumentException("action = " + action.toString());
95 * Add the ACE hashed ID to the permission action so we can map the permission to the Spring Security
98 private void handlePermissionActions(Permission perm) throws DocumentException {
100 // Verify the permission actions. If the action group is missing, create it from the action list and vice versa.
102 ActionCompare compareResult = PermissionClient.validatePermActions(perm);
103 switch (compareResult) {
104 case ACTIONS_MISSING:
105 String msg = "Permission resource encountered with missing action group and action list.";
106 throw new DocumentException(msg);
108 case ACTION_GROUP_EMPTY:
109 String actionGroup = PermissionClient.getActionGroup(perm.getAction());
110 perm.setActionGroup(actionGroup);
113 case ACTION_LIST_EMPTY:
114 List<PermissionAction> actionList = PermissionClient.getActionList(perm.getActionGroup());
115 perm.setAction(actionList);
123 msg = String.format("Permission has mismatching action group and action list. Action group='%s' and Action list = '%s'.",
124 perm.getActionGroup(), PermissionClient.getActionGroup(perm.getAction()));
125 throw new DocumentException(msg);
128 List<PermissionAction> permActions = perm.getAction();
129 for (PermissionAction permAction : permActions) {
130 CSpaceAction action = getAction(permAction.getName());
131 URIResourceImpl uriRes = new URIResourceImpl(perm.getTenantId(), perm.getResourceName(), action);
132 permAction.setObjectIdentity(uriRes.getHashedId().toString());
133 permAction.setObjectIdentityResource(uriRes.getId());
134 //PermissionActionUtil.update(perm, permAction);
138 private Permission findExistingPermission(Permission perm) throws TransactionException {
139 Permission result = null;
141 ServiceContext ctx = getServiceContext();
142 String tenantId = ctx.getTenantId(); // we need a tenant ID
143 JPATransactionContext jpaTransactionContext = (JPATransactionContext)ctx.openConnection();
145 result = (Permission)JpaStorageUtils.getEntityByDualKeys(jpaTransactionContext,
146 Permission.class.getName(),
147 PermissionStorageConstants.RESOURCE_NAME, perm.getResourceName(),
148 PermissionStorageConstants.ACTION_GROUP, perm.getActionGroup(),
150 } catch (NoResultException e) {
151 if (logger.isTraceEnabled()) {
152 String msg = String.format("Looked for but could not find permission with resource name = '%s', action group = '%s', tenat ID = '%s'.",
153 perm.getResourceName(), perm.getActionGroup(), tenantId);
157 ctx.closeConnection();
164 public void handleCreate(DocumentWrapper<Permission> wrapDoc) throws EntityExistsException, DocumentException {
166 // First check to see if an equivalent permission exists
168 Permission permission = wrapDoc.getWrappedObject();
169 Permission existingPermission = findExistingPermission(permission);
171 if (existingPermission == null) {
172 String id = UUID.randomUUID().toString();
173 permission.setCsid(id);
174 setTenant(permission);
175 handlePermissionActions(permission);
177 String msg = String.format("Found existing permission with resource name = '%s', action group = '%s', and tenant ID = '%s'.",
178 existingPermission.getResourceName(), existingPermission.getActionGroup(), existingPermission.getTenantId());
179 wrapDoc.resetWrapperObject(existingPermission); // update the wrapped document with the existing permission instance
180 throw new EntityExistsException(msg);
185 public void completeCreate(DocumentWrapper<Permission> wrapDoc) throws Exception {
189 public void handleUpdate(DocumentWrapper<Permission> wrapDoc) throws Exception {
190 Permission permissionFound = wrapDoc.getWrappedObject();
191 Permission permissionReceived = getCommonPart();
192 merge(permissionReceived, permissionFound);
196 * merge manually merges the from from to the to permission
197 * -this method is created due to inefficiency of JPA EM merge
200 * @return merged permission
202 private Permission merge(Permission from, Permission to) throws Exception {
203 if (!(from.getResourceName().equalsIgnoreCase(to.getResourceName()))) {
204 String msg = "Resource name cannot be changed " + to.getResourceName();
206 throw new BadRequestException(msg);
208 //resource name, attribute cannot be changed
210 if (from.getDescription() != null) {
211 to.setDescription(from.getDescription());
213 if (from.getEffect() != null) {
214 to.setEffect(from.getEffect());
216 List<PermissionAction> fromActions = from.getAction();
217 if (!fromActions.isEmpty()) {
218 // Override the whole list, no reconciliation by design
219 to.setAction(fromActions);
220 // Update the actionGroup field to reflect the new action list
221 to.setActionGroup(PermissionClient.getActionGroup(fromActions));
224 if (logger.isDebugEnabled()) {
225 logger.debug("merged permission=" + JaxbUtils.toString(to, Permission.class));
228 handlePermissionActions(to);
232 @SuppressWarnings("unchecked")
234 public void completeUpdate(DocumentWrapper<Permission> wrapDoc) throws Exception {
235 Permission upAcc = wrapDoc.getWrappedObject();
236 getServiceContext().setOutput(upAcc);
238 //FIXME update lower-layer authorization (acls)
239 //will require deleting old permissions for this resource and adding
240 //new based on new actions and effect
243 @SuppressWarnings("unchecked")
245 public void handleGet(DocumentWrapper<Permission> wrapDoc) throws Exception {
246 setCommonPart(extractCommonPart(wrapDoc));
247 sanitize(getCommonPart());
248 getServiceContext().setOutput(permission);
251 @SuppressWarnings("unchecked")
253 public void handleGetAll(DocumentWrapper<List<Permission>> wrapDoc) throws Exception {
254 PermissionsList permissionsList = extractCommonPartList(wrapDoc);
255 setCommonPartList(permissionsList);
256 getServiceContext().setOutput(getCommonPartList());
260 public void completeDelete(DocumentWrapper<Permission> wrapDoc) throws Exception {
264 * See https://issues.collectionspace.org/browse/DRYD-181
266 * For backward compatibility, we could not change the permission list to be a child class of AbstractCommonList. This
267 * would have change the result payload and would break existing API clients. So the best we can do, it treat
268 * the role list payload as a special case and return the paging information.
271 protected PermissionsList extractPagingInfoForPerms(PermissionsList permList, DocumentWrapper<List<Permission>> wrapDoc)
274 DocumentFilter docFilter = this.getDocumentFilter();
275 long pageSize = docFilter.getPageSize();
276 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
277 // set the page size and page number
278 permList.setPageNum(pageNum);
279 permList.setPageSize(pageSize);
280 List<Permission> docList = wrapDoc.getWrappedObject();
281 // Set num of items in list. this is useful to our testing framework.
282 permList.setItemsInPage(docList.size());
283 // set the total result size
284 permList.setTotalItems(docFilter.getTotalItemsResult());
290 public Permission extractCommonPart(
291 DocumentWrapper<Permission> wrapDoc)
293 return wrapDoc.getWrappedObject();
297 public void fillCommonPart(Permission obj, DocumentWrapper<Permission> wrapDoc)
299 throw new UnsupportedOperationException("operation not relevant for AccountDocumentHandler");
303 public PermissionsList extractCommonPartList(
304 DocumentWrapper<List<Permission>> wrapDoc)
307 PermissionsList permissionsList = extractPagingInfoForPerms(new PermissionsList(), wrapDoc);
308 List<Permission> list = new ArrayList<Permission>();
309 permissionsList.setPermission(list);
310 for (Object obj : wrapDoc.getWrappedObject()) {
311 Permission permission = (Permission) obj;
312 sanitize(permission);
313 list.add(permission);
316 return permissionsList;
320 public Permission getCommonPart() {
325 public void setCommonPart(Permission permission) {
326 this.permission = permission;
330 public PermissionsList getCommonPartList() {
331 return permissionsList;
335 public void setCommonPartList(PermissionsList permissionsList) {
336 this.permissionsList = permissionsList;
340 public String getQProperty(
346 public DocumentFilter createDocumentFilter() {
347 DocumentFilter filter = new PermissionJpaFilter(this.getServiceContext());
352 * Sanitize removes data not needed to be sent to the consumer
355 private void sanitize(Permission permission) {
356 if (!SecurityUtils.isCSpaceAdmin()) {
357 // permission.setTenantId(null); // REM - Why are we removing the tenant ID from the payload? Commenting out this line for now.
361 private void setTenant(Permission permission) {
362 //set tenant only if not available from input
363 if (permission.getTenantId() == null || permission.getTenantId().isEmpty()) {
364 permission.setTenantId(getServiceContext().getTenantId());