/* * JBoss, Home of Professional Open Source. * Copyright 2013, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * 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 2.1 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, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.as.core.model.test.access; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ACCESS; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.AUTHORIZATION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.CORE_SERVICE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FAILED; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.INCLUDE_ALL; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.MANAGEMENT; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.NAME; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP_ADDR; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OUTCOME; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.REALM; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.REMOVE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RESULT; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ROLE_MAPPING; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUCCESS; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.TYPE; import static org.jboss.as.domain.management.ModelDescriptionConstants.IS_CALLER_IN_ROLE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.security.PrivilegedAction; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.jboss.as.controller.AccessAuditContext; import org.jboss.as.controller.access.rbac.StandardRole; import org.jboss.as.controller.descriptions.ModelDescriptionConstants; import org.jboss.as.core.model.test.AbstractCoreModelTest; import org.jboss.as.core.model.test.KernelServices; import org.jboss.as.core.model.test.TestModelType; import org.jboss.as.core.security.AccountPrincipal; import org.jboss.as.core.security.GroupPrincipal; import org.jboss.as.core.security.RealmPrincipal; import org.jboss.dmr.ModelNode; import org.junit.Before; import org.junit.Test; import org.wildfly.security.auth.permission.LoginPermission; import org.wildfly.security.auth.realm.SimpleMapBackedSecurityRealm; import org.wildfly.security.auth.realm.SimpleRealmEntry; import org.wildfly.security.auth.server.RealmUnavailableException; import org.wildfly.security.auth.server.SecurityDomain; import org.wildfly.security.auth.server.SecurityIdentity; import org.wildfly.security.auth.server.ServerAuthenticationContext; import org.wildfly.security.authz.MapAttributes; import org.wildfly.security.authz.RoleDecoder; /** * Test case to test the role mapping behaviour (model and runtime mapping). * * @author <a href="mailto:darran.lofthouse@jboss.com">Darran Lofthouse</a> */ public class RoleMappingTestCase extends AbstractCoreModelTest { private static final String TEST_REALM = "TestRealm"; private static final String OTHER_REALM = "OtherRealm"; private static final String OTHER_USER = "OtherUser"; private KernelServices kernelServices; private int uniqueCount = 0; @Before public void setUp() throws Exception { kernelServices = createKernelServicesBuilder(TestModelType.STANDALONE) .setXmlResource("constraints.xml") .validateDescription() .build(); } /** * Test that a user is assigned a role based on their username (not realm specific). * * Also verify that assignment of a group with the same name does not result in role assignment. */ @Test public void testIncludeByUsername() { final String roleName = "Deployer"; final String userName = "UserOne"; addRole(roleName, false); addPrincipal(roleName, MappingType.INCLUDE, PrincipalType.USER, userName, null); assertIsCallerInRole(roleName, null, false); assertIsCallerInRole(roleName, true, userName, TEST_REALM, null); assertIsCallerInRole(roleName, false, OTHER_USER, TEST_REALM, null, userName); removeRole(roleName); } /** * Same as testIncludeByUsername but now verify that the users realm is taken into account. */ @Test public void testIncludeByUsernameAndRealm() { final String roleName = "Deployer"; final String userName = "UserTwo"; addRole(roleName, false); addPrincipal(roleName, MappingType.INCLUDE, PrincipalType.USER, userName, TEST_REALM); assertIsCallerInRole(roleName, null, false); assertIsCallerInRole(roleName, true, userName, TEST_REALM, null); assertIsCallerInRole(roleName, false, userName, OTHER_REALM, null); assertIsCallerInRole(roleName, false, OTHER_USER, TEST_REALM, null, userName); removeRole(roleName); } /** * Test that a user is assigned a role based on their group membership (not realm specific). * * Also verify that a user account with the same name does not result in role assignment. */ @Test public void testIncludeByGroup() { final String roleName = "Deployer"; final String userName = "UserThree"; final String groupName = "GroupThree"; addRole(roleName, false); addPrincipal(roleName, MappingType.INCLUDE, PrincipalType.GROUP, groupName, null); assertIsCallerInRole(roleName, null, false); assertIsCallerInRole(roleName, true, userName, TEST_REALM, null, groupName); assertIsCallerInRole(roleName, true, userName, OTHER_REALM, null, groupName); assertIsCallerInRole(roleName, false, groupName, TEST_REALM, null, userName); removeRole(roleName); } /** * Same as testIncludeByGroup but now include the realm name in the match. */ @Test public void testIncludeByGroupAndRealm() { final String roleName = "Deployer"; final String userName = "UserFour"; final String groupName = "GroupFour"; addRole(roleName, false); addPrincipal(roleName, MappingType.INCLUDE, PrincipalType.GROUP, groupName, TEST_REALM); assertIsCallerInRole(roleName, null, false); assertIsCallerInRole(roleName, true, userName, TEST_REALM, null, groupName); assertIsCallerInRole(roleName, false, userName, OTHER_REALM, null, groupName); assertIsCallerInRole(roleName, false, groupName, TEST_REALM, null, userName); removeRole(roleName); } /** * Test that a user matched to a role by group is not assigned the role if their username is in the exclude list. */ @Test public void testExcludeByUsername() { final String roleName = "Deployer"; final String userName = "UserFive"; final String groupName = "GroupFive"; addRole(roleName, false); addPrincipal(roleName, MappingType.INCLUDE, PrincipalType.GROUP, groupName, null); addPrincipal(roleName, MappingType.EXCLUDE, PrincipalType.USER, userName, null); assertIsCallerInRole(roleName, null, false); assertIsCallerInRole(roleName, true, OTHER_USER, TEST_REALM, null, groupName); assertIsCallerInRole(roleName, false, userName, TEST_REALM, null, groupName); removeRole(roleName); } /** * Same as testExcludeByUsername except the exclusion is realm specific. */ @Test public void testExcludeByUsernameAndRealm() { final String roleName = "Deployer"; final String userName = "UserFive"; final String groupName = "GroupFive"; addRole(roleName, false); addPrincipal(roleName, MappingType.INCLUDE, PrincipalType.GROUP, groupName, null); addPrincipal(roleName, MappingType.EXCLUDE, PrincipalType.USER, userName, TEST_REALM); assertIsCallerInRole(roleName, null, false); assertIsCallerInRole(roleName, true, OTHER_USER, TEST_REALM, null, groupName); assertIsCallerInRole(roleName, false, userName, TEST_REALM, null, groupName); assertIsCallerInRole(roleName, true, userName, OTHER_REALM, null, groupName); removeRole(roleName); } /** * Test that a user assigned a role due to group membership is excluded based on the membership of another group. */ @Test public void testExcludeByGroup() { final String roleName = "Deployer"; final String userName = "UserSix"; final String inGroupName = "GroupSix_In"; final String outGroupName = "GroupSix_Out"; addRole(roleName, false); addPrincipal(roleName, MappingType.INCLUDE, PrincipalType.GROUP, inGroupName, null); addPrincipal(roleName, MappingType.EXCLUDE, PrincipalType.GROUP, outGroupName, null); assertIsCallerInRole(roleName, null, false); assertIsCallerInRole(roleName, true, userName, TEST_REALM, null, inGroupName); assertIsCallerInRole(roleName, false, userName, TEST_REALM, null, inGroupName, outGroupName); removeRole(roleName); } /** * Same as testExcludeByGroup but the exclusion takes the realm into account. */ @Test public void testExcludeByGroupAndRealm() { final String roleName = "Deployer"; final String userName = "UserSeven"; final String inGroupName = "GroupSeven_In"; final String outGroupName = "GroupSeven_Out"; addRole(roleName, false); addPrincipal(roleName, MappingType.INCLUDE, PrincipalType.GROUP, inGroupName, null); addPrincipal(roleName, MappingType.EXCLUDE, PrincipalType.GROUP, outGroupName, TEST_REALM); assertIsCallerInRole(roleName, null, false); assertIsCallerInRole(roleName, true, userName, TEST_REALM, null, inGroupName); assertIsCallerInRole(roleName, false, userName, TEST_REALM, null, inGroupName, outGroupName); assertIsCallerInRole(roleName, true, userName, OTHER_REALM, null, inGroupName, outGroupName); removeRole(roleName); } /** * Test that user assigned the SUPERUSER role can actually request a different role. * * On requesting the different role the user should not be assigned the SUPERUSER role anymore. */ @Test public void testSuperUserAs() { final String roleName = "SuperUser"; final String otherRole = "Deployer"; final String userName = "UserThirteen"; // TODO Elytron The SuperUser mapping was added to constraints.xml to allow the remainder of the tests to run. //addRole(roleName, false); ModelNode addedAddress = addPrincipal(roleName, MappingType.INCLUDE, PrincipalType.USER, userName, null); assertIsCallerInRole(roleName, true, userName, TEST_REALM, null); assertIsCallerInRole(otherRole, true, userName, TEST_REALM, otherRole); assertIsCallerInRole(roleName, false, userName, TEST_REALM, otherRole); removePrincipal(addedAddress); // TODO Elytron The SuperUser mapping was added to constraints.xml to allow the remainder of the tests to run. //removeRole(roleName); } /** * Test that user assigned the Deployer role can NOT request a different role. */ @Test public void testDeployerAs() { final String roleName = "Deployer"; final String otherRole = "MONITOR"; final String userName = "UserFourteen"; addRole(roleName, false); ModelNode addedAddress = addPrincipal(roleName, MappingType.INCLUDE, PrincipalType.USER, userName, null); assertIsCallerInRole(roleName, true, userName, TEST_REALM, null); assertIsCallerInRole(otherRole, false, userName, TEST_REALM, otherRole); assertIsCallerInRole(roleName, true, userName, TEST_REALM, otherRole); removePrincipal(addedAddress); removeRole(roleName); } /** * Test that an authenticated user is assigned a role where include-all = true. */ @Test public void testIncludeAll() { final String roleName = "Deployer"; final String userName = "UserEight"; addRole(roleName, true); // TODO Elytron Hack - Default user 'anonymous' is picked up as being in the role. assertIsCallerInRole(roleName, null, true); assertIsCallerInRole(roleName, true, userName, TEST_REALM, null); removeRole(roleName); } /** * Test that a user matched to a role by include-all is not assigned the role if their username is in the exclude list. */ @Test public void testIncludeAll_ExcludeByUsername() { final String roleName = "Deployer"; final String userName = "UserNine"; final String groupName = "GroupNine"; addRole(roleName, true); addPrincipal(roleName, MappingType.EXCLUDE, PrincipalType.USER, userName, null); // TODO Elytron Hack to also exclude the default user 'anonymous'. addPrincipal(roleName, MappingType.EXCLUDE, PrincipalType.USER, "anonymous", null); assertIsCallerInRole(roleName, null, false); assertIsCallerInRole(roleName, true, OTHER_USER, TEST_REALM, null, groupName); assertIsCallerInRole(roleName, false, userName, TEST_REALM, null, groupName); removeRole(roleName); } /** * Test that a user matched to a role by include-all is not assigned the role if their group is in the exclude list. */ @Test public void testIncludeAll_ExcludeByGroup() { final String roleName = "Deployer"; final String userName = "UserTen"; final String groupName = "GroupTen"; addRole(roleName, true); addPrincipal(roleName, MappingType.EXCLUDE, PrincipalType.GROUP, groupName, null); // TODO Elytron Hack to also exclude the default user 'anonymous'. addPrincipal(roleName, MappingType.EXCLUDE, PrincipalType.USER, "anonymous", null); assertIsCallerInRole(roleName, null, false); assertIsCallerInRole(roleName, true, userName, TEST_REALM, null); assertIsCallerInRole(roleName, false, userName, TEST_REALM, null, groupName); removeRole(roleName); } /* * Duplicate Handling * * Tests to verify that the add operations successfully detect duplicate include/exclude definitions. */ @Test public void testDuplicateUserComplete() { final String roleName = "Deployer"; final String userName = "UserEleven"; addRole(roleName, false); addPrincipal(roleName, MappingType.INCLUDE, PrincipalType.USER, userName, TEST_REALM); addPrincipal(roleName, MappingType.INCLUDE, PrincipalType.USER, userName, TEST_REALM, true); removeRole(roleName); } @Test public void testDuplicateUserRealmLess() { final String roleName = "Deployer"; final String userName = "UserTwelve"; addRole(roleName, false); addPrincipal(roleName, MappingType.INCLUDE, PrincipalType.USER, userName, null); addPrincipal(roleName, MappingType.INCLUDE, PrincipalType.USER, userName, TEST_REALM); addPrincipal(roleName, MappingType.INCLUDE, PrincipalType.USER, userName, null, true); addPrincipal(roleName, MappingType.EXCLUDE, PrincipalType.USER, userName, TEST_REALM); addPrincipal(roleName, MappingType.EXCLUDE, PrincipalType.USER, userName, null); addPrincipal(roleName, MappingType.EXCLUDE, PrincipalType.USER, userName, null, true); removeRole(roleName); } @Test public void testDuplicateGroupComplete() { final String roleName = "Deployer"; final String groupName = "UserThirteen"; addRole(roleName, false); addPrincipal(roleName, MappingType.EXCLUDE, PrincipalType.GROUP, groupName, TEST_REALM); addPrincipal(roleName, MappingType.EXCLUDE, PrincipalType.GROUP, groupName, TEST_REALM, true); removeRole(roleName); } @Test public void testDuplicateGroupRealmLess() { final String roleName = "Deployer"; final String groupName = "UserFourteen"; addRole(roleName, false); addPrincipal(roleName, MappingType.EXCLUDE, PrincipalType.GROUP, groupName, null); addPrincipal(roleName, MappingType.EXCLUDE, PrincipalType.GROUP, groupName, TEST_REALM); addPrincipal(roleName, MappingType.EXCLUDE, PrincipalType.GROUP, groupName, null, true); addPrincipal(roleName, MappingType.INCLUDE, PrincipalType.GROUP, groupName, TEST_REALM); addPrincipal(roleName, MappingType.INCLUDE, PrincipalType.GROUP, groupName, null); addPrincipal(roleName, MappingType.INCLUDE, PrincipalType.GROUP, groupName, null, true); removeRole(roleName); } private void addRole(final String roleName, boolean includeAll) { ModelNode operation = new ModelNode(); operation.get(OP_ADDR).add(CORE_SERVICE, MANAGEMENT).add(ACCESS, AUTHORIZATION).add(ROLE_MAPPING, roleName); operation.get(OP).set(ADD); if (includeAll) { operation.get(INCLUDE_ALL).set(true); } ModelNode response = kernelServices.executeOperation(operation); assertEquals(SUCCESS, response.get(OUTCOME).asString()); } private ModelNode addPrincipal(final String roleName, final MappingType mappingType, final PrincipalType principalType, final String name, final String realm) { return addPrincipal(roleName, mappingType, principalType, name, realm, false); } private ModelNode addPrincipal(final String roleName, final MappingType mappingType, final PrincipalType principalType, final String name, final String realm, boolean expectFailure) { ModelNode operation = new ModelNode(); operation.get(OP_ADDR).add(CORE_SERVICE, MANAGEMENT).add(ACCESS, AUTHORIZATION).add(ROLE_MAPPING, roleName).add(mappingType.toString(), uniqueCount++); operation.get(OP).set(ADD); operation.get(TYPE).set(principalType.toString()); operation.get(NAME).set(name); if (realm != null) { operation.get(REALM).set(realm); } ModelNode response = kernelServices.executeOperation(operation); if (expectFailure) { assertEquals(FAILED, response.get(OUTCOME).asString()); } else { assertEquals(SUCCESS, response.get(OUTCOME).asString()); } return operation.get(OP_ADDR); } private void removePrincipal(final ModelNode address) { ModelNode operation = new ModelNode(); operation.get(OP_ADDR).set(address); operation.get(OP).set(REMOVE); ModelNode response = kernelServices.executeOperation(operation); assertEquals(SUCCESS, response.get(OUTCOME).asString()); } private void removeRole(final String roleName) { ModelNode operation = new ModelNode(); operation.get(OP_ADDR).add(CORE_SERVICE, MANAGEMENT).add(ACCESS, AUTHORIZATION).add(ROLE_MAPPING, roleName); operation.get(OP).set(REMOVE); ModelNode response = kernelServices.executeOperation(operation); assertEquals(SUCCESS, response.get(OUTCOME).asString()); } private void assertIsCallerInRole(final String roleName, final String runAsRole, final boolean expectedOutcome) { ModelNode operation = new ModelNode(); operation.get(OP_ADDR).add(CORE_SERVICE, MANAGEMENT).add(ACCESS, AUTHORIZATION).add(ROLE_MAPPING, roleName); operation.get(OP).set(IS_CALLER_IN_ROLE); if (runAsRole != null) { ModelNode headers = operation.get(ModelDescriptionConstants.OPERATION_HEADERS); headers.get("roles").set(runAsRole); } ModelNode response = kernelServices.executeOperation(operation); assertEquals(SUCCESS, response.get(OUTCOME).asString()); assertEquals(expectedOutcome, response.get(RESULT).asBoolean()); } private void assertIsCallerInRole(final String roleName, final boolean expectedOutcome, final String userName, final String realm, final String runAsRole, final String... groups) { MapAttributes testAttributes = new MapAttributes(); testAttributes.addFirst("realm", realm); testAttributes.addAll("groups", Arrays.asList(groups)); Map<String, SimpleRealmEntry> entries = new HashMap<>(StandardRole.values().length); entries.put(userName, new SimpleRealmEntry(Collections.emptyList(), testAttributes)); SimpleMapBackedSecurityRealm securityRealm = new SimpleMapBackedSecurityRealm(); securityRealm.setPasswordMap(entries); SecurityDomain testDomain = SecurityDomain.builder() .setDefaultRealmName("Default") .addRealm("Default", securityRealm).setRoleDecoder(RoleDecoder.simple("groups")).build() .setPermissionMapper((p,r) -> new LoginPermission()) .build(); SecurityIdentity securityIdentity; try { ServerAuthenticationContext authenticationContext = testDomain.createNewAuthenticationContext(); authenticationContext.setAuthenticationName(userName); assertTrue("Authorized", authenticationContext.authorize()); securityIdentity = authenticationContext.getAuthorizedIdentity(); } catch (RealmUnavailableException e) { // Should not be possible throw new IllegalStateException(e); } AccessAuditContext.doAs(securityIdentity, null, new PrivilegedAction<Void>() { @Override public Void run() { assertIsCallerInRole(roleName, runAsRole, expectedOutcome); return null; } }); } private enum PrincipalType { GROUP, USER; @Override public String toString() { return super.toString().toLowerCase(); } } private enum MappingType { EXCLUDE, INCLUDE; @Override public String toString() { return super.toString().toLowerCase(); } } private static class User implements RealmPrincipal, AccountPrincipal { private final String realm; private final String name; private User(final String name, final String realm) { this.name = name; this.realm = realm; } @Override public String getName() { return name; } @Override public String getRealm() { return realm; } } private static class Group implements RealmPrincipal, GroupPrincipal { private final String name; private final String realm; private Group(final String name, final String realm) { this.name = name; this.realm = realm; } @Override public String getName() { return name; } @Override public String getRealm() { return realm; } } }