/**
* 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);
}
}
}