org.jtalks.jcommune.service.security.acl.AclGroupPermissionEvaluator.java Source code

Java tutorial

Introduction

Here is the source code for org.jtalks.jcommune.service.security.acl.AclGroupPermissionEvaluator.java

Source

/**
 * Copyright (C) 2011  JTalks.org Team
 * This library 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 2.1 of the License, or (at your option) any later version.
 * 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
 * Lesser General Public License for more details.
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

package org.jtalks.jcommune.service.security.acl;

import org.apache.commons.lang.Validate;
import org.jtalks.common.model.entity.Group;
import org.jtalks.common.model.permissions.BranchPermission;
import org.jtalks.common.model.permissions.GeneralPermission;
import org.jtalks.common.model.permissions.JtalksPermission;
import org.jtalks.common.model.permissions.ProfilePermission;
import org.jtalks.jcommune.model.entity.JCUser;
import org.jtalks.jcommune.model.entity.UserInfo;
import org.jtalks.jcommune.plugin.api.PluginPermissionManager;
import org.jtalks.jcommune.service.security.SecurityService;
import org.jtalks.jcommune.service.security.acl.sids.JtalksSidFactory;
import org.jtalks.jcommune.service.security.acl.sids.UniversalSid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.acls.jdbc.JdbcMutableAclService;
import org.springframework.security.acls.model.*;
import org.springframework.security.core.Authentication;

import javax.annotation.Nonnull;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

/**
 * This evaluator is used to process the annotations of the Spring Security like {@link
 * org.springframework.security.access.prepost.PreAuthorize}. In order to be able to use it, you need to specify the id
 * of object identity, the class of object identity and one of implementation of {@link
 * org.jtalks.common.model.permissions.JtalksPermission}. So it should look precisely like this:<br/> <code>
 * \@PreAuthorize("hasPermission(#topicId, 'TOPIC', 'GeneralPermission.WRITE')")</code>
 *
 * @author Elena Lepaeva
 * @author stanislav bashkirtsev
 */
public class AclGroupPermissionEvaluator implements PermissionEvaluator {
    private static final Logger LOGGER = LoggerFactory.getLogger(AclGroupPermissionEvaluator.class);

    private final AclManager aclManager;
    private final AclUtil aclUtil;
    private final JtalksSidFactory sidFactory;
    private final JdbcMutableAclService mutableAclService;
    private final PluginPermissionManager pluginPermissionManager;
    private final SecurityService securityService;

    /**
     * @param aclManager        for getting permissions on object indentity
     * @param aclUtil           utilities to work with Spring ACL
     * @param sidFactory        factory to work with principals
     * @param mutableAclService for checking existing of sids
     * @param securityService   to get current user from SecurityContext
     */
    public AclGroupPermissionEvaluator(@Nonnull AclManager aclManager, @Nonnull AclUtil aclUtil,
            @Nonnull JtalksSidFactory sidFactory, @Nonnull JdbcMutableAclService mutableAclService,
            @Nonnull PluginPermissionManager pluginPermissionManager, @Nonnull SecurityService securityService) {
        this.aclManager = aclManager;
        this.aclUtil = aclUtil;
        this.sidFactory = sidFactory;
        this.mutableAclService = mutableAclService;
        this.pluginPermissionManager = pluginPermissionManager;
        this.securityService = securityService;
    }

    /**
     * {@inheritDoc}
     * NOTE: Method with current arguments is not supported.
     */
    @Override
    @Deprecated
    public boolean hasPermission(Authentication authentication, Object targetId, Object permission) {
        throw new UnsupportedOperationException("Current implementation does not support this method");
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType,
            Object permission) {
        Long id = parseTargetId(targetId);
        JtalksPermission jtalksPermission = parseJtalksPermissionFrom(permission);
        if (jtalksPermission == ProfilePermission.EDIT_OWN_PROFILE
                && ((UserInfo) authentication.getPrincipal()).getId() != id) {
            return false;
        }
        ObjectIdentity objectIdentity = aclUtil.createIdentity(id, targetType);
        Sid sid = sidFactory.createPrincipal(authentication);
        List<AccessControlEntry> aces;
        List<GroupAce> controlEntries;

        try {
            aces = ExtendedMutableAcl.castAndCreate(mutableAclService.readAclById(objectIdentity)).getEntries();
            controlEntries = aclManager.getGroupPermissionsFilteredByPermissionOn(objectIdentity, jtalksPermission);
        } catch (NotFoundException nfe) {
            aces = new ArrayList<>();
            controlEntries = new ArrayList<>();
        }
        if (jtalksPermission instanceof ProfilePermission && authentication.getPrincipal() instanceof UserInfo) {
            if (isRestrictedPersonalPermission(authentication, jtalksPermission))
                return false;
            else if (isAllowedPersonalPermission(authentication, jtalksPermission))
                return true;
        }
        if (isRestrictedForSid(sid, aces, jtalksPermission)
                || isRestrictedForGroup(controlEntries, authentication, jtalksPermission)) {
            return false;
        } else if (isAllowedForSid(sid, aces, jtalksPermission)
                || isAllowedForGroup(controlEntries, authentication, jtalksPermission)) {
            return true;
        }
        return false;
    }

