ru.runa.wfe.security.dao.PermissionDao.java Source code

Java tutorial

Introduction

Here is the source code for ru.runa.wfe.security.dao.PermissionDao.java

Source

/*
 * This file is part of the RUNA WFE project.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; version 2.1
 * of the License.
 *
 * This program 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 */
package ru.runa.wfe.security.dao;

import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.mysema.commons.lang.CloseableIterator;
import com.querydsl.jpa.JPQLQuery;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import ru.runa.wfe.InternalApplicationException;
import ru.runa.wfe.commons.CollectionUtil;
import ru.runa.wfe.commons.SystemProperties;
import ru.runa.wfe.commons.TimeMeasurer;
import ru.runa.wfe.commons.dao.CommonDao;
import ru.runa.wfe.presentation.BatchPresentation;
import ru.runa.wfe.presentation.hibernate.CompilerParameters;
import ru.runa.wfe.presentation.hibernate.PresentationCompiler;
import ru.runa.wfe.presentation.hibernate.RestrictionsToPermissions;
import ru.runa.wfe.security.ApplicablePermissions;
import ru.runa.wfe.security.AuthorizationException;
import ru.runa.wfe.security.Permission;
import ru.runa.wfe.security.PermissionSubstitutions;
import ru.runa.wfe.security.SecuredObject;
import ru.runa.wfe.security.SecuredObjectType;
import ru.runa.wfe.user.Executor;
import ru.runa.wfe.user.User;
import ru.runa.wfe.user.dao.ExecutorDao;

/**
 * Permission DAO level implementation via Hibernate.
 *
 * @author Konstantinov Aleksey 19.02.2012
 */
@Component
@SuppressWarnings("unchecked")
public class PermissionDao extends CommonDao {

    private static final List<List<Long>> nonEmptyListList = Collections
            .singletonList(Collections.singletonList(1L));
    private static final Set<Long> nonEmptySet = Collections.singleton(1L);

    @Autowired
    private ExecutorDao executorDao;
    @Autowired
    private SessionFactory sessionFactory;

    private final Map<SecuredObjectType, Set<Executor>> privelegedExecutors = new HashMap<>();
    private final Set<Long> privelegedExecutorIds = new HashSet<>();

    public PermissionDao() {
        for (SecuredObjectType type : SecuredObjectType.values()) {
            privelegedExecutors.put(type, new HashSet<>());
        }
    }

    /**
     * Called once after patches are successfully applied.
     */
    public void init() {
        QPrivelegedMapping pm = QPrivelegedMapping.privelegedMapping;
        CloseableIterator<PrivelegedMapping> i = queryFactory.selectFrom(pm).iterate();
        while (i.hasNext()) {
            PrivelegedMapping m = i.next();
            privelegedExecutors.get(m.getType()).add(m.getExecutor());
            privelegedExecutorIds.add(m.getExecutor().getId());
        }
        i.close();
    }

    public List<Permission> getIssuedPermissions(Executor executor, SecuredObject object) {
        QPermissionMapping pm = QPermissionMapping.permissionMapping;
        return queryFactory.select(pm.permission).from(pm).where(pm.objectType.eq(object.getSecuredObjectType())
                .and(pm.objectId.eq(object.getIdentifiableId())).and(pm.executor.eq(executor))).fetch();
    }

    /**
     * Sets permissions for executor on securedObject.
     *
     * @param executor
     *            Executor, which got permissions.
     * @param permissions
     *            Permissions for executor.
     * @param object
     *            Secured object to set permission on.
     */
    public void setPermissions(Executor executor, Collection<Permission> permissions, SecuredObject object) {
        ApplicablePermissions.check(object, permissions);
        if (isPrivilegedExecutor(object, executor)) {
            logger.debug(permissions + " not granted for privileged " + executor);
            return;
        }

        List<Permission> issued = getIssuedPermissions(executor, object);
        Set<Permission> toAdd = new HashSet<>(permissions);
        toAdd.removeAll(issued);
        Set<Permission> toDelete = new HashSet<>(issued);
        toDelete.removeAll(permissions);

        if (!toAdd.isEmpty()) {
            Session session = sessionFactory.getCurrentSession();
            for (Permission p : toAdd) {
                session.save(new PermissionMapping(executor, object, p));
            }
        }
        if (!toDelete.isEmpty()) {
            QPermissionMapping pm = QPermissionMapping.permissionMapping;
            queryFactory.delete(pm)
                    .where(pm.objectType.eq(object.getSecuredObjectType())
                            .and(pm.objectId.eq(object.getIdentifiableId())).and(pm.executor.eq(executor))
                            .and(pm.permission.in(toDelete)))
                    .execute();
        }
    }

