001: /***************************************************************
002: * This file is part of the [fleXive](R) project.
003: *
004: * Copyright (c) 1999-2008
005: * UCS - unique computing solutions gmbh (http://www.ucs.at)
006: * All rights reserved
007: *
008: * The [fleXive](R) project is free software; you can redistribute
009: * it and/or modify it under the terms of the GNU General Public
010: * License as published by the Free Software Foundation;
011: * either version 2 of the License, or (at your option) any
012: * later version.
013: *
014: * The GNU General Public License can be found at
015: * http://www.gnu.org/copyleft/gpl.html.
016: * A copy is found in the textfile GPL.txt and important notices to the
017: * license from the author are found in LICENSE.txt distributed with
018: * these libraries.
019: *
020: * This library is distributed in the hope that it will be useful,
021: * but WITHOUT ANY WARRANTY; without even the implied warranty of
022: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
023: * GNU General Public License for more details.
024: *
025: * For further information about UCS - unique computing solutions gmbh,
026: * please see the company website: http://www.ucs.at
027: *
028: * For further information about [fleXive](R), please see the
029: * project website: http://www.flexive.org
030: *
031: *
032: * This copyright notice MUST APPEAR in all copies of the file!
033: ***************************************************************/package com.flexive.shared.content;
034:
035: import com.flexive.shared.CacheAdmin;
036: import com.flexive.shared.FxContext;
037: import com.flexive.shared.FxLanguage;
038: import com.flexive.shared.exceptions.FxApplicationException;
039: import com.flexive.shared.exceptions.FxInvalidParameterException;
040: import com.flexive.shared.exceptions.FxNoAccessException;
041: import com.flexive.shared.exceptions.FxNotFoundException;
042: import com.flexive.shared.security.*;
043: import com.flexive.shared.structure.FxEnvironment;
044: import com.flexive.shared.structure.FxPropertyAssignment;
045: import com.flexive.shared.structure.FxType;
046: import com.flexive.shared.value.FxNoAccess;
047: import org.apache.commons.logging.Log;
048: import org.apache.commons.logging.LogFactory;
049:
050: import java.util.ArrayList;
051: import java.util.List;
052:
053: /**
054: * Permission Utilities
055: *
056: * @author Markus Plesser (markus.plesser@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
057: */
058: public class FxPermissionUtils {
059: private static final Log LOG = LogFactory
060: .getLog(FxPermissionUtils.class);
061:
062: public static final byte PERM_MASK_INSTANCE = 0x01;
063: public static final byte PERM_MASK_PROPERTY = 0x02;
064: public static final byte PERM_MASK_STEP = 0x04;
065: public static final byte PERM_MASK_TYPE = 0x08;
066:
067: /**
068: * Permission check for (new) contents
069: *
070: * @param ticket calling users ticket
071: * @param ownerId owner of the content to check
072: * @param permission permission to check
073: * @param type used type
074: * @param stepACL step ACL
075: * @param contentACL content ACL
076: * @param throwException should exception be thrown
077: * @return access granted
078: * @throws FxNoAccessException if not accessible for calling user
079: */
080: public static boolean checkPermission(UserTicket ticket,
081: long ownerId, ACL.Permission permission, FxType type,
082: long stepACL, long contentACL, boolean throwException)
083: throws FxNoAccessException {
084: if (ticket.isGlobalSupervisor() || !type.usePermissions()
085: || FxContext.get().getRunAsSystem())
086: return true;
087: boolean typeAllowed = !type.useTypePermissions();
088: boolean stepAllowed = !type.useStepPermissions();
089: boolean contentAllowed = !type.useInstancePermissions();
090: for (ACLAssignment assignment : ticket.getACLAssignments()) {
091: if (!typeAllowed
092: && assignment.getAclId() == type.getACL().getId())
093: typeAllowed = assignment.getPermission(permission,
094: ownerId, ticket.getUserId());
095: if (!stepAllowed && assignment.getAclId() == stepACL)
096: stepAllowed = assignment.getPermission(permission,
097: ownerId, ticket.getUserId());
098: if (!contentAllowed && assignment.getAclId() == contentACL)
099: contentAllowed = assignment.getPermission(permission,
100: ownerId, ticket.getUserId());
101: }
102: if (throwException
103: && !(typeAllowed && stepAllowed && contentAllowed)) {
104: List<String> lacking = new ArrayList<String>(3);
105: if (!typeAllowed)
106: addACLName(lacking, ticket.getLanguage(), type.getACL()
107: .getId());
108: if (!stepAllowed)
109: addACLName(lacking, ticket.getLanguage(), stepACL);
110: if (!contentAllowed)
111: addACLName(lacking, ticket.getLanguage(), contentACL);
112: String[] params = new String[lacking.size() + 1];
113: params[0] = "ex.acl.name."
114: + permission.toString().toLowerCase();
115: for (int i = 0; i < lacking.size(); i++)
116: params[i + 1] = lacking.get(i);
117: throw new FxNoAccessException("ex.acl.noAccess.extended."
118: + lacking.size(), (Object[]) params);
119: }
120: return typeAllowed && stepAllowed && contentAllowed;
121: }
122:
123: /**
124: * Permission check for existing contents
125: *
126: * @param ticket calling users ticket
127: * @param permission permission to check
128: * @param si security info of the content to check
129: * @param throwException should exception be thrown
130: * @return access granted
131: * @throws FxNoAccessException if access denied and exception should be thrown
132: */
133: public static boolean checkPermission(UserTicket ticket,
134: ACL.Permission permission, FxContentSecurityInfo si,
135: boolean throwException) throws FxNoAccessException {
136: if (!si.usePermissions() || ticket.isGlobalSupervisor()
137: || FxContext.get().getRunAsSystem())
138: return true;
139: boolean typeAllowed = !si.useTypePermissions();
140: boolean stepAllowed = !si.useStepPermissions();
141: boolean contentAllowed = !si.useInstancePermissions();
142: boolean propertyAllowed = true;
143:
144: List<String> lacking = (throwException ? new ArrayList<String>(
145: 3) : null);
146:
147: for (ACLAssignment assignment : ticket.getACLAssignments()) {
148: if (!typeAllowed
149: && assignment.getAclId() == si.getTypeACL())
150: typeAllowed = assignment.getPermission(permission, si
151: .getOwnerId(), ticket.getUserId());
152: if (!stepAllowed
153: && assignment.getAclId() == si.getStepACL())
154: stepAllowed = assignment.getPermission(permission, si
155: .getOwnerId(), ticket.getUserId());
156: if (!contentAllowed
157: && assignment.getAclId() == si.getContentACL())
158: contentAllowed = assignment.getPermission(permission,
159: si.getOwnerId(), ticket.getUserId());
160: if (permission == ACL.Permission.DELETE) {
161: //property permissions are only checked for delete operations since no
162: //exception should be thrown when ie loading as properties are wrapped in
163: //FxNoAccess values or set to read only
164: if (si.usePermissions()
165: && si.getUsedPropertyACL().size() > 0
166: && assignment.getACLCategory() == ACL.Category.STRUCTURE) {
167: for (long propertyACL : si.getUsedPropertyACL())
168: if (propertyACL == assignment.getAclId())
169: if (!assignment
170: .getPermission(permission, si
171: .getOwnerId(), ticket
172: .getUserId())) {
173: propertyAllowed = false;
174: addACLName(lacking, ticket
175: .getLanguage(), propertyACL);
176: }
177: }
178: }
179: }
180: if (throwException
181: && !(typeAllowed && stepAllowed && contentAllowed && propertyAllowed)) {
182: if (!typeAllowed)
183: addACLName(lacking, ticket.getLanguage(), si
184: .getTypeACL());
185: if (!stepAllowed)
186: addACLName(lacking, ticket.getLanguage(), si
187: .getStepACL());
188: if (!contentAllowed)
189: addACLName(lacking, ticket.getLanguage(), si
190: .getContentACL());
191: String[] params = new String[lacking.size() + 1];
192: params[0] = "ex.acl.name."
193: + permission.toString().toLowerCase();
194: for (int i = 0; i < lacking.size(); i++)
195: params[i + 1] = lacking.get(i);
196: throw new FxNoAccessException("ex.acl.noAccess.extended."
197: + lacking.size(), (Object[]) params);
198: }
199: return typeAllowed && stepAllowed && contentAllowed
200: && propertyAllowed;
201: }
202:
203: /**
204: * Get the translation for an ACL name in the requested language and add it to the given list
205: *
206: * @param list list to add the name
207: * @param lang language
208: * @param acl id of the acl
209: */
210: private static void addACLName(List<String> list, FxLanguage lang,
211: long acl) {
212: String name;
213: try {
214: name = CacheAdmin.getEnvironment().getACL(acl).getLabel()
215: .getTranslation(lang);
216: } catch (Exception e) {
217: name = "#" + acl;
218: }
219: if (list == null)
220: list = new ArrayList<String>(3);
221: if (!list.contains(name))
222: list.add(name);
223: }
224:
225: /**
226: * Process a contents property and wrap FxValue's in FxNoAccess or set them to readonly where appropriate
227: *
228: * @param ticket calling users ticket
229: * @param securityInfo needed security information
230: * @param content the content to process
231: * @param type the content's FxType
232: * @param env environment
233: * @throws FxNotFoundException on errors
234: * @throws FxInvalidParameterException on errors
235: * @throws FxNoAccessException on errors
236: */
237: public static void wrapNoAccessValues(UserTicket ticket,
238: FxContentSecurityInfo securityInfo, FxContent content,
239: FxType type, FxEnvironment env) throws FxNotFoundException,
240: FxInvalidParameterException, FxNoAccessException {
241: if (!type.usePropertyPermissions()
242: || ticket.isGlobalSupervisor()
243: || FxContext.get().getRunAsSystem())
244: return; //invalid call, nothing to process ...
245: List<String> xpaths = content.getAllPropertyXPaths();
246: FxPropertyData pdata;
247: List<Long> noAccess = new ArrayList<Long>(5);
248: List<Long> readOnly = new ArrayList<Long>(5);
249: for (long aclId : securityInfo.getUsedPropertyACL()) {
250: if (!ticket.mayReadACL(aclId, securityInfo.getOwnerId()))
251: noAccess.add(aclId);
252: else if (!ticket.mayEditACL(aclId, securityInfo
253: .getOwnerId()))
254: readOnly.add(aclId);
255: }
256: for (String xpath : xpaths) {
257: pdata = content.getPropertyData(xpath);
258: if (pdata.getValue() instanceof FxNoAccess)
259: continue;
260: ACL propACL = ((FxPropertyAssignment) env
261: .getAssignment(pdata.getAssignmentId())).getACL();
262: if (noAccess.contains(propACL.getId())) {
263: //may not read => wrap in a FxNoAccess value
264: pdata
265: .setValue(new FxNoAccess(ticket, pdata
266: .getValue()));
267: } else if (readOnly.contains(propACL.getId())) {
268: //may not edit => set readonly
269: pdata.getValue().setReadOnly();
270: }
271: }
272: }
273:
274: /**
275: * Unwrap all FxNoAccess values to their original values.
276: * Must be called as supervisor to work ...
277: *
278: * @param content the FxContent to process
279: * @throws FxNotFoundException on errors
280: * @throws FxInvalidParameterException on errors
281: * @throws FxNoAccessException on errors
282: */
283: public static void unwrapNoAccessValues(FxContent content)
284: throws FxNotFoundException, FxInvalidParameterException,
285: FxNoAccessException {
286: List<String> xpaths = content.getAllPropertyXPaths();
287: FxPropertyData pdata;
288: for (String xpath : xpaths) {
289: pdata = content.getPropertyData(xpath);
290: if (pdata.getValue() instanceof FxNoAccess)
291: pdata.setValue(((FxNoAccess) pdata.getValue())
292: .getWrappedValue());
293: }
294: }
295:
296: /**
297: * Check if the calling user has the requested permission for all properties in this content.
298: * Call only if the assigned type uses propery permissions!
299: *
300: * @param content content to check
301: * @param perm requested permission
302: * @throws FxNotFoundException on errors
303: * @throws FxInvalidParameterException on errors
304: * @throws FxNoAccessException on errors
305: */
306: public static void checkPropertyPermissions(FxContent content,
307: ACL.Permission perm) throws FxNotFoundException,
308: FxInvalidParameterException, FxNoAccessException {
309: final UserTicket ticket = FxContext.get().getTicket();
310: List<String> xpaths = content.getAllPropertyXPaths();
311: FxPropertyData pdata;
312: for (String xpath : xpaths) {
313: pdata = content.getPropertyData(xpath);
314: if (pdata.getValue() instanceof FxNoAccess
315: || pdata.isEmpty() || pdata.getValue().isReadOnly())
316: continue; //dont touch NoAccess or readonly values
317: if (perm == ACL.Permission.EDIT
318: && !ticket.mayEditACL(((FxPropertyAssignment) pdata
319: .getAssignment()).getACL().getId(), content
320: .getLifeCycleInfo().getCreatorId()))
321: throw new FxNoAccessException(
322: "ex.acl.noAccess.property.edit", xpath);
323: else if (perm == ACL.Permission.CREATE
324: && !ticket.mayCreateACL(
325: ((FxPropertyAssignment) pdata
326: .getAssignment()).getACL().getId(),
327: content.getLifeCycleInfo().getCreatorId()))
328: throw new FxNoAccessException(
329: "ex.acl.noAccess.property.create", xpath);
330: else if (perm == ACL.Permission.READ
331: && !ticket.mayReadACL(((FxPropertyAssignment) pdata
332: .getAssignment()).getACL().getId(), content
333: .getLifeCycleInfo().getCreatorId()))
334: throw new FxNoAccessException(
335: "ex.acl.noAccess.property.read", xpath);
336: else if (perm == ACL.Permission.DELETE
337: && !ticket.mayDeleteACL(
338: ((FxPropertyAssignment) pdata
339: .getAssignment()).getACL().getId(),
340: content.getLifeCycleInfo().getCreatorId()))
341: throw new FxNoAccessException(
342: "ex.acl.noAccess.property.delete", xpath);
343: }
344: }
345:
346: /**
347: * Encode permissions for use in FxType
348: *
349: * @param useInstancePermissions instance
350: * @param usePropertyPermissions property
351: * @param useStepPermissions (workflow)step
352: * @param useTypePermissions type
353: * @return encoded permissions
354: */
355: public static byte encodeTypePermissions(
356: boolean useInstancePermissions,
357: boolean usePropertyPermissions, boolean useStepPermissions,
358: boolean useTypePermissions) {
359: byte perm = 0;
360: if (useInstancePermissions)
361: perm = PERM_MASK_INSTANCE;
362: if (usePropertyPermissions)
363: perm |= PERM_MASK_PROPERTY;
364: if (useStepPermissions)
365: perm |= PERM_MASK_STEP;
366: if (useTypePermissions)
367: perm |= PERM_MASK_TYPE;
368: return perm;
369: }
370:
371: /**
372: * Get a human readable form of bit coded permissions
373: *
374: * @param bitCodedPermissions permissions
375: * @return human readable form
376: */
377: public static String toString(byte bitCodedPermissions) {
378: StringBuilder res = new StringBuilder(30);
379: if ((bitCodedPermissions & PERM_MASK_TYPE) == PERM_MASK_TYPE)
380: res.append(",Type");
381: if ((bitCodedPermissions & PERM_MASK_STEP) == PERM_MASK_STEP)
382: res.append(",Step");
383: if ((bitCodedPermissions & PERM_MASK_PROPERTY) == PERM_MASK_PROPERTY)
384: res.append(",Property");
385: if ((bitCodedPermissions & PERM_MASK_INSTANCE) == PERM_MASK_INSTANCE)
386: res.append(",Instance");
387: if (res.length() > 0)
388: res.deleteCharAt(0);
389: return res.toString();
390: }
391:
392: /**
393: * Get a users permission for a given instance ACL
394: *
395: * @param acl instance ACL
396: * @param type used type
397: * @param stepACL step ACL
398: * @param createdBy owner
399: * @param mandator mandator
400: * @return array of permissions in the order edit, relate, delete, export and create
401: * @throws com.flexive.shared.exceptions.FxNoAccessException
402: * if no read access if permitted
403: */
404: public static boolean[] getPermissions(long acl, FxType type,
405: long stepACL, long createdBy, long mandator)
406: throws FxNoAccessException {
407: boolean[] perms = new boolean[5];
408: UserTicket ticket = FxContext.get().getTicket();
409: final boolean _system = FxContext.get().getRunAsSystem()
410: || ticket.isGlobalSupervisor();
411: checkPermission(ticket, 0, ACL.Permission.READ, type, stepACL,
412: acl, true);
413: if (_system || ticket.isMandatorSupervisor()
414: && mandator == ticket.getMandatorId()
415: || !type.usePermissions() /*|| ticket.isInGroup((int) UserGroup.GROUP_OWNER) && createdBy == ticket.getUserId()*/) {
416: perms[0] = perms[1] = perms[2] = perms[3] = perms[4] = true;
417: } else {
418: //throw exception if read is forbidden
419: checkPermission(ticket, 0, ACL.Permission.READ, type,
420: stepACL, acl, true);
421: perms[0] = checkPermission(ticket, createdBy,
422: ACL.Permission.EDIT, type, stepACL, acl, false);
423: perms[1] = checkPermission(ticket, createdBy,
424: ACL.Permission.RELATE, type, stepACL, acl, false);
425: perms[2] = checkPermission(ticket, createdBy,
426: ACL.Permission.DELETE, type, stepACL, acl, false);
427: perms[3] = checkPermission(ticket, createdBy,
428: ACL.Permission.EXPORT, type, stepACL, acl, false);
429: perms[4] = checkPermission(ticket, createdBy,
430: ACL.Permission.CREATE, type, stepACL, acl, false);
431: }
432: return perms;
433: }
434:
435: /**
436: * Throw an exception if the calling user is not in the given roles
437: *
438: * @param ticket calling user
439: * @param roles Roles to check
440: * @throws com.flexive.shared.exceptions.FxNoAccessException
441: * on errors
442: */
443: public static void checkRole(UserTicket ticket, Role... roles)
444: throws FxNoAccessException {
445: if (ticket.isGlobalSupervisor())
446: return;
447: for (Role role : roles)
448: if (!ticket.isInRole(role)) {
449: throw new FxNoAccessException(LOG, "ex.role.notInRole",
450: role.getName());
451: }
452: }
453:
454: /**
455: * Check if the requested FxType is available. A FxNotFoundException will be thrown if the FxType's state is
456: * <code>TypeState.Unavailable</code>, if <code>allowLocked</code> is <code>true</code> and the FxType's state is
457: * <code>TypeState.Locked</code> a FxNoAccessException will be thrown.
458: *
459: * @param typeId requested type id to check
460: * @param allowLocked allow a locked state?
461: * @throws FxApplicationException on errors
462: * @see com.flexive.shared.structure.TypeState
463: */
464: public static void checkTypeAvailable(long typeId,
465: boolean allowLocked) throws FxApplicationException {
466: FxType check = CacheAdmin.getEnvironment().getType(typeId);
467: switch (check.getState()) {
468: case Available:
469: return;
470: case Locked:
471: if (allowLocked)
472: return;
473: throw new FxNoAccessException("ex.structure.type.locked",
474: check.getName(), check.getId());
475: case Unavailable:
476: throw new FxNotFoundException(
477: "ex.structure.type.unavailable", check.getName(),
478: check.getId());
479: }
480: }
481:
482: /**
483: * Check if the mandator with the requested id exists and is active.
484: * Will throw a FxNotFoundException if inactive or not existant.
485: *
486: * @param id requested mandator id
487: * @throws FxNotFoundException if inactive or not existant
488: */
489: public static void checkMandatorExistance(long id)
490: throws FxNotFoundException {
491: Mandator m = CacheAdmin.getEnvironment().getMandator(id);
492: if (!m.isActive())
493: throw new FxNotFoundException(
494: "ex.structure.mandator.notFound.notActive", m
495: .getName(), id);
496: }
497: }
|