com.flexive.shared.content.FxPermissionUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.flexive.shared.content.FxPermissionUtils.java

Source

/***************************************************************
 *  This file is part of the [fleXive](R) framework.
 *
 *  Copyright (c) 1999-2014
 *  UCS - unique computing solutions gmbh (http://www.ucs.at)
 *  All rights reserved
 *
 *  The [fleXive](R) project is free software; you can redistribute
 *  it and/or modify it under the terms of the GNU Lesser General Public
 *  License version 2.1 or higher as published by the Free Software Foundation.
 *
 *  The GNU Lesser General Public License can be found at
 *  http://www.gnu.org/licenses/lgpl.html.
 *  A copy is found in the textfile LGPL.txt and important notices to the
 *  license from the author are found in LICENSE.txt distributed with
 *  these libraries.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  For further information about UCS - unique computing solutions gmbh,
 *  please see the company website: http://www.ucs.at
 *
 *  For further information about [fleXive](R), please see the
 *  project website: http://www.flexive.org
 *
 *
 *  This copyright notice MUST APPEAR in all copies of the file!
 ***************************************************************/
package com.flexive.shared.content;

import com.flexive.shared.CacheAdmin;
import com.flexive.shared.FxContext;
import com.flexive.shared.FxLanguage;
import com.flexive.shared.exceptions.FxApplicationException;
import com.flexive.shared.exceptions.FxInvalidParameterException;
import com.flexive.shared.exceptions.FxNoAccessException;
import com.flexive.shared.exceptions.FxNotFoundException;
import com.flexive.shared.security.*;
import com.flexive.shared.structure.FxEnvironment;
import com.flexive.shared.structure.FxPropertyAssignment;
import com.flexive.shared.structure.FxType;
import com.flexive.shared.value.FxNoAccess;
import com.flexive.shared.value.FxValue;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.util.*;

/**
 * Permission Utilities
 *
 * @author Markus Plesser (markus.plesser@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
 */
public class FxPermissionUtils {
    private static final Log LOG = LogFactory.getLog(FxPermissionUtils.class);

    public static final byte PERM_MASK_INSTANCE = 0x01;
    public static final byte PERM_MASK_PROPERTY = 0x02;
    public static final byte PERM_MASK_STEP = 0x04;
    public static final byte PERM_MASK_TYPE = 0x08;

    /**
     * Permission check for (new) contents
     *
     * @param ticket         calling users ticket
     * @param ownerId        owner of the content to check
     * @param permission     permission to check
     * @param type           used type
     * @param stepACL        step ACL
     * @param contentACLs    content ACL(s)
     * @param throwException should exception be thrown
     * @return access granted
     * @throws FxNoAccessException if not accessible for calling user
     * @since 3.1
     */
    public static boolean checkPermission(UserTicket ticket, long ownerId, ACLPermission permission, FxType type,
            long stepACL, Collection<Long> contentACLs, boolean throwException) throws FxNoAccessException {
        if (ticket.isGlobalSupervisor() || !type.isUsePermissions() || FxContext.get().getRunAsSystem())
            return true;
        boolean typeAllowed = !type.isUseTypePermissions();
        boolean stepAllowed = !type.isUseStepPermissions();
        boolean contentAllowed = !type.isUseInstancePermissions();
        final long userId = ticket.getUserId();
        final long typeAclId = type.getACL().getId();
        for (ACLAssignment assignment : ticket.getACLAssignments()) {
            final long assignmentAclId = assignment.getAclId();
            if (!typeAllowed && assignmentAclId == typeAclId)
                typeAllowed = assignment.getPermission(permission, ownerId, userId);
            if (!stepAllowed && assignmentAclId == stepACL)
                stepAllowed = assignment.getPermission(permission, ownerId, userId);
            if (!contentAllowed && contentACLs.contains(assignmentAclId))
                contentAllowed = assignment.getPermission(permission, ownerId, userId);
        }
        if (throwException && !(typeAllowed && stepAllowed && contentAllowed)) {
            Set<String> lacking = new HashSet<String>(3);
            if (!typeAllowed)
                addACLName(lacking, ticket.getLanguage(), typeAclId);
            if (!stepAllowed)
                addACLName(lacking, ticket.getLanguage(), stepACL);
            if (!contentAllowed) {
                for (Long acl : contentACLs) {
                    addACLName(lacking, ticket.getLanguage(), acl);
                }
            }
            throw noAccess(permission, lacking);
        }
        return typeAllowed && stepAllowed && contentAllowed;
    }