    /**
     * Throws if user has no permission to object.
     */
    public void checkAllowed(User user, Permission permission, SecuredObject object) {
        if (!isAllowed(user, permission, object)) {
            throw new AuthorizationException(user + " does not have " + permission + " to " + object);
        }
    }

    /**
     * Throws if user has no permission to {type, id}.
     */
    public void checkAllowed(User user, Permission permission, SecuredObjectType type, Long id) {
        if (!isAllowed(user, permission, type, id)) {
            throw new AuthorizationException(
                    user + " does not have " + permission + " to (" + type + ", " + id + ")");
        }
    }

    /**
     * Throws if user has no permission to {type, all given ids}.
     */
    public void checkAllowedForAll(User user, Permission permission, SecuredObjectType type, List<Long> ids) {
        Assert.notNull(ids);
        List<Long> notAllowed = CollectionUtil.diffList(ids,
                filterAllowedIds(user.getActor(), permission, type, ids));
        if (!notAllowed.isEmpty()) {
            Collections.sort(notAllowed);
            throw new AuthorizationException("User " + user + " does not have " + permission + " on all of (" + type
                    + ", " + notAllowed + ")");
        }
    }

    /**
     * Returns true if user have permission to object.
     */
    public boolean isAllowed(User user, Permission permission, SecuredObject object) {
        return isAllowed(user.getActor(), permission, object.getSecuredObjectType(), object.getIdentifiableId());
    }

    public boolean isAllowed(User user, Permission permission, SecuredObjectType type, Long id) {
        return isAllowed(user.getActor(), permission, type, id);
    }

    public boolean isAllowed(Executor executor, Permission permission, SecuredObjectType type, Long id) {
        Assert.notNull(id);
        return !filterAllowedIds(executor, permission, type, Collections.singletonList(id)).isEmpty();
    }

    public boolean isAllowed(Executor executor, Permission permission, SecuredObject object,
            boolean checkPrivileged) {
        Long id = object.getIdentifiableId();
        SecuredObjectType type = object.getSecuredObjectType();
        Assert.notNull(id);
        return !filterAllowedIds(executor, permission, type, Collections.singletonList(id), checkPrivileged)
                .isEmpty();
    }

    /**
     * Returns true if user have permission to {type, any id}.
     */
    public boolean isAllowedForAny(User user, Permission permission, SecuredObjectType type) {
        return !filterAllowedIds(user.getActor(), permission, type, null).isEmpty();
    }

    public Set<Long> filterAllowedIds(Executor executor, Permission permission, SecuredObjectType type,
            List<Long> idsOrNull) {
        return filterAllowedIds(executor, permission, type, idsOrNull, true);
    }

