/*
* JBoss, Home of Professional Open Source.
* Copyright 2017, 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.test.integration.security.aselytron;
import java.io.File;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
import org.apache.commons.io.FileUtils;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.container.test.api.OperateOnDeployment;
import org.jboss.arquillian.container.test.api.RunAsClient;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.as.arquillian.api.ServerSetup;
import org.jboss.as.arquillian.api.ServerSetupTask;
import org.jboss.as.arquillian.container.ManagementClient;
import org.jboss.as.test.integration.management.util.CLIWrapper;
import org.jboss.as.test.integration.security.common.AbstractSecurityDomainsServerSetupTask;
import org.jboss.as.test.integration.security.common.RolesPrintingServletUtils;
import org.jboss.as.test.integration.security.common.Utils;
import org.jboss.as.test.integration.security.common.config.SecurityDomain;
import org.jboss.as.test.integration.security.common.config.SecurityModule;
import org.jboss.as.test.integration.security.common.servlets.RolePrintingServlet;
import org.jboss.as.test.shared.ServerReload;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* Test case for usage PicketBox security domain as Elytron security realm in Elytron security domain.
*
* @author olukas
*/
@ServerSetup({SecurityDomainAsElytronSecurityRealmTestCase.PropertyFilesSetup.class,
SecurityDomainAsElytronSecurityRealmTestCase.LegacySecurityDomainsSetup.class,
SecurityDomainAsElytronSecurityRealmTestCase.ElytronRelatedSetup.class})
@RunAsClient
@RunWith(Arquillian.class)
public class SecurityDomainAsElytronSecurityRealmTestCase {
private static final String PB_ONE_LOGIN_MODULE = "pb-one-login-module";
private static final String PB_TWO_LOGIN_MODULES = "pb-two-login-modules";
private static final String PB_WITH_MAPPING_MODULE = "pb-with-mapping-module";
private static final String ELY_ONE_LOGIN_MODULE = "ely-one-login-module";
private static final String ELY_TWO_LOGIN_MODULES = "ely-two-login-modules";
private static final String ELY_WITH_MAPPING_MODULE = "ely-with-mapping-module";
private static final String USER1 = "user1";
private static final String USER2 = "user2";
private static final String USER_WITHOUT_ROLE = "userWithoutRole";
private static final String PASSWORD1 = "password1";
private static final String PASSWORD2 = "password2";
private static final String ROLE1 = "role1";
private static final String ROLE2 = "role2";
private static final String ROLE3 = "role3";
private static final String ROLE_FROM_MAPPING_MODULE = "roleFromMappingModule";
private final String[] allPossibleRoles = {ROLE1, ROLE2, ROLE3, ROLE_FROM_MAPPING_MODULE};
@Deployment(name = PB_ONE_LOGIN_MODULE)
public static WebArchive directAccessToLegacyDomainDeployment() {
return createWar(PB_ONE_LOGIN_MODULE);
}
@Deployment(name = ELY_ONE_LOGIN_MODULE)
public static WebArchive securityDomainWithOneLoginModuleDeployment() {
return createWar(ELY_ONE_LOGIN_MODULE);
}
@Deployment(name = ELY_TWO_LOGIN_MODULES)
public static WebArchive securityDomainWithTwoLoginModulesDeployment() {
return createWar(ELY_TWO_LOGIN_MODULES);
}
@Deployment(name = ELY_WITH_MAPPING_MODULE)
public static WebArchive securityDomainWithMappingModuleDeployment() {
return createWar(ELY_WITH_MAPPING_MODULE);
}
private static WebArchive createWar(String securityDomainName) {
final WebArchive war = ShrinkWrap.create(WebArchive.class, securityDomainName + ".war");
war.addClasses(RolePrintingServlet.class);
war.addAsWebInfResource(SecurityDomainAsElytronSecurityRealmTestCase.class.getPackage(),
SecurityDomainAsElytronSecurityRealmTestCase.class.getSimpleName() + "-web.xml", "web.xml");
war.addAsWebInfResource(Utils.getJBossWebXmlAsset(securityDomainName), "jboss-web.xml");
return war;
}
/**
* Test whether access through PicketBox security domain is still possible, even if this domain is exported as Elytron
* security realm.
*/
@Test
@OperateOnDeployment(PB_ONE_LOGIN_MODULE)
public void testDirectAccessToLegacyDomain(@ArquillianResource URL webAppURL) throws Exception {
URL prepareRolePrintingUrl = RolesPrintingServletUtils.prepareRolePrintingUrl(webAppURL, allPossibleRoles);
String responseBody = Utils.makeCallWithBasicAuthn(prepareRolePrintingUrl, USER1, PASSWORD1, SC_OK);
String[] expectedRoles = {ROLE1};
RolesPrintingServletUtils.assertExpectedRoles(responseBody, allPossibleRoles, expectedRoles);
}
/**
* Test whether user with correct credentials has granted access to application secured by Elytron security domain which
* uses PicketBox security domain as Elytron security realm. It also checks whether just a correct roles are assigned to the
* user.
*/
@Test
@OperateOnDeployment(ELY_ONE_LOGIN_MODULE)
public void testAccessAndRoleAssignement(@ArquillianResource URL webAppURL) throws Exception {
URL prepareRolePrintingUrl = RolesPrintingServletUtils.prepareRolePrintingUrl(webAppURL, allPossibleRoles);
String responseBody = Utils.makeCallWithBasicAuthn(prepareRolePrintingUrl, USER1, PASSWORD1, SC_OK);
String[] expectedRoles = {ROLE1};
RolesPrintingServletUtils.assertExpectedRoles(responseBody, allPossibleRoles, expectedRoles);
}
/**
* Test whether forbidden (HTTP status 403) is returned by application secured by Elytron security domain which uses
* PicketBox security domain as Elytron security realm when it is accessed by user with correct credentials but without any
* role.
*/
@Test
@OperateOnDeployment(ELY_ONE_LOGIN_MODULE)
public void testAccessWithoutRole(@ArquillianResource URL webAppURL) throws Exception {
URL prepareRolePrintingUrl = RolesPrintingServletUtils.prepareRolePrintingUrl(webAppURL, allPossibleRoles);
Utils.makeCallWithBasicAuthn(prepareRolePrintingUrl, USER_WITHOUT_ROLE, PASSWORD1, SC_FORBIDDEN);
}
/**
* Test whether user with wrong password has denied access to application secured by Elytron security domain which uses
* PicketBox security domain as Elytron security realm.
*/
@Test
@OperateOnDeployment(ELY_ONE_LOGIN_MODULE)
public void testWrongPassword(@ArquillianResource URL webAppURL) throws Exception {
URL prepareRolePrintingUrl = RolesPrintingServletUtils.prepareRolePrintingUrl(webAppURL, allPossibleRoles);
Utils.makeCallWithBasicAuthn(prepareRolePrintingUrl, USER1, "wrongPassword", SC_UNAUTHORIZED);
}
/**
* Test whether user with empty password has denied access to application secured by Elytron security domain which uses
* PicketBox security domain as Elytron security realm.
*/
@Test
@OperateOnDeployment(ELY_ONE_LOGIN_MODULE)
public void testEmptyPassword(@ArquillianResource URL webAppURL) throws Exception {
URL prepareRolePrintingUrl = RolesPrintingServletUtils.prepareRolePrintingUrl(webAppURL, allPossibleRoles);
Utils.makeCallWithBasicAuthn(prepareRolePrintingUrl, USER1, "", SC_UNAUTHORIZED);
}
/**
* Test whether non existing user has denied access to application secured by Elytron security domain which uses PicketBox
* security domain as Elytron security realm.
*/
@Test
@OperateOnDeployment(ELY_ONE_LOGIN_MODULE)
public void testNonExistingUser(@ArquillianResource URL webAppURL) throws Exception {
URL prepareRolePrintingUrl = RolesPrintingServletUtils.prepareRolePrintingUrl(webAppURL, allPossibleRoles);
Utils.makeCallWithBasicAuthn(prepareRolePrintingUrl, "nonExistingUser", PASSWORD1, SC_UNAUTHORIZED);
}
/**
* Given PicketBox security domain uses two login modules. First of them is sufficient and second is required. This test
* checks whether user with correct credentials has granted access to application secured by Elytron security domain which
* uses given PicketBox security domain as Elytron security realm in case when this user is included in first login module.
* It also checks whether just a correct roles are assigned to the user.
*/
@Test
@OperateOnDeployment(ELY_TWO_LOGIN_MODULES)
public void testAccessForTwoLoginModules_userInFirstmodule(@ArquillianResource URL webAppURL) throws Exception {
URL prepareRolePrintingUrl = RolesPrintingServletUtils.prepareRolePrintingUrl(webAppURL, allPossibleRoles);
String responseBody = Utils.makeCallWithBasicAuthn(prepareRolePrintingUrl, USER2, PASSWORD2, SC_OK);
String[] expectedRoles = {ROLE2, ROLE3};
RolesPrintingServletUtils.assertExpectedRoles(responseBody, allPossibleRoles, expectedRoles);
}
/**
* Given PicketBox security domain uses two login modules. First of them is sufficient and second is required. This test
* checks whether user with correct credentials has granted access to application secured by Elytron security domain which
* uses given PicketBox security domain as Elytron security realm in case when this user is included in second login module.
* It also checks whether just a correct roles are assigned to the user.
*/
@Test
@OperateOnDeployment(ELY_TWO_LOGIN_MODULES)
public void testAccessForTwoLoginModules_userInSecondmodule(@ArquillianResource URL webAppURL) throws Exception {
URL prepareRolePrintingUrl = RolesPrintingServletUtils.prepareRolePrintingUrl(webAppURL, allPossibleRoles);
String responseBody = Utils.makeCallWithBasicAuthn(prepareRolePrintingUrl, USER1, PASSWORD1, SC_OK);
String[] expectedRoles = {ROLE1};
RolesPrintingServletUtils.assertExpectedRoles(responseBody, allPossibleRoles, expectedRoles);
}
/**
* Given PicketBox security domain uses two login modules. First of them is sufficient and second is required. This test
* checks whether user which does not exist in first or second login module has denied access to application secured by
* Elytron security domain which uses PicketBox security domain as Elytron security realm.
*/
@Test
@OperateOnDeployment(ELY_TWO_LOGIN_MODULES)
public void testAccessForTwoLoginModules_nonexistingUser(@ArquillianResource URL webAppURL) throws Exception {
URL prepareRolePrintingUrl = RolesPrintingServletUtils.prepareRolePrintingUrl(webAppURL, allPossibleRoles);
Utils.makeCallWithBasicAuthn(prepareRolePrintingUrl, "nonExistingUser", PASSWORD1, SC_UNAUTHORIZED);
}
/**
* Given PicketBox security domain includes also mapping module which maps some role to the user. This test checks whether
* user has assigned also role from that mapping module.
*/
@Test
@Ignore("https://issues.jboss.org/browse/WFLY-8316")
@OperateOnDeployment(ELY_WITH_MAPPING_MODULE)
public void testAssignRoleFromMappingModule(@ArquillianResource URL webAppURL) throws Exception {
URL prepareRolePrintingUrl = RolesPrintingServletUtils.prepareRolePrintingUrl(webAppURL, allPossibleRoles);
String responseBody = Utils.makeCallWithBasicAuthn(prepareRolePrintingUrl, USER1, PASSWORD1, SC_OK);
String[] expectedRoles = {ROLE1, ROLE_FROM_MAPPING_MODULE};
RolesPrintingServletUtils.assertExpectedRoles(responseBody, allPossibleRoles, expectedRoles);
}
static class PropertyFilesSetup implements ServerSetupTask {
private static final String USERS1_CONTENT = USER1 + "=" + PASSWORD1 + "\n"
+ USER_WITHOUT_ROLE + "=" + PASSWORD1;
private static final String ROLES1_CONTENT = USER1 + "=" + ROLE1;
private static final String USERS2_CONTENT = USER2 + "=" + PASSWORD2;
private static final String ROLES2_CONTENT = USER2 + "=" + ROLE2 + "," + ROLE3;
public static final File FILE_USERS1 = new File("users1.properties");
public static final File FILE_ROLES1 = new File("roles1.properties");
public static final File FILE_USERS2 = new File("users2.properties");
public static final File FILE_ROLES2 = new File("roles2.properties");
/**
* Generates property files.
*/
@Override
public void setup(ManagementClient managementClient, String containerId) throws Exception {
FileUtils.writeStringToFile(FILE_USERS1, USERS1_CONTENT, "UTF-8");
FileUtils.writeStringToFile(FILE_ROLES1, ROLES1_CONTENT, "UTF-8");
FileUtils.writeStringToFile(FILE_USERS2, USERS2_CONTENT, "UTF-8");
FileUtils.writeStringToFile(FILE_ROLES2, ROLES2_CONTENT, "UTF-8");
}
/**
* Removes generated property files.
*/
@Override
public void tearDown(ManagementClient managementClient, String containerId) throws Exception {
FILE_USERS1.delete();
FILE_ROLES1.delete();
FILE_USERS2.delete();
FILE_ROLES2.delete();
}
}
static class LegacySecurityDomainsSetup extends AbstractSecurityDomainsServerSetupTask {
@Override
protected SecurityDomain[] getSecurityDomains() throws Exception {
final Map<String, String> lmOptions1 = new HashMap<>();
lmOptions1.put("usersProperties", PropertyFilesSetup.FILE_USERS1.getAbsolutePath());
lmOptions1.put("rolesProperties", PropertyFilesSetup.FILE_ROLES1.getAbsolutePath());
final SecurityModule.Builder loginModuleBuilder1 = new SecurityModule.Builder()
.name("UsersRoles")
.flag("required")
.options(lmOptions1);
final SecurityDomain sd1 = new SecurityDomain.Builder()
.name(PB_ONE_LOGIN_MODULE)
.loginModules(loginModuleBuilder1.build())
.build();
final Map<String, String> lmOptions2 = new HashMap<>();
lmOptions2.put("usersProperties", PropertyFilesSetup.FILE_USERS2.getAbsolutePath());
lmOptions2.put("rolesProperties", PropertyFilesSetup.FILE_ROLES2.getAbsolutePath());
final SecurityModule.Builder loginModuleBuilder2 = new SecurityModule.Builder()
.name("org.jboss.security.auth.spi.UsersRolesLoginModule")
.flag("sufficient")
.options(lmOptions2);
final SecurityDomain sd2 = new SecurityDomain.Builder()
.name(PB_TWO_LOGIN_MODULES)
.loginModules(loginModuleBuilder2.build(), loginModuleBuilder1.build())
.build();
final Map<String, String> mmOptions = new HashMap<>();
mmOptions.put(USER1, ROLE_FROM_MAPPING_MODULE);
final SecurityModule.Builder mappingModuleBuilder = new SecurityModule.Builder()
.name("SimpleRoles")
.flag("role")
.options(mmOptions);
final SecurityDomain sd3 = new SecurityDomain.Builder()
.name(PB_WITH_MAPPING_MODULE)
.loginModules(loginModuleBuilder1.build())
.mappingModules(mappingModuleBuilder.build())
.build();
return new SecurityDomain[]{sd1, sd2, sd3};
}
}
static class ElytronRelatedSetup implements ServerSetupTask {
private static final String SIMPLE_ROLE_DECODER = "test-simple-role-decoder";
private static final String PREDEFINED_PERMISSION_MAPPER_NAME = "default-permission-mapper";
private static final String PREDEFINED_HTTP_SERVER_MECHANISM_FACTORY = "global";
@Override
public void setup(ManagementClient managementClient, String string) throws Exception {
try (CLIWrapper cli = new CLIWrapper(true)) {
cli.sendLine(String.format(
"/subsystem=elytron/simple-role-decoder=%s:add(attribute=Roles)", SIMPLE_ROLE_DECODER));
prepareAuthenticationWithElytron(cli, ELY_ONE_LOGIN_MODULE, PB_ONE_LOGIN_MODULE);
prepareAuthenticationWithElytron(cli, ELY_TWO_LOGIN_MODULES, PB_TWO_LOGIN_MODULES);
prepareAuthenticationWithElytron(cli, ELY_WITH_MAPPING_MODULE, PB_WITH_MAPPING_MODULE);
}
ServerReload.reloadIfRequired(managementClient.getControllerClient());
}
@Override
public void tearDown(ManagementClient managementClient, String string) throws Exception {
try (CLIWrapper cli = new CLIWrapper(true)) {
removeAuthenticationWithElytron(cli, ELY_WITH_MAPPING_MODULE, PB_WITH_MAPPING_MODULE);
removeAuthenticationWithElytron(cli, ELY_TWO_LOGIN_MODULES, PB_TWO_LOGIN_MODULES);
removeAuthenticationWithElytron(cli, ELY_ONE_LOGIN_MODULE, PB_ONE_LOGIN_MODULE);
cli.sendLine(String.format(
"/subsystem=elytron/simple-role-decoder=%s:remove()", SIMPLE_ROLE_DECODER));
}
ServerReload.reloadIfRequired(managementClient.getControllerClient());
}
private void prepareAuthenticationWithElytron(CLIWrapper cli, String elytronName, String legacySecurityDomain) {
String exportedLegacySecurityDomain = legacySecurityDomain + "-exported";
cli.sendLine(String.format(
"/subsystem=security/elytron-realm=%s:add(legacy-jaas-config=%s)", exportedLegacySecurityDomain,
legacySecurityDomain));
cli.sendLine(String.format(
"/subsystem=elytron/security-domain=%s:add(default-realm=%s,permission-mapper=%s,realms=[{realm=%s,role-decoder=%s}])",
elytronName, exportedLegacySecurityDomain, PREDEFINED_PERMISSION_MAPPER_NAME, exportedLegacySecurityDomain,
SIMPLE_ROLE_DECODER));
cli.sendLine(String.format(
"/subsystem=elytron/http-authentication-factory=%s:add(http-server-mechanism-factory=%s,security-domain=%s,"
+ "mechanism-configurations=[{mechanism-name=BASIC,mechanism-realm-configurations=[{realm-name=\"%s\"}]}])",
elytronName, PREDEFINED_HTTP_SERVER_MECHANISM_FACTORY, elytronName, elytronName));
cli.sendLine(String.format(
"/subsystem=undertow/application-security-domain=%s:add(http-authentication-factory=%s)", elytronName,
elytronName));
}
private void removeAuthenticationWithElytron(CLIWrapper cli, String elytronName, String legacySecurityDomain) {
cli.sendLine(String.format("/subsystem=undertow/application-security-domain=%s:remove()", elytronName));
cli.sendLine(String.format("/subsystem=elytron/http-authentication-factory=%s:remove()", elytronName));
cli.sendLine(String.format("/subsystem=elytron/security-domain=%s:remove()", elytronName));
cli.sendLine(String.format("/subsystem=security/elytron-realm=%s:remove()", legacySecurityDomain + "-exported"));
}
}
}