    /**
     * Permission check for existing contents
     *
     * @param ticket         calling users ticket
     * @param permission     permission to check
     * @param si             security info of the content to check
     * @param throwException should exception be thrown
     * @return access granted
     * @throws FxNoAccessException if access denied and exception should be thrown
     */
    public static boolean checkPermission(UserTicket ticket, ACLPermission permission, FxContentSecurityInfo si,
            boolean throwException) throws FxNoAccessException {
        if (ticket.isGlobalSupervisor() || FxContext.get().getRunAsSystem())
            return true;
        final boolean checkLock;
        switch (permission) {
        case CREATE:
        case DELETE:
        case EDIT:
            checkLock = true;
            break;
        default:
            checkLock = false;
        }
        final long userId = ticket.getUserId();
        if (checkLock && si.getLock().isLocked() && !si.getLock().isUnlockable()) {
            if (si.getLock().getUserId() != userId && !ticket.isMandatorSupervisor()) {
                if (throwException)
                    throw new FxNoAccessException("ex.lock.content.locked.noAccess", si.getPk());
                return false;
            }
        }
        if (!si.usePermissions())
            return true;
        boolean typeAllowed = !si.useTypePermissions();
        boolean stepAllowed = !si.useStepPermissions();
        boolean contentAllowed = !si.useInstancePermissions();

        final Set<String> lacking = (throwException ? new HashSet<String>(3) : null);

        for (ACLAssignment assignment : ticket.getACLAssignments()) {
            final long assignmentAclId = assignment.getAclId();
            if (!typeAllowed && assignmentAclId == si.getTypeACL())
                typeAllowed = assignment.getPermission(permission, si.getOwnerId(), userId);
            if (!stepAllowed && assignmentAclId == si.getStepACL())
                stepAllowed = assignment.getPermission(permission, si.getOwnerId(), userId);
            if (!contentAllowed && si.getContentACLs().contains(assignmentAclId))
                contentAllowed = assignment.getPermission(permission, si.getOwnerId(), userId);
        }

        if (throwException && !(typeAllowed && stepAllowed && contentAllowed)) {
            if (!typeAllowed)
                addACLName(lacking, ticket.getLanguage(), si.getTypeACL());
            if (!stepAllowed)
                addACLName(lacking, ticket.getLanguage(), si.getStepACL());
            if (!contentAllowed) {
                for (Long aclId : si.getContentACLs()) {
                    addACLName(lacking, ticket.getLanguage(), aclId);
                }
            }
            throw noAccess(permission, lacking);
        }
        return typeAllowed && stepAllowed && contentAllowed;
    }

    private static FxNoAccessException noAccess(ACLPermission permission, Collection<String> lacking) {
        String[] params = new String[lacking.size() + 1];
        params[0] = "ex.acl.name." + permission.toString().toLowerCase();
        int index = 1;
        final int displayedNames = Math.min(lacking.size(), 4);
        for (String name : lacking) {
            params[index++] = name;
            if (index > displayedNames) {
                break;
            }
        }
        return new FxNoAccessException("ex.acl.noAccess.extended." + displayedNames, (Object[]) params);
    }

    /**
     * Get the translation for an ACL name in the requested language and add it to the given list
     *
     * @param list list to add the name
     * @param lang language
     * @param acl  id of the acl
     */
    private static void addACLName(Set<String> list, FxLanguage lang, long acl) {
        if (list != null) {
            list.add(CacheAdmin.getEnvironment().getACL(acl).getDisplayName());
        }
    }

    /**
     * Process a contents property and wrap FxValue's in FxNoAccess or set them to readonly where appropriate
     *
     * @param ticket       calling users ticket
     * @param securityInfo needed security information
     * @param content      the content to process
     * @param type         the content's FxType
     * @param env          environment
     * @throws FxNotFoundException         on errors
     * @throws FxInvalidParameterException on errors
     * @throws FxNoAccessException         on errors
     */
    public static void wrapNoAccessValues(UserTicket ticket, FxContentSecurityInfo securityInfo, FxContent content,
            FxType type, FxEnvironment env)
            throws FxNotFoundException, FxInvalidParameterException, FxNoAccessException {
        if (!type.isUsePropertyPermissions() || ticket.isGlobalSupervisor() || FxContext.get().getRunAsSystem())
            return; //invalid call, nothing to process ...
        List<String> xpaths = content.getAllPropertyXPaths();
        FxPropertyData pdata;
        List<Long> noAccess = new ArrayList<Long>(5);
        List<Long> readOnly = new ArrayList<Long>(5);
        for (long aclId : securityInfo.getUsedPropertyACLs()) {
            if (!ticket.mayReadACL(aclId, securityInfo.getOwnerId()))
                noAccess.add(aclId);
            else if (!ticket.mayEditACL(aclId, securityInfo.getOwnerId()))
                readOnly.add(aclId);
        }
        if (noAccess.size() == 0 && readOnly.size() == 0)
            return; //nothing to do
        for (String xpath : xpaths) {
            pdata = content.getPropertyData(xpath);
            if (pdata.getValue() instanceof FxNoAccess)
                continue;
            ACL propACL = ((FxPropertyAssignment) env.getAssignment(pdata.getAssignmentId())).getACL();
            if (noAccess.contains(propACL.getId())) {
                //may not read => wrap in a FxNoAccess value
                pdata.setValue(new FxNoAccess(ticket, pdata.getValue()));
            } else if (readOnly.contains(propACL.getId())) {
                //may not edit => set readonly
                pdata.getValue().setReadOnly();
            }
        }
    }