    /**
     * Returns subset of `idsOrNull` for which `actor` has `permission`. If `idsOrNull` is null (e.g. when called from isAllowedForAny()),
     * non-empty set (containing arbitrary value) means positive check result.
     *
     * @param checkPrivileged If false, only permission_mapping table is checked, but not privileged_mapping.
     */
    public Set<Long> filterAllowedIds(Executor executor, Permission permission, SecuredObjectType type,
            List<Long> idsOrNull, boolean checkPrivileged) {
        ApplicablePermissions.check(type, permission);
        boolean haveIds = idsOrNull != null;

        if (permission == Permission.NONE) {
            // Optimization; see comments at NONE definition.
            return Collections.emptySet();
        }

        final Set<Executor> executorWithGroups = getExecutorWithAllHisGroups(executor);
        if (checkPrivileged && isPrivilegedExecutor(type, executorWithGroups)) {
            return haveIds ? new HashSet<>(idsOrNull) : nonEmptySet;
        }

        PermissionSubstitutions.ForCheck subst = PermissionSubstitutions.getForCheck(type, permission);
        QPermissionMapping pm = QPermissionMapping.permissionMapping;

        // Same type for all objects, thus same listType. I believe it would be faster to perform separate query here.
        // ATTENTION!!! Also, HQL query with two conditions (on both type and listType) always returns empty rowset. :(
        //              (Both here with QueryDSL and in HibernateCompilerHQLBuilder.addSecureCheck() with raw HQL.)
        if (!subst.listPermissions.isEmpty() && queryFactory.select(pm.id).from(pm)
                .where(pm.executor.in(executorWithGroups).and(pm.objectType.eq(type.getListType()))
                        .and(pm.objectId.eq(0L)).and(pm.permission.in(subst.listPermissions)))
                .fetchFirst() != null) {
            return haveIds ? new HashSet<>(idsOrNull) : nonEmptySet;
        }

        Set<Long> result = new HashSet<>();
        for (List<Long> idsPart : haveIds
                ? Lists.partition(idsOrNull, SystemProperties.getDatabaseParametersCount())
                : nonEmptyListList) {
            JPQLQuery<Long> q = queryFactory.select(pm.id).from(pm).where(pm.executor.in(executorWithGroups)
                    .and(pm.objectType.eq(type)).and(pm.permission.in(subst.selfPermissions)));
            if (haveIds) {
                result.addAll(q.where(pm.objectId.in(idsPart)).fetch());
            } else if (q.fetchFirst() != null) {
                return nonEmptySet;
            }
        }
        return result;
    }

    /**
     * Checks whether executor has permission on securedObject's. Create result array in same order, as securedObject's.
     *
     * @param user
     *            Executor, which permission must be check.
     * @param permission
     *            Checking permission.
     * @param securedObjects
     *            Secured objects to check permission on.
     * @return Array of: true if executor has requested permission on securedObject; false otherwise.
     * @deprecated Use filterAllowedIds() which takes list of IDs, not of whole entities.
     */
    @Deprecated
    public <T extends SecuredObject> boolean[] isAllowed(User user, Permission permission, List<T> securedObjects) {
        boolean[] result = new boolean[securedObjects.size()];
        if (result.length == 0) {
            return result;
        }

        if (permission == Permission.NONE) {
            // Optimization; see comments at NONE definition.
            Arrays.fill(result, false);
            return result;
        }

        SecuredObjectType type = securedObjects.get(0).getSecuredObjectType();
        Set<Executor> executorWithGroups = getExecutorWithAllHisGroups(user.getActor());
        if (isPrivilegedExecutor(type, executorWithGroups)) {
            Arrays.fill(result, true);
            return result;
        }

        PermissionSubstitutions.ForCheck subst = PermissionSubstitutions.getForCheck(type, permission);
        QPermissionMapping pm = QPermissionMapping.permissionMapping;
        // Same type for all objects, thus same listType. I believe it would be faster to perform separate query here.
        if (!subst.listPermissions.isEmpty() && queryFactory.select(pm.id).from(pm)
                .where(pm.executor.in(executorWithGroups).and(pm.objectType.eq(type.getListType()))
                        .and(pm.objectId.eq(0L)).and(pm.permission.in(subst.listPermissions)))
                .fetchFirst() != null) {
            Arrays.fill(result, true);
            return result;
        }

        Set<Long> allowedIdentifiableIds = new HashSet<>(result.length);
        int window = SystemProperties.getDatabaseParametersCount() - executorWithGroups.size() - 2;
        Preconditions.checkArgument(window > 100);
        for (int i = 0; i <= (result.length - 1) / window; ++i) {
            int start = i * window;
            int end = Math.min((i + 1) * window, result.length);
            List<Long> identifiableIds = new ArrayList<>(end - start);
            for (int j = start; j < end; j++) {
                SecuredObject securedObject = securedObjects.get(j);
                identifiableIds.add(securedObject.getIdentifiableId());
                if (type != securedObject.getSecuredObjectType()) {
                    throw new InternalApplicationException(
                            "Secured objects should be of the same secured object type (" + type + ")");
                }
            }
            if (identifiableIds.isEmpty()) {
                break;
            }
            allowedIdentifiableIds.addAll(queryFactory.selectDistinct(pm.objectId).from(pm)
                    .where(pm.executor.in(executorWithGroups).and(pm.objectType.eq(type))
                            .and(pm.objectId.in(identifiableIds)).and(pm.permission.in(subst.selfPermissions)))
                    .fetch());
        }
        for (int i = 0; i < securedObjects.size(); i++) {
            result[i] = allowedIdentifiableIds.contains(securedObjects.get(i).getIdentifiableId());
        }
        return result;
    }

