/** * 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 com.google.common.collect.Lists; import org.apache.commons.lang.RandomStringUtils; import org.apache.commons.lang.math.RandomUtils; import org.hibernate.Session; import org.hibernate.SessionFactory; 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.JtalksPermission; 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.model.entity.Branch; import org.jtalks.jcommune.model.entity.ObjectsFactory; import org.jtalks.jcommune.model.entity.PersistedObjectsFactory; 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.ExtendedMutableAcl; import org.jtalks.jcommune.service.security.acl.GroupAce; import org.jtalks.jcommune.service.security.acl.sids.UserGroupSid; import org.jtalks.jcommune.service.security.acl.sids.UserSid; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.acls.domain.*; import org.springframework.security.acls.model.*; import org.springframework.security.core.authority.GrantedAuthorityImpl; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests; import org.springframework.test.context.transaction.TransactionConfiguration; import org.springframework.transaction.annotation.Transactional; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import static com.google.common.collect.Lists.newArrayList; import static org.mockito.Matchers.anyListOf; import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.*; import static org.testng.Assert.*; /** * @author stanislav bashkirtsev * @author Vyacheslav Zhivaev */ @ContextConfiguration(locations = {"classpath:/org/jtalks/jcommune/model/entity/applicationContext-dao.xml"}) @TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true) @Transactional public class PermissionManagerTest extends AbstractTransactionalTestNGSpringContextTests { @Mock private GroupDao groupDao; @Mock private AclManager aclManager; @Mock private AclUtil aclUtil; @Mock private PermissionManager manager; @Mock private PluginPermissionManager pluginPermissionManager; @Autowired private SessionFactory sessionFactory; private Session session; private List<Group> groups; private List<GroupAce> groupAces; private List<JtalksPermission> permissions; @Deprecated public static Group randomGroup(long id) { Group group = new Group(RandomStringUtils.randomAlphanumeric(15), RandomStringUtils.randomAlphanumeric(20)); group.setId(id); return group; } @Deprecated public static Group getGroupWithId(List<Group> groups, long id) { for (Group group : groups) { if (group.getId() == id) { return group; } } return null; } /** * Mockito answer for {@link GroupDao#get(Long)} which return group from defined group list. * * @author Vyacheslav Zhivaev */ class GroupDaoAnswer implements Answer<Group> { private final List<Group> groups; public GroupDaoAnswer(List<Group> groups) { this.groups = groups; } /** * {@inheritDoc} */ @Override public Group answer(InvocationOnMock invocation) throws Throwable { long id = (Long) invocation.getArguments()[0]; return PermissionManagerTest.getGroupWithId(groups, id); } } @BeforeMethod public void beforeMethod() { MockitoAnnotations.initMocks(this); groups = Lists.newArrayList(); permissions = Lists.newArrayList(); groupAces = Lists.newArrayList(); session = sessionFactory.getCurrentSession(); PersistedObjectsFactory.setSession(session); Long targetId = 1L; String targetType = "BRANCH"; ObjectIdentityImpl objectIdentity = new ObjectIdentityImpl(targetType, targetId); when(aclUtil.createIdentityFor(any(Entity.class))).thenReturn(objectIdentity); ExtendedMutableAcl mutableAcl = mock(ExtendedMutableAcl.class); List<AccessControlEntry> controlEntries = new ArrayList<>(); when(mutableAcl.getEntries()).thenReturn(controlEntries); when(aclUtil.getAclFor(objectIdentity)).thenReturn(mutableAcl); manager = new PermissionManager(aclManager, groupDao, aclUtil, pluginPermissionManager); } @Test(dataProvider = "accessChanges") public void testChangeGrants(PermissionChanges changes) throws Exception { Branch branch = ObjectsFactory.getDefaultBranch(); manager.changeGrants(branch, changes); verify(aclManager, times(changes.getRemovedGroups().size())). delete(anyListOf(Sid.class), eq(listFromArray(changes.getPermission())), eq(branch)); verify(aclManager, times(changes.getNewlyAddedGroupsAsArray().length)). grant(anyListOf(Sid.class), eq(listFromArray(changes.getPermission())), eq(branch)); } @Test(dataProvider = "accessChanges") public void testChangeRestriction(PermissionChanges changes) throws Exception { Branch branch = ObjectsFactory.getDefaultBranch(); manager.changeRestrictions(branch, changes); verify(aclManager, times(changes.getRemovedGroups().size())). delete(anyListOf(Sid.class), eq(listFromArray(changes.getPermission())), eq(branch)); verify(aclManager, times(changes.getNewlyAddedGroupsAsArray().length)). restrict(anyListOf(Sid.class), eq(listFromArray(changes.getPermission())), eq(branch)); } @Test public void testGetPermissionsMapForBranch() throws Exception { Branch branch = PersistedObjectsFactory.getDefaultBranch(); givenPermissions(branch, BranchPermission.values()); GroupsPermissions groupsPermissions = manager.getPermissionsMapFor(branch); verify(pluginPermissionManager).getPluginsBranchPermissions(); verify(aclManager).getGroupPermissionsOn(branch); verify(aclUtil, times(BranchPermission.values().length)).getAclFor(branch); assertTrue(groupsPermissions.getPermissions().containsAll(permissions)); for (GroupAce groupAce : groupAces) { List<Group> groups = groupsPermissions.get(groupAce.getPermission(), groupAce.isGranting()); assertNotNull(getGroupWithId(groups, groupAce.getGroupId())); assertTrue(groups.contains(AnonymousGroup.ANONYMOUS_GROUP)); } } @Test public void testChangeGrantsOfAnonymousGroup() throws Exception { Branch branch = ObjectsFactory.getDefaultBranch(); PermissionChanges changes = new PermissionChanges(BranchPermission.CLOSE_TOPICS); List<Group> groupList = new ArrayList<>(); groupList.add(AnonymousGroup.ANONYMOUS_GROUP); changes.addNewlyAddedGroups(groupList); manager.changeGrants(branch, changes); List<Sid> sids = new ArrayList<>(); sids.add(UserSid.createAnonymous()); verify(aclManager, times(changes.getRemovedGroups().size())). delete(eq(sids), eq(listFromArray(changes.getPermission())), eq(branch)); verify(aclManager, times(changes.getNewlyAddedGroupsAsArray().length)). grant(eq(sids), eq(listFromArray(changes.getPermission())), eq(branch)); } @Test public void testRestrictGrantsOfAnonymousGroup() throws Exception { Branch branch = ObjectsFactory.getDefaultBranch(); PermissionChanges changes = new PermissionChanges(BranchPermission.CLOSE_TOPICS); List<Group> groupList = new ArrayList<>(); groupList.add(AnonymousGroup.ANONYMOUS_GROUP); changes.addNewlyAddedGroups(groupList); manager.changeRestrictions(branch, changes); List<Sid> sids = new ArrayList<>(); sids.add(UserSid.createAnonymous()); verify(aclManager, times(changes.getRemovedGroups().size())). delete(eq(sids), eq(listFromArray(changes.getPermission())), eq(branch)); verify(aclManager, times(changes.getNewlyAddedGroupsAsArray().length)). restrict(eq(sids), eq(listFromArray(changes.getPermission())), eq(branch)); } @Test public void testDeleteGrantsOfAnonymousGroup() throws Exception { Branch branch = ObjectsFactory.getDefaultBranch(); PermissionChanges changes = new PermissionChanges(BranchPermission.CLOSE_TOPICS); changes.addRemovedGroups(Lists.newArrayList(AnonymousGroup.ANONYMOUS_GROUP)); manager.changeGrants(branch, changes); List<Sid> sids = Lists.<Sid>newArrayList(UserSid.createAnonymous()); verify(aclManager, times(changes.getRemovedGroups().size())). delete(eq(sids), eq(listFromArray(changes.getPermission())), eq(branch)); verify(aclManager, times(changes.getNewlyAddedGroupsAsArray().length)). grant(eq(sids), eq(listFromArray(changes.getPermission())), eq(branch)); } @Test public void getAllGroupsWithoutExcludedShouldReturnAllGroupsWhenExcludedListIsEmpty() { List<Group> allGroups = Arrays.asList(new Group("1"), new Group("2")); when(groupDao.getAll()).thenReturn(allGroups); List<Group> result = manager.getAllGroupsWithoutExcluded(Collections.EMPTY_LIST, BranchPermission.CLOSE_TOPICS); assertEquals(allGroups, result); } @Test public void getAllGroupsWithoutExcludedShouldReturnAllGroupsWithoutExcluded() { Group excludedGroup = new Group("1"); List<Group> allGroups = new ArrayList<>(Arrays.asList(excludedGroup, new Group("2"))); when(groupDao.getAll()).thenReturn(allGroups); List<Group> result = manager.getAllGroupsWithoutExcluded(Arrays.asList(excludedGroup), BranchPermission.CLOSE_TOPICS); assertEquals(result.size(), 1); assertEquals(result.get(0), allGroups.get(0)); } @Test public void getAllGroupsWithoutExcludedShouldReturnListWithAnonymousGroupWhenItNotExcludedAndRequestedViewTopicPermission() { Group excludedGroup = new Group("1"); List<Group> allGroups = new ArrayList<>(Arrays.asList(excludedGroup, new Group("2"))); when(groupDao.getAll()).thenReturn(allGroups); List<Group> result = manager.getAllGroupsWithoutExcluded(Arrays.asList(excludedGroup), BranchPermission.VIEW_TOPICS); assertTrue(result.contains(AnonymousGroup.ANONYMOUS_GROUP)); } @Test public void getGroupsByIdsShouldReturnAllGroupsWhichIdIsSpecified() { List<Group> groups = Arrays.asList(new Group("group1"), new Group("group2")); List<Long> ids = Arrays.asList(1L, 2L); when(groupDao.getGroupsByIds(ids)).thenReturn(groups); List<Group> result = manager.getGroupsByIds(ids); assertEquals(result, groups); } @Test public void getGroupsByIdsShouldReturnEmptyListWhenListOfIdsIsEmpty() { List<Group> result = manager.getGroupsByIds(Collections.EMPTY_LIST); assertTrue(result.isEmpty()); } @Test public void getGroupsByIdsShouldReturnListWithAnonymousGroupWhenListOfIdsContainsZeroValue() { List<Group> groups = new ArrayList<>(Arrays.asList(new Group("group1"))); List<Long> ids = Arrays.asList(0L, 1L); when(groupDao.getGroupsByIds(ids)).thenReturn(groups); List<Group> result = manager.getGroupsByIds(ids); assertTrue(result.contains(AnonymousGroup.ANONYMOUS_GROUP)); } @Test public void findPermissionByMaskShouldReturnPluginBranchPermissionIfNoCommonBranchPermissionFound() { int mask = -1; JtalksPermission targetPermission = BranchPermission.CLOSE_TOPICS; when(pluginPermissionManager.findPluginsBranchPermissionByMask(mask)).thenReturn(targetPermission); JtalksPermission result = manager.findBranchPermissionByMask(mask); assertEquals(result, targetPermission); } @DataProvider public Object[][] accessChanges() { PermissionChanges accessChanges = new PermissionChanges(BranchPermission.CLOSE_TOPICS); accessChanges.addNewlyAddedGroups(newArrayList(new Group("new1"), new Group("new2"))); accessChanges.addRemovedGroups(newArrayList(new Group("removed1"), new Group("removed2"))); return new Object[][]{{accessChanges}}; } @DataProvider public Object[][] branches() { return new Object[][]{{ObjectsFactory.getDefaultBranch()}}; } private List<Permission> listFromArray(Permission... permissions) { return Lists.newArrayList(permissions); } private void givenPermissions(Entity entity, JtalksPermission... permissions) { givenGroupAces(entity, permissions); Answer<Group> answer = new GroupDaoAnswer(groups); when(groupDao.get(anyLong())).thenAnswer(answer); when(aclManager.getGroupPermissionsOn(eq(entity))).thenReturn(groupAces); } private void givenGroupAces(Entity entity, JtalksPermission... permissions) { long entityId = entity.getId(); AuditLogger auditLogger = new ConsoleAuditLogger(); AclAuthorizationStrategy aclAuthorizationStrategy = new org.springframework.security.acls.domain.AclAuthorizationStrategyImpl( new GrantedAuthorityImpl("some_role") ); ObjectIdentity entityIdentity = new AclUtil(null).createIdentity(entityId, entity.getClass().getSimpleName()); ExtendedMutableAcl mutableAcl = mock(ExtendedMutableAcl.class); List<AccessControlEntry> accessControlEntries = new ArrayList<>(); Acl acl = new AclImpl(entityIdentity, entityId + 1, aclAuthorizationStrategy, auditLogger); long lastGroupId = 1; for (int i = 0; i < permissions.length; i++) { for (int j = 0, count = RandomUtils.nextInt(20) + 10; j < count; j++) { Group group = randomGroup(lastGroupId++); groups.add(group); this.permissions.add(permissions[i]); groupAces.add(buildGroupAce(entity, permissions[i], (i % 2 == 1), acl, new UserGroupSid(group.getId()))); } AccessControlEntry controlEntry = mock(AccessControlEntry.class); when(controlEntry.getPermission()).thenReturn(permissions[i]); when(controlEntry.getSid()).thenReturn(UserSid.createAnonymous()); when(controlEntry.isGranting()).thenReturn((i % 2 == 1)); accessControlEntries.add(controlEntry); } when(mutableAcl.getEntries()).thenReturn(accessControlEntries); when(aclUtil.getAclFor(entity)).thenReturn(mutableAcl); } private GroupAce buildGroupAce(Entity entity, JtalksPermission permission, boolean isGranting, Acl acl, Sid sid) { AccessControlEntry accessControlEntry = new AccessControlEntryImpl(entity.getId(), acl, sid, permission, isGranting, false, false); return new GroupAce(accessControlEntry); } }