    /**
     * Unwrap all FxNoAccess values to their original values.
     * Must be called as supervisor to work ...
     *
     * @param content  the FxContent to process
     * @param original the original content to get the wrapped values from
     * @throws FxNotFoundException         on errors
     * @throws FxInvalidParameterException on errors
     * @throws FxNoAccessException         on errors
     */
    public static void unwrapNoAccessValues(FxContent content, FxContent original)
            throws FxNotFoundException, FxInvalidParameterException, FxNoAccessException {
        List<String> xpaths = content.getAllPropertyXPaths();
        FxPropertyData pdata;
        for (String xpath : xpaths) {
            pdata = content.getPropertyData(xpath);
            if (pdata.getValue() instanceof FxNoAccess)
                pdata.setValue(original.getValue(((FxNoAccess) pdata.getValue()).getOriginalXPath()));
        }
    }

    /**
     * Check if the calling user has the requested permission for all properties in this content.
     * Call only if the assigned type uses propery permissions!
     * Delete permission can not be checked using this method since it can't be determined if a property has been removed!
     *
     * @param content content to check
     * @param perm    requested permission
     * @throws FxNotFoundException         on errors
     * @throws FxInvalidParameterException on errors
     * @throws FxNoAccessException         on errors
     */
    public static void checkPropertyPermissions(FxContent content, ACLPermission perm)
            throws FxNotFoundException, FxInvalidParameterException, FxNoAccessException {
        final UserTicket ticket = FxContext.getUserTicket();
        List<String> xpaths = content.getAllPropertyXPaths();
        FxPropertyData pdata;
        for (String xpath : xpaths) {
            pdata = content.getPropertyData(xpath);
            checkPropertyPermission(pdata.getValue(), xpath, ticket, content.getLifeCycleInfo().getCreatorId(),
                    ((FxPropertyAssignment) pdata.getAssignment()).getACL().getId(), perm);
        }
    }

    /**
     * Check propery permissions for delta updates
     *
     * @param creatorId content instance creator
     * @param delta     delta changes
     * @param perm      permisson to check
     * @throws FxNoAccessException if not accessible for the calling user
     */
    public static void checkPropertyPermissions(long creatorId, FxDelta delta, ACLPermission perm)
            throws FxNoAccessException {
        if (!delta.changes())
            return;
        final UserTicket ticket = FxContext.getUserTicket();
        for (FxDelta.FxDeltaChange add : delta.getAdds())
            if (add.isProperty())
                checkPropertyPermission(((FxPropertyData) add.getNewData()).getValue(), add.getXPath(), ticket,
                        creatorId, ((FxPropertyAssignment) add.getNewData().getAssignment()).getACL().getId(),
                        perm);
        for (FxDelta.FxDeltaChange rem : delta.getRemoves())
            if (rem.isProperty())
                checkPropertyPermission(((FxPropertyData) rem.getOriginalData()).getValue(), rem.getXPath(), ticket,
                        creatorId, ((FxPropertyAssignment) rem.getOriginalData().getAssignment()).getACL().getId(),
                        perm);
        for (FxDelta.FxDeltaChange upd : delta.getUpdates())
            if (!upd.isPositionChangeOnly() && upd.isProperty())
                checkPropertyPermission(((FxPropertyData) upd.getNewData()).getValue(), upd.getXPath(), ticket,
                        creatorId, ((FxPropertyAssignment) upd.getOriginalData().getAssignment()).getACL().getId(),
                        perm);
    }

