org.squashtest.tm.service.security.acls.jdbc.DerivedPermissionsManager.java Source code

Java tutorial

Introduction

Here is the source code for org.squashtest.tm.service.security.acls.jdbc.DerivedPermissionsManager.java

Source

/**
 *     This file is part of the Squashtest platform.
 *     Copyright (C) 2010 - 2016 Henix, henix.fr
 *
 *     See the NOTICE file distributed with this work for additional
 *     information regarding copyright ownership.
 *
 *     This 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, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     this software 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 software.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.squashtest.tm.service.security.acls.jdbc;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.type.LongType;
import org.hibernate.type.StringType;
import org.springframework.security.acls.model.ObjectIdentity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.squashtest.tm.domain.project.Project;
import org.squashtest.tm.security.acls.CustomPermission;
import org.squashtest.tm.service.internal.repository.hibernate.SqLIdResultTransformer;

@Service
@Transactional
class DerivedPermissionsManager {

    private static final String PROJECT_CLASS_NAME = Project.class.getName();

    private static final String PERM_MANAGEMENT = Integer.toString(CustomPermission.MANAGEMENT.getMask());

    private static final String REMOVE_CORE_PARTY_MANAGER_AUTHORITY = "delete from CORE_PARTY_AUTHORITY where PARTY_ID in (:ids) and AUTHORITY = 'ROLE_TM_PROJECT_MANAGER'";
    private static final String INSERT_CORE_PARTY_MANAGER_AUTHORITY = "insert into CORE_PARTY_AUTHORITY(PARTY_ID, AUTHORITY) values (:id, 'ROLE_TM_PROJECT_MANAGER')";

    private static final String CHECK_OBJECT_IDENTITY_EXISTENCE = "select aoi.ID from ACL_OBJECT_IDENTITY aoi "
            + "inner join ACL_CLASS acc on acc.ID = aoi.CLASS_ID "
            + "where aoi.IDENTITY = :id and acc.CLASSNAME = :class";

    private static final String CHECK_PARTY_EXISTENCE = "select PARTY_ID from CORE_PARTY where PARTY_ID = :id";

    private static final String FIND_ALL_USERS = "select PARTY_ID from CORE_USER";

    private static final String FIND_TEAM_MEMBERS_OR_USER = "select cu.PARTY_ID from CORE_USER cu "
            + "where cu.PARTY_ID = :id " + "UNION " + "select cu.PARTY_ID from CORE_USER cu "
            + "inner join CORE_TEAM_MEMBER ctm on ctm.USER_ID = cu.PARTY_ID "
            + "inner join CORE_TEAM ct on ct.PARTY_ID = ctm.TEAM_ID " + "where ct.PARTY_ID = :id";

    private static final String FIND_PARTIES_USING_IDENTITY = "select arse.PARTY_ID from ACL_RESPONSIBILITY_SCOPE_ENTRY arse "
            + "inner join ACL_OBJECT_IDENTITY aoi on arse.OBJECT_IDENTITY_ID = aoi.ID "
            + "inner join ACL_CLASS acc on aoi.CLASS_ID = acc.ID "
            + "inner join ACL_GROUP_PERMISSION acp on acp.ACL_GROUP_ID = arse.ACL_GROUP_ID "
            + "where acp.CLASS_ID = acc.ID " + "and aoi.IDENTITY = :id and acc.CLASSNAME = :class ";

    private static final String RETAIN_USERS_MANAGING_ANYTHING = "select arse.PARTY_ID from ACL_RESPONSIBILITY_SCOPE_ENTRY arse "
            + "inner join ACL_OBJECT_IDENTITY aoi on arse.OBJECT_IDENTITY_ID = aoi.ID "
            + "inner join ACL_CLASS acc on aoi.CLASS_ID = acc.ID "
            + "inner join ACL_GROUP_PERMISSION acp on acp.ACL_GROUP_ID = arse.ACL_GROUP_ID "
            + "where acp.CLASS_ID = acc.ID and acp.PERMISSION_MASK = " + PERM_MANAGEMENT + " "
            + "and acc.CLASSNAME in ('org.squashtest.tm.domain.project.Project', 'org.squashtest.tm.domain.project.ProjectTemplate') "
            + "and arse.PARTY_ID in (:ids)";

    private static final String RETAIN_MEMBERS_OF_TEAMS_MANAGING_ANYTHING = "select cu.PARTY_ID from CORE_USER cu "
            + "inner join CORE_TEAM_MEMBER ctm on ctm.USER_ID = cu.PARTY_ID "
            + "inner join ACL_RESPONSIBILITY_SCOPE_ENTRY arse on arse.PARTY_ID = ctm.TEAM_ID "
            + "inner join ACL_OBJECT_IDENTITY aoi on arse.OBJECT_IDENTITY_ID = aoi.ID "
            + "inner join ACL_CLASS acc on aoi.CLASS_ID = acc.ID "
            + "inner join ACL_GROUP_PERMISSION acp on acp.ACL_GROUP_ID = arse.ACL_GROUP_ID "
            + "where acp.CLASS_ID = acc.ID and acp.PERMISSION_MASK = " + PERM_MANAGEMENT + " "
            + "and acc.CLASSNAME in ('org.squashtest.tm.domain.project.Project', 'org.squashtest.tm.domain.project.ProjectTemplate') "
            + "and cu.PARTY_ID in (:ids)";

    @PersistenceContext
    private EntityManager em;

    void updateDerivedPermissions(ObjectIdentity identity) {
        updateDerivedAuths(identity);
    }

    void updateDerivedPermissions(long partyId) {
        updateDerivedAuths(partyId);
    }

    void updateDerivedPermissions(long partyId, ObjectIdentity identity) {

        // as for now we don't have a use for the identity argument yet. That might change later though.
        //updateDerivedAcl(partyId);
        updateDerivedAuths(partyId);
    }

    // *************************** private ******************************

    private void updateDerivedAuths(ObjectIdentity identity) {

        flush();

        if (!isSortOfProject(identity)) {
            return;
        }

        if (doesExist(identity)) {
            Collection<Long> userIds = findUsers(identity);
            updateAuthsForThoseUsers(userIds);

        } else {
            // corner case : the target object doesn't exist anymore so we can't find
            // which users were using it. We must then update them all.
            updateDerivedAuths();
        }

    }

    private void updateDerivedAuths(long partyId) {

        flush();

        if (doesExist(partyId)) {
            Collection<Long> memberIds = findMembers(partyId);
            updateAuthsForThoseUsers(memberIds);
        } else {
            // corner case : the target party doesn't exist anymore so we don't know
            // which team members were part of it (in case of a team). We must then update
            // all the users.
            updateDerivedAuths();
        }

    }

    // will update all users, no exceptions
    private void updateDerivedAuths() {

        flush();

        Collection<Long> allUsers = findAllUsers();

        updateAuthsForThoseUsers(allUsers);
    }

    private void updateAuthsForThoseUsers(Collection<Long> userIds) {

        removeProjectManagerAuthorities(userIds);

        Collection<Long> managerIds = retainUsersManagingAnything(userIds);

        grantProjectManagerAuthorities(managerIds);
    }

    // ******************************** helpers ***********************************

    /*
     *  will help to cut some uneeded computations and DB calls. For now only project-level permissions induces derived permissions.
     *  also, remember that : DONT BE SHY and modify/remove it if the permission management specs ever changes, instead of working around !
     */
    private boolean isSortOfProject(ObjectIdentity identity) {
        String type = identity.getType();
        return type.equals(PROJECT_CLASS_NAME);
    }

    private boolean doesExist(ObjectIdentity identity) {

        Query query = em.unwrap(Session.class).createSQLQuery(CHECK_OBJECT_IDENTITY_EXISTENCE);
        query.setParameter("id", identity.getIdentifier(), LongType.INSTANCE);
        query.setParameter("class", identity.getType());

        List<?> result = query.list();
        return !result.isEmpty();
    }

    private boolean doesExist(long partyId) {

        Query query = em.unwrap(Session.class).createSQLQuery(CHECK_PARTY_EXISTENCE);
        query.setParameter("id", partyId, LongType.INSTANCE);

        List<?> result = query.list();
        return !result.isEmpty();
    }

    // will find all members of a team given its id. It the id actually refers to a user, that user id will be the only result.
    private Collection<Long> findMembers(long partyId) {

        Query query = em.unwrap(Session.class).createSQLQuery(FIND_TEAM_MEMBERS_OR_USER);
        query.setParameter("id", partyId, LongType.INSTANCE);
        query.setResultTransformer(new SqLIdResultTransformer());
        return query.list();

    }

    // will find all the users
    private Collection<Long> findUsers(ObjectIdentity identity) {

        // first find the parties managing that thing
        Query query = em.unwrap(Session.class).createSQLQuery(FIND_PARTIES_USING_IDENTITY);
        query.setParameter("id", identity.getIdentifier(), LongType.INSTANCE);
        query.setParameter("class", identity.getType(), StringType.INSTANCE);
        query.setResultTransformer(new SqLIdResultTransformer());

        Collection<Long> partyIds = query.list();

        // then find the corresponding users
        Collection<Long> userIds = new HashSet<>();
        for (Long id : partyIds) {
            userIds.addAll(findMembers(id));
        }

        return userIds;

    }

    private Collection<Long> findAllUsers() {
        Query query = em.unwrap(Session.class).createSQLQuery(FIND_ALL_USERS);
        query.setResultTransformer(new SqLIdResultTransformer());
        return query.list();
    }

    private void removeProjectManagerAuthorities(Collection<Long> ids) {
        if (!ids.isEmpty()) {
            Query query = em.unwrap(Session.class).createSQLQuery(REMOVE_CORE_PARTY_MANAGER_AUTHORITY);
            query.setParameterList("ids", ids, LongType.INSTANCE);
            query.executeUpdate();
        }
    }

    private Collection<Long> retainUsersManagingAnything(Collection<Long> ids) {
        if (!ids.isEmpty()) {

            Set<Long> userIds = new HashSet<>();
            Collection<Long> buffer;

            // first, get users directly managing anything
            Query query = em.unwrap(Session.class).createSQLQuery(RETAIN_USERS_MANAGING_ANYTHING);
            query.setParameterList("ids", ids, LongType.INSTANCE);
            query.setResultTransformer(new SqLIdResultTransformer());

            buffer = query.list();
            userIds.addAll(buffer);

            // second, get users managing through teams or project leaders (which sounds quite silly I agree)
            query = em.unwrap(Session.class).createSQLQuery(RETAIN_MEMBERS_OF_TEAMS_MANAGING_ANYTHING);
            query.setParameterList("ids", ids, LongType.INSTANCE);
            query.setResultTransformer(new SqLIdResultTransformer());

            buffer = query.list();
            userIds.addAll(buffer);

            return userIds;
        } else {
            return Collections.emptyList();
        }
    }

    private void grantProjectManagerAuthorities(Collection<Long> ids) {
        Query query;
        for (Long id : ids) {
            query = em.unwrap(Session.class).createSQLQuery(INSERT_CORE_PARTY_MANAGER_AUTHORITY);
            query.setParameter("id", id, LongType.INSTANCE);
            query.executeUpdate();
        }

    }

    private void flush() {
        em.unwrap(Session.class).flush();
    }

}