    private Set<Executor> getExecutorWithAllHisGroups(Executor executor) {
        Set<Executor> set = new HashSet<>(executorDao.getExecutorParentsAll(executor, false));
        set.add(executor);
        return set;
    }

    /**
     * Deletes all permissions for executor.
     */
    public void deleteOwnPermissions(Executor executor) {
        QPermissionMapping pm = QPermissionMapping.permissionMapping;
        queryFactory.delete(pm).where(pm.executor.eq(executor)).execute();
    }

    /**
     * Deletes all permissions for securedObject.
     */
    public void deleteAllPermissions(SecuredObject obj) {
        QPermissionMapping pm = QPermissionMapping.permissionMapping;
        queryFactory.delete(pm)
                .where(pm.objectType.eq(obj.getSecuredObjectType()).and(pm.objectId.eq(obj.getIdentifiableId())))
                .execute();
    }

    /**
     * Load {@linkplain Executor}s which have permission on {@linkplain SecuredObject}. <b>Paging is not enabled.</b>
     */
    public Set<Executor> getExecutorsWithPermission(SecuredObject obj) {
        QPermissionMapping pm = QPermissionMapping.permissionMapping;
        List<Executor> list = queryFactory.selectDistinct(pm.executor).from(pm)
                .where(pm.objectType.eq(obj.getSecuredObjectType()).and(pm.objectId.eq(obj.getIdentifiableId())))
                .fetch();
        Set<Executor> result = new HashSet<>(list);
        result.addAll(getPrivilegedExecutors(obj.getSecuredObjectType()));
        return result;
    }

    /**
     * Return array of privileged {@linkplain Executor}s for given (@linkplain SecuredObject) type (i.e. executors whose permissions on SecuredObject
     * type can not be changed).
     *
     * @return Privileged {@linkplain Executor}'s array.
     */
    public Collection<Executor> getPrivilegedExecutors(SecuredObjectType securedObjectType) {
        return privelegedExecutors.get(securedObjectType);
    }