    /**
     * Check a single property permission
     *
     * @param value     the affected value
     * @param xpath     xpath of the property
     * @param ticket    calling users ticket
     * @param creatorId creator of the content instance
     * @param aclId     acl id to check
     * @param perm      permission to check
     * @throws FxNoAccessException if not accessible for the calling user
     */
    protected static void checkPropertyPermission(FxValue value, String xpath, UserTicket ticket, long creatorId,
            long aclId, ACLPermission perm) throws FxNoAccessException {
        if (value instanceof FxNoAccess || value.isEmpty() || value.isReadOnly())
            return; //dont touch NoAccess or readonly values
        if (perm == ACLPermission.EDIT && !ticket.mayEditACL(aclId, creatorId))
            throw new FxNoAccessException("ex.acl.noAccess.property.edit",
                    CacheAdmin.getEnvironment().getACL(aclId).getDisplayName(), xpath);
        else if (perm == ACLPermission.CREATE && !ticket.mayCreateACL(aclId, creatorId))
            throw new FxNoAccessException("ex.acl.noAccess.property.create",
                    CacheAdmin.getEnvironment().getACL(aclId).getDisplayName(), xpath);
        else if (perm == ACLPermission.READ && !ticket.mayReadACL(aclId, creatorId))
            throw new FxNoAccessException("ex.acl.noAccess.property.read",
                    CacheAdmin.getEnvironment().getACL(aclId).getDisplayName(), xpath);
    }

    /**
     * Encode permissions for use in FxType
     *
     * @param useInstancePermissions instance
     * @param usePropertyPermissions property
     * @param useStepPermissions     (workflow)step
     * @param useTypePermissions     type
     * @return encoded permissions
     */
    public static byte encodeTypePermissions(boolean useInstancePermissions, boolean usePropertyPermissions,
            boolean useStepPermissions, boolean useTypePermissions) {
        byte perm = 0;
        if (useInstancePermissions)
            perm = PERM_MASK_INSTANCE;
        if (usePropertyPermissions)
            perm |= PERM_MASK_PROPERTY;
        if (useStepPermissions)
            perm |= PERM_MASK_STEP;
        if (useTypePermissions)
            perm |= PERM_MASK_TYPE;
        return perm;
    }

    /**
     * Get a human readable form of bit coded permissions
     *
     * @param bitCodedPermissions permissions
     * @return human readable form
     */
    public static String toString(byte bitCodedPermissions) {
        StringBuilder res = new StringBuilder(30);
        if ((bitCodedPermissions & PERM_MASK_TYPE) == PERM_MASK_TYPE)
            res.append(",Type");
        if ((bitCodedPermissions & PERM_MASK_STEP) == PERM_MASK_STEP)
            res.append(",Step");
        if ((bitCodedPermissions & PERM_MASK_PROPERTY) == PERM_MASK_PROPERTY)
            res.append(",Property");
        if ((bitCodedPermissions & PERM_MASK_INSTANCE) == PERM_MASK_INSTANCE)
            res.append(",Instance");
        if (res.length() > 0)
            res.deleteCharAt(0);
        return res.toString();
    }

    /**
     * Gets the permission set union of all given ACLs.
     *
     * @param aclIds    instance ACLs
     * @param type      used type
     * @param stepACL   step ACL
     * @param createdBy owner
     * @param mandator  mandator
     * @return array of permissions in the order edit, relate, delete, export and create
     * @throws com.flexive.shared.exceptions.FxNoAccessException
     *          if no read access if permitted
     * @since 3.1
     */
    public static PermissionSet getPermissionUnion(Collection<Long> aclIds, FxType type, long stepACL,
            long createdBy, long mandator) {
        PermissionSet result = new PermissionSet(false, false, false, false, false);
        for (long aclId : aclIds) {
            try {
                result = result.union(getPermissions(aclId, type, stepACL, createdBy, mandator));
            } catch (FxNoAccessException e) {
                // no read access, ignore it since we're starting with an empty permission set anyway
            }
        }
        return result;
    }

    /**
     * Gets the permission set intersection of all given ACLs.
     *
     * @param aclIds    instance ACLs
     * @param type      used type
     * @param stepACL   step ACL
     * @param createdBy owner
     * @param mandator  mandator
     * @return array of permissions in the order edit, relate, delete, export and create
     * @throws com.flexive.shared.exceptions.FxNoAccessException
     *          if no read access if permitted
     * @since 3.1
     */
    public static PermissionSet getPermissionIntersection(Collection<Long> aclIds, FxType type, long stepACL,
            long createdBy, long mandator) throws FxNoAccessException {
        PermissionSet result = new PermissionSet(true, true, true, true, true);
        for (long aclId : aclIds) {
            result = result.intersect(getPermissions(aclId, type, stepACL, createdBy, mandator));
        }
        return result;
    }

    /**
     * Get a users permission for a given instance ACL
     *
     * @param acl       instance ACL
     * @param type      used type
     * @param stepACL   step ACL
     * @param createdBy owner
     * @param mandator  mandator
     * @return array of permissions in the order edit, relate, delete, export and create
     * @throws com.flexive.shared.exceptions.FxNoAccessException
     *          if no read access if permitted
     */
    public static PermissionSet getPermissions(long acl, FxType type, long stepACL, long createdBy, long mandator)
            throws FxNoAccessException {
        final UserTicket ticket = FxContext.getUserTicket();
        final List<Long> acls = Arrays.asList(acl);
        final boolean _system = FxContext.get().getRunAsSystem() || ticket.isGlobalSupervisor();
        //throw exception if read is forbidden
        checkPermission(ticket, createdBy, ACLPermission.READ, type, stepACL, acls, true);
        // check for supervisor permissions
        if (_system || ticket.isMandatorSupervisor() && mandator == ticket.getMandatorId() || !type
                .isUsePermissions() /*|| ticket.isInGroup((int) UserGroup.GROUP_OWNER) && createdBy == ticket.getUserId()*/) {
            return new PermissionSet(true, true, true, true, true);
        }
        // get permission matrix from ACL assignments
        return new PermissionSet(checkPermission(ticket, createdBy, ACLPermission.EDIT, type, stepACL, acls, false),
                checkPermission(ticket, createdBy, ACLPermission.RELATE, type, stepACL, acls, false),
                checkPermission(ticket, createdBy, ACLPermission.DELETE, type, stepACL, acls, false),
                checkPermission(ticket, createdBy, ACLPermission.EXPORT, type, stepACL, acls, false),
                checkPermission(ticket, createdBy, ACLPermission.CREATE, type, stepACL, acls, false));
    }

    /**
     * Throw an exception if the calling user is not in the given roles
     *
     * @param ticket calling user
     * @param roles  Roles to check
     * @throws com.flexive.shared.exceptions.FxNoAccessException
     *          on errors
     */
    public static void checkRole(UserTicket ticket, Role... roles) throws FxNoAccessException {
        if (ticket.isGlobalSupervisor())
            return;
        for (Role role : roles)
            if (!ticket.isInRole(role)) {
                throw new FxNoAccessException("ex.role.notInRole", role.getName());
            }
    }

    /**
     * Check if the requested FxType is available. A FxNotFoundException will be thrown if the FxType's state is
     * <code>TypeState.Unavailable</code>, if <code>allowLocked</code> is <code>true</code> and the FxType's state is
     * <code>TypeState.Locked</code> a FxNoAccessException will be thrown.
     *
     * @param typeId      requested type id to check
     * @param allowLocked allow a locked state?
     * @throws FxApplicationException on errors
     * @see com.flexive.shared.structure.TypeState
     */
    public static void checkTypeAvailable(long typeId, boolean allowLocked) throws FxApplicationException {
        FxType check = CacheAdmin.getEnvironment().getType(typeId);
        switch (check.getState()) {
        case Available:
            return;
        case Locked:
            if (allowLocked)
                return;
            throw new FxNoAccessException("ex.structure.type.locked", check.getName(), check.getId());
        case Unavailable:
            throw new FxNotFoundException("ex.structure.type.unavailable", check.getName(), check.getId());
        }
    }

    /**
     * Check if the mandator with the requested id exists and is active.
     * Will throw a FxNotFoundException if inactive or not existant.
     *
     * @param id requested mandator id
     * @throws FxNotFoundException if inactive or not existant
     */
    public static void checkMandatorExistance(long id) throws FxNotFoundException {
        Mandator m = CacheAdmin.getEnvironment().getMandator(id);
        if (!m.isActive())
            throw new FxNotFoundException("ex.structure.mandator.notFound.notActive", m.getName(), id);
    }

    /**
     * Checks if a UserTicket contains at least one of a given List of (content) ACLs
     *
     * @param ticket the UserTicket
     * @param ACLIds a List of (content) ACL ids
     * @return returns true if a match can be found
     */
    public static boolean currentUserInACLList(UserTicket ticket, List<Long> ACLIds) {
        for (Long id : ACLIds) {
            for (ACLAssignment a : ticket.getACLAssignments()) {
                if (a.getAclId() == id)
                    return true;
            }
        }
        return false;
    }
}