/** * 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; import org.jtalks.common.model.entity.Branch; import org.jtalks.common.model.entity.Component; import org.jtalks.common.model.entity.Entity; 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.dao.GroupDao; import org.jtalks.jcommune.model.dto.GroupsPermissions; import org.jtalks.jcommune.model.dto.PermissionChanges; import org.jtalks.jcommune.model.entity.AnonymousGroup; import org.jtalks.jcommune.plugin.api.PluginPermissionManager; import org.jtalks.jcommune.service.security.acl.AclManager; import org.jtalks.jcommune.service.security.acl.AclUtil; import org.jtalks.jcommune.service.security.acl.GroupAce; import org.jtalks.jcommune.service.security.acl.builders.AclBuilders; import org.jtalks.jcommune.service.security.acl.sids.UniversalSid; import org.jtalks.jcommune.service.security.acl.sids.UserSid; import org.springframework.security.acls.model.AccessControlEntry; import org.springframework.security.acls.model.Permission; import org.springframework.security.acls.model.Sid; import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * Responsible for allowing, restricting or deleting the permissions of the User Groups to actions. * * @author stanislav bashkirtsev * @author Vyacheslav Zhivaev */ public class PermissionManager { private final AclManager aclManager; private final AclUtil aclUtil; private final GroupDao groupDao; private final PluginPermissionManager pluginPermissionManager; /** * Constructs {@link org.jtalks.jcommune.service.security.PermissionManager} with given * {@link AclManager} and {@link GroupDao} * * @param aclManager manager instance * @param groupDao group dao instance */ public PermissionManager(@Nonnull AclManager aclManager, @Nonnull GroupDao groupDao, @Nonnull AclUtil aclUtil, @Nonnull PluginPermissionManager pluginPermissionManager) { this.aclManager = aclManager; this.groupDao = groupDao; this.aclUtil = aclUtil; this.pluginPermissionManager = pluginPermissionManager; } /** * Changes the granted permissions according to the specified changes. * * @param entity the entity to change permissions to * @param changes contains a permission itself, a list of groups to be granted to the permission and the list of * groups to remove their granted privileges * @see org.jtalks.jcommune.model.dto.PermissionChanges#getNewlyAddedGroupsAsArray() * @see org.jtalks.jcommune.model.dto.PermissionChanges#getRemovedGroups() */ public void changeGrants(Entity entity, PermissionChanges changes) { for (Group group : changes.getNewlyAddedGroupsAsArray()) { changeGrantsOfGroup(group, changes.getPermission(), entity, true); } for (Group group : changes.getRemovedGroupsAsArray()) { deleteGrantsOfGroup(group, changes.getPermission(), entity); } } /** * Changes the restricting permissions according to the specified changes. * * @param entity the entity to change permissions to * @param changes contains a permission itself, a list of groups to be restricted from the permission and the list * of groups to remove their restricting privileges * @see org.jtalks.jcommune.model.dto.PermissionChanges#getNewlyAddedGroupsAsArray() * @see org.jtalks.jcommune.model.dto.PermissionChanges#getRemovedGroups() */ public void changeRestrictions(Entity entity, PermissionChanges changes) { for (Group group : changes.getNewlyAddedGroupsAsArray()) { changeGrantsOfGroup(group, changes.getPermission(), entity, false); } for (Group group : changes.getRemovedGroupsAsArray()) { deleteGrantsOfGroup(group, changes.getPermission(), entity); } } /** * Gets list of branch permissions for specified branch. Performs search in both of common and plugin permissions * * @param branch object identity * @return {@link org.jtalks.jcommune.model.dto.GroupsPermissions <BranchPermission>} for given branch */ public GroupsPermissions getPermissionsMapFor(Branch branch) { List<JtalksPermission> branchPermissions = new ArrayList<>(); branchPermissions.addAll(BranchPermission.getAllAsList()); branchPermissions.addAll(pluginPermissionManager.getPluginsBranchPermissions()); return getPermissionsMapFor(branchPermissions, branch); } /** * Search branch permission by mask. Firstly looks in common permissions then in plugin permissions * * @param mask interested permission mask * @return permission with specified mask if it exist * <b>null</b> otherwise */ public JtalksPermission findBranchPermissionByMask(int mask) { JtalksPermission permission = BranchPermission.findByMask(mask); if (permission == null) { permission = pluginPermissionManager.findPluginsBranchPermissionByMask(mask); } return permission; } /** * Gets {@link org.jtalks.jcommune.model.dto.GroupsPermissions} for provided {@link org.jtalks.common.model.entity.Component}. * * @param component the component to obtain PermissionsMap for * @return {@link org.jtalks.jcommune.model.dto.GroupsPermissions} for {@link org.jtalks.common.model.entity.Component} */ public GroupsPermissions getPermissionsMapFor(Component component) { List<JtalksPermission> generalPermissions = new ArrayList<>(); generalPermissions.addAll(GeneralPermission.getAllAsList()); return getPermissionsMapFor(generalPermissions, component); } /** * Gets for provided list of {@link org.jtalks.common.model.entity.Group}'s. * * @param groups the List {@link org.jtalks.common.model.entity.Group}'s to obtain PermissionsMap for * @return for {@link org.jtalks.common.model.entity.Group} */ public GroupsPermissions getPermissionsMapFor(List<Group> groups) { List<JtalksPermission> profilePermissions = new ArrayList<>(); profilePermissions.addAll(ProfilePermission.getAllAsList()); GroupsPermissions permissions = new GroupsPermissions(profilePermissions); for (Group group : groups) { GroupsPermissions pmGroup = getPermissionsMapFor(profilePermissions, group); for (JtalksPermission permission : pmGroup.getPermissions()) { for (Group groupInsert : pmGroup.getAllowed(permission)) { permissions.addAllowed(permission, groupInsert); } for (Group groupInsert : pmGroup.getRestricted(permission)) { permissions.addRestricted(permission, groupInsert); } } } return permissions; } /** * Gets {@link org.jtalks.jcommune.model.dto.GroupsPermissions} for provided {@link org.jtalks.common.model.entity.Entity}. * * @param permissions the list of permissions to get * @param entity the entity to get for * @return {@link org.jtalks.jcommune.model.dto.GroupsPermissions} for provided {@link org.jtalks.common.model.entity.Entity} */ public GroupsPermissions getPermissionsMapFor(List<JtalksPermission> permissions, Entity entity) { GroupsPermissions groupsPermissions = new GroupsPermissions(permissions); List<GroupAce> groupAces = aclManager.getGroupPermissionsOn(entity); for (JtalksPermission permission : permissions) { for (GroupAce groupAce : groupAces) { if (groupAce.getPermissionMask() == permission.getMask()) { groupsPermissions.add(permission, getGroup(groupAce), groupAce.isGranting()); } } for (AccessControlEntry controlEntry : aclUtil.getAclFor(entity).getEntries()) { if (controlEntry.getPermission().equals(permission) && ((UniversalSid)controlEntry.getSid()).getSidId().equals(UserSid.createAnonymous().getSidId())) { groupsPermissions.add(permission, AnonymousGroup.ANONYMOUS_GROUP, controlEntry.isGranting()); } } } return groupsPermissions; } /** * Gets the list of the all group existing in the System except the group in specified group list. * Note: we have Anonymous group which not stored in database and available only for VIEW_TOPICS permission. * @param excludedGroupsList groups which should be excluded from the result * @return the list of the all group existing in the System except the group in specified group list * @see org.jtalks.jcommune.model.entity.AnonymousGroup */ public List<Group> getAllGroupsWithoutExcluded(List<Group> excludedGroupsList, JtalksPermission permission) { List<Group> allGroups = groupDao.getAll(); allGroups.removeAll(excludedGroupsList); if (permission.equals(BranchPermission.VIEW_TOPICS) && !excludedGroupsList.contains(AnonymousGroup.ANONYMOUS_GROUP)) { allGroups.add(AnonymousGroup.ANONYMOUS_GROUP); } return allGroups; } /** * Gets the list of groups which IDs specified in parameter. If list of IDs contains 0L value result should * contain instance of AnonymousGroup * @param groupIds the list of IDs for which groups should be found * @return the list of found groups or empty list if list of IDs is empty * @see org.jtalks.jcommune.model.entity.AnonymousGroup */ public List<Group> getGroupsByIds(List<Long> groupIds) { if (groupIds.isEmpty()) { return Collections.emptyList(); } else { List<Group> groups = groupDao.getGroupsByIds(groupIds); if (groupIds.contains(0L)) { groups.add(AnonymousGroup.ANONYMOUS_GROUP); } return groups; } } /** * @param groupAce from which if of group should be extracted * @return {@link Group} extracted from {@link GroupAce} */ private Group getGroup(GroupAce groupAce) { return groupDao.get(groupAce.getGroupId()); } /** * Changes the granted permission for group. If group is AnonymousGroup method changes permissions * for Anonymous Sid. * * @param group user group * @param permission permission * @param entity the entity to change permissions to * @param granted permission is granted or restricted */ private void changeGrantsOfGroup(Group group, JtalksPermission permission, Entity entity, boolean granted) { if (group instanceof AnonymousGroup) { changeGrantsOfAnonymousGroup(permission, entity, granted); } else { AclBuilders builders = new AclBuilders(); if (granted) { builders.newBuilder(aclManager).grant(permission).to(group).on(entity).flush(); } else { builders.newBuilder(aclManager).restrict(permission).to(group).on(entity).flush(); } } } /** * Changes permissions for Anonymous Sid. * * @param permission permission * @param entity the entity to change permissions to * @param granted permission is granted or restricted */ private void changeGrantsOfAnonymousGroup(JtalksPermission permission, Entity entity, boolean granted) { List<Permission> jtalksPermissions = new ArrayList<>(); jtalksPermissions.add(permission); List<Sid> sids = new ArrayList<>(); sids.add(UserSid.createAnonymous()); if (granted) { aclManager.grant(sids, jtalksPermissions, entity); } else { aclManager.restrict(sids, jtalksPermissions, entity); } } /** * Deletes the granted permission for group. If group is AnonymousGroup method deletes permissions * for Anonymous Sid. * * @param group user group * @param permission permission * @param entity the entity to change permissions to */ private void deleteGrantsOfGroup(Group group, JtalksPermission permission, Entity entity) { if (group instanceof AnonymousGroup) { deleteGrantsOfAnonymousGroup(permission, entity); } else { AclBuilders builders = new AclBuilders(); builders.newBuilder(aclManager).delete(permission).from(group).on(entity).flush(); } } /** * Deletes permissions for Anonymous Sid. * * @param permission permission * @param entity the entity to change permissions to */ private void deleteGrantsOfAnonymousGroup(JtalksPermission permission, Entity entity) { List<Permission> jtalksPermissions = new ArrayList<>(); jtalksPermissions.add(permission); List<Sid> sids = new ArrayList<>(); sids.add(UserSid.createAnonymous()); aclManager.delete(sids, jtalksPermissions, entity); } }