    /**
     * Check if executor is privileged executor for any secured object type.
     */
    public boolean isPrivilegedExecutor(Executor executor) {
        for (Set<Executor> executors : privelegedExecutors.values()) {
            if (executors.contains(executor)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Check if executor is privileged executor for any secured object type.
     */
    public boolean hasPrivilegedExecutor(List<Long> executorIds) {
        for (Long executorId : executorIds) {
            if (privelegedExecutorIds.contains(executorId)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Check if executor is privileged executor for given object.
     *
     * @param executor
     *            {@linkplain Executor}, to check if privileged.
     * @param object
     *            {@linkplain SecuredObject} object, to check if executor is privileged to it.
     * @return true if executor is privileged for given object and false otherwise.
     */
    private boolean isPrivilegedExecutor(SecuredObject object, Executor executor) {
        Collection<Executor> executorWithGroups = getExecutorWithAllHisGroups(executor);
        return isPrivilegedExecutor(object.getSecuredObjectType(), executorWithGroups);
    }

    private boolean isPrivilegedExecutor(SecuredObjectType type, Collection<Executor> executorWithGroups) {
        for (Executor executor : executorWithGroups) {
            if (getPrivilegedExecutors(type).contains(executor)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Adds new record in <i>dictionary</i> tables describing new SecuredObject type.
     *
     * @param type
     *            Type of SecuredObject.
     * @param executors
     *            Privileged executors for target class.
     */
    public void addType(SecuredObjectType type, List<? extends Executor> executors) {
        for (Executor executor : executors) {
            PrivelegedMapping mapping = new PrivelegedMapping(type, executor);
            sessionFactory.getCurrentSession().save(mapping);
            privelegedExecutors.get(mapping.getType()).add(mapping.getExecutor());
            privelegedExecutorIds.add(mapping.getExecutor().getId());
        }
    }

    /**
     * Load list of {@linkplain SecuredObject} for which executors have permission on.
     *
     * @param user
     *            User which must have permission on loaded {@linkplain SecuredObject} (at least one).
     * @param batchPresentation
     *            {@linkplain BatchPresentation} with parameters for loading {@linkplain SecuredObject}'s.
     * @param permission
     *            {@linkplain Permission}, which executors must has on {@linkplain SecuredObject}.
     * @param securedObjectTypes
     *            {@linkplain SecuredObjectType} types, used to check permissions.
     * @param enablePaging
     *            Flag, equals true, if paging must be enabled and false otherwise.
     * @return List of {@link SecuredObject}'s for which executors have permission on.
     */
    public List<? extends SecuredObject> getPersistentObjects(User user, BatchPresentation batchPresentation,
            Permission permission, SecuredObjectType[] securedObjectTypes, boolean enablePaging) {
        TimeMeasurer timeMeasurer = new TimeMeasurer(logger, 1000);
        timeMeasurer.jobStarted();
        RestrictionsToPermissions permissions = new RestrictionsToPermissions(user, permission, securedObjectTypes);
        CompilerParameters parameters = CompilerParameters.create(enablePaging).addPermissions(permissions);
        List<? extends SecuredObject> result = new PresentationCompiler(batchPresentation).getBatch(parameters);
        timeMeasurer.jobEnded("getObjects: " + result.size());
        if (result.size() == 0 && enablePaging && batchPresentation.getPageNumber() > 1) {
            logger.debug("resetting batch presentation to first page due to 0 results");
            batchPresentation.setPageNumber(1);
            result = getPersistentObjects(user, batchPresentation, permission, securedObjectTypes, enablePaging);
        }
        return result;
    }

    /**
     * Load count of {@linkplain SecuredObject} for which executors have permission on.
     *
     * @param user
     *            User which must have permission on loaded {@linkplain SecuredObject} (at least one).
     * @param batchPresentation
     *            {@linkplain BatchPresentation} with parameters for loading {@linkplain SecuredObject}'s.
     * @param permission
     *            {@linkplain Permission}, which executors must have on {@linkplain SecuredObject}.
     * @param securedObjectTypes
     *            {@linkplain SecuredObjectType} types, used to check permissions.
     * @return Count of {@link SecuredObject}'s for which executors have permission on.
     */
    public int getPersistentObjectCount(User user, BatchPresentation batchPresentation, Permission permission,
            SecuredObjectType[] securedObjectTypes) {
        TimeMeasurer timeMeasurer = new TimeMeasurer(logger, 1000);
        timeMeasurer.jobStarted();
        RestrictionsToPermissions permissions = new RestrictionsToPermissions(user, permission, securedObjectTypes);
        CompilerParameters parameters = CompilerParameters.createNonPaged().addPermissions(permissions);
        int count = new PresentationCompiler(batchPresentation).getCount(parameters);
        timeMeasurer.jobEnded("getCount: " + count);
        return count;
    }

    public boolean permissionExists(final Executor executor, final Permission permission,
            final SecuredObject object) {
        QPermissionMapping pm = QPermissionMapping.permissionMapping;
        return queryFactory.select(pm.id).from(pm)
                .where(pm.executor.eq(executor).and(pm.objectType.eq(object.getSecuredObjectType()))
                        .and(pm.objectId.eq(object.getIdentifiableId())).and(pm.permission.eq(permission)))
                .fetchFirst() != null;
    }
}