    /**
     * Parses targetId parameter
     *
     * @param targetId parameter value to parse.
     * @return targetId as Long.
     */
    private Long parseTargetId(Serializable targetId) {
        Validate.isTrue(targetId instanceof String || targetId instanceof Long,
                "Can't parse targetId, value=[" + targetId + "]");
        if (targetId instanceof String)
            return Long.parseLong((String) targetId);
        return (Long) targetId;
    }

    /**
     * Check if this <tt>personal permission</tt> is allowed for groups of user from authentication
     *
     * @return <code>true</code> if this permission is allowed
     */
    private boolean isAllowedPersonalPermission(Authentication authentication, Permission permission) {
        return isGrantedPersonalPermission(authentication, permission, true);
    }

    /**
     * Check if this <tt>personal permission</tt> is restricted for groups of user from authentication
     *
     * @return <code>true</code> if this permission is restricted
     */
    private boolean isRestrictedPersonalPermission(Authentication authentication, Permission permission) {
        return isGrantedPersonalPermission(authentication, permission, false);
    }

    /**
     * Check if this <tt>permission</tt> is allowed for specified <tt>sid</tt>
     *
     * @param sid            sid to check permission for it
     * @param controlEntries list of records with security information for sids
     * @param permission     permission to check
     * @return <code>true</code> if this permission is allowed
     */
    private boolean isAllowedForSid(Sid sid, List<AccessControlEntry> controlEntries, Permission permission) {
        return isGrantedForSid(sid, controlEntries, permission, true);
    }

    /**
     * Check if this <tt>permission</tt> is restricted for specified <tt>sid</tt>
     *
     * @param sid            sid to check permission for it
     * @param controlEntries list of records with security information for sids
     * @param permission     permission to check
     * @return <code>true</code> if this permission is restricted
     */
    private boolean isRestrictedForSid(Sid sid, List<AccessControlEntry> controlEntries, Permission permission) {
        return isGrantedForSid(sid, controlEntries, permission, false);
    }

    /**
     * Check if this <tt>permission</tt> is granted for specified <tt>sid</tt>
     *
     * @param sid                 sid to check permission for it
     * @param controlEntries      list of records with security information for sids
     * @param permission          permission to check
     * @param isCheckAllowedGrant flag that indicates what type of grant need to
     *                            be checked  - 'allowed' (true) or 'restricted' (false)
     * @return <code>true</code> if this permission was found with specified
     *         type of grant.
     */
    private boolean isGrantedForSid(Sid sid, List<AccessControlEntry> controlEntries, Permission permission,
            boolean isCheckAllowedGrant) {
        for (AccessControlEntry ace : controlEntries) {
            if (isGrantedForSid(sid, ace, permission, isCheckAllowedGrant)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Check if this <tt>permission</tt> is granted for specified <tt>sid</tt>
     *
     * @param sid                 sid to check permission for it
     * @param ace                 entry with security information (for sids)
     * @param permission          permission to check
     * @param isCheckAllowedGrant flag that indicates what type of grant need to
     *                            be checked  - 'allowed' (true) or 'restricted' (false)
     * @return <code>true</code> if this entry has specified <tt>permission</tt>
     *         and type of grant.
     */
    private boolean isGrantedForSid(Sid sid, AccessControlEntry ace, Permission permission,
            boolean isCheckAllowedGrant) {
        return ace.isGranting() == isCheckAllowedGrant && permission.equals(ace.getPermission())
                && ((UniversalSid) sid).getSidId().equals(((UniversalSid) ace.getSid()).getSidId());
    }

    /**
     * Check if this <tt>permission</tt> is granted for groups of user from authentication
     *
     * @param permission          permission to check
     * @param isCheckAllowedGrant flag that indicates what type of grant need to
     *                            be checked  - 'allowed' (true) or 'restricted' (false)
     * @return <code>true</code> if this permission was found with specified
     *         type of grant.
     */
    private boolean isGrantedPersonalPermission(Authentication authentication, Permission permission,
            boolean isCheckAllowedGrant) {
        JCUser jcUser = securityService.getFullUserInfoFrom(authentication);
        if (jcUser == null)
            return false;
        List<Group> groups = jcUser.getGroups();
        for (Group group : groups) {
            ObjectIdentity groupIdentity = aclUtil.createIdentity(group.getId(), "GROUP");
            Sid groupSid = sidFactory.create(group);
            List<AccessControlEntry> groupAces;
            try {
                groupAces = ExtendedMutableAcl.castAndCreate(mutableAclService.readAclById(groupIdentity))
                        .getEntries();
            } catch (NotFoundException nfe) {
                groupAces = new ArrayList<>();
            }
            if (isGrantedForSid(groupSid, groupAces, permission, isCheckAllowedGrant)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Check if this <tt>permission</tt> is allowed for any <tt>authority's</tt>
     * group.
     *
     * @param controlEntries list of entries with security information for groups
     *                       to loop through
     * @param permission     permission to check
     * @return <code>true</code> if this permission is allowed.
     */
    private boolean isAllowedForGroup(List<GroupAce> controlEntries, Authentication authentication,
            Permission permission) {
        return isGrantedForGroup(controlEntries, authentication, permission, true);
    }

    /**
     * Check if this <tt>permission</tt> is restricted for any <tt>authority's</tt>
     * group.
     *
     * @param controlEntries list of entries with security information for groups
     *                       to loop through
     * @param permission     permission to check
     * @return <code>true</code> if this permission is restricted.
     */
    private boolean isRestrictedForGroup(List<GroupAce> controlEntries, Authentication authentication,
            Permission permission) {
        return isGrantedForGroup(controlEntries, authentication, permission, false);
    }

    /**
     * Check if this <tt>permission</tt> is granted for any <tt>authority's</tt>
     * group.
     *
     * @param controlEntries      list of entries with security information for groups
     *                            to loop through
     * @param permission          permission to check
     * @param isCheckAllowedGrant flag that indicates what type of grant need to
     *                            be checked  - 'allowed' (true) or 'restricted' (false)
     * @return <code>true</code> if this permission was found with specified
     *         type of grant.
     */
    private boolean isGrantedForGroup(List<GroupAce> controlEntries, Authentication authentication,
            Permission permission, boolean isCheckAllowedGrant) {
        JCUser jcUser = securityService.getFullUserInfoFrom(authentication);
        if (jcUser == null)
            return false;
        List<Long> groupsIDs = jcUser.getGroupsIDs();
        for (GroupAce ace : controlEntries) {
            if (groupsIDs.contains(ace.getGroupId())) {
                if (isGrantedForGroup(ace, permission, isCheckAllowedGrant))
                    return true;
            }
        }
        return false;
    }

    /**
     * Check if this <tt>permission</tt> is granted for any <tt>authority's</tt>
     * group.
     *
     * @param ace                 entry with security information (for groups)
     * @param permission          permission to check
     * @param isCheckAllowedGrant flag that indicates what type of grant need to
     *                            be checked  - 'allowed' (true) or 'restricted' (false)
     * @return <code>true</code> if this entry has specified <tt>permission</tt>
     *         and type of grant.
     */
    private boolean isGrantedForGroup(GroupAce ace, Permission permission, boolean isCheckAllowedGrant) {
        Permission permissionToComapare = ace.getPermission();
        if (permissionToComapare == null) {
            permissionToComapare = pluginPermissionManager
                    .findPluginsBranchPermissionByMask(ace.getPermissionMask());
        }
        return ace.isGranting() == isCheckAllowedGrant && permission.equals(permissionToComapare);
    }

    private JtalksPermission parseJtalksPermissionFrom(Object permission) {
        if (permission instanceof Permission)
            return (JtalksPermission) permission;
        String permissionName = (String) permission;

        if ((permissionName).startsWith(GeneralPermission.class.getSimpleName())) {
            String particularPermission = permissionName.replace(GeneralPermission.class.getSimpleName() + ".", "");
            return GeneralPermission.valueOf(particularPermission);

        } else if ((permissionName).startsWith(BranchPermission.class.getSimpleName())) {
            String particularPermission = permissionName.replace(BranchPermission.class.getSimpleName() + ".", "");
            return BranchPermission.valueOf(particularPermission);

        } else if ((permissionName).startsWith(ProfilePermission.class.getSimpleName())) {
            String particularPermission = permissionName.replace(ProfilePermission.class.getSimpleName() + ".", "");
            return ProfilePermission.valueOf(particularPermission);

        } else {
            throw new IllegalArgumentException("No other permissions that GeneralPermission are supported now. "
                    + "Was specified: " + permission);
        }
    }
}