/* * JBoss, Home of Professional Open Source. * Copyright 2012, 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.loginmodules; import static org.junit.Assert.assertEquals; import java.io.IOException; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; import java.util.HashMap; import java.util.Map; import org.apache.http.client.ClientProtocolException; import org.h2.tools.Server; 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.categories.CommonCriteria; import org.jboss.as.test.integration.security.common.AbstractDataSourceServerSetupTask; import org.jboss.as.test.integration.security.common.AbstractSecurityDomainsServerSetupTask; import org.jboss.as.test.integration.security.common.Coding; import org.jboss.as.test.integration.security.common.Utils; import org.jboss.as.test.integration.security.common.config.DataSource; 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.SimpleSecuredServlet; import org.jboss.as.test.integration.security.common.servlets.SimpleServlet; import org.jboss.logging.Logger; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.asset.StringAsset; import org.jboss.shrinkwrap.api.spec.WebArchive; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; /** * Tests for {@link org.jboss.security.auth.spi.DatabaseServerLoginModule}. It uses embedded H2 database as a datastore for * users and roles. * * @author Jan Lanik * @author Josef Cacek */ @RunWith(Arquillian.class) @ServerSetup({DatabaseLoginModuleTestCase.DBSetup.class, // DatabaseLoginModuleTestCase.DataSourcesSetup.class, // DatabaseLoginModuleTestCase.SecurityDomainsSetup.class // }) @RunAsClient @Category(CommonCriteria.class) public class DatabaseLoginModuleTestCase { private static Logger LOGGER = Logger.getLogger(DatabaseLoginModuleTestCase.class); private static final String MARCUS = "marcus"; private static final String ANIL = "anil"; private static final String DATASOURCE_NAME = "DBLMTest"; private static final String DEP1 = "DEP1"; //"DatabaseLogin-defaultSetting"; private static final String DEP2 = "DEP2"; //"DatabaseLogin-hashMD5"; private static final String DEP3 = "DEP3"; //"DatabaseLogin-hashMD5-base64"; private static final String DEP4 = "DEP4"; //"DatabaseLogin-hashMD5-hex"; private static final String MD5 = "MD5"; /** * Creates WAR for test login module with default settings. * * @return */ @Deployment(name = DEP1) public static WebArchive appDeployment1() { return createWar(DEP1); } /** * Creates WAR for test login module with MD5 hashing enabled. * * @return */ @Deployment(name = DEP2) public static WebArchive appDeployment2() { return createWar(DEP2); } /** * Creates WAR for test login module with MD5 hashing enabled - Base64 coding used. * * @return */ @Deployment(name = DEP3) public static WebArchive appDeployment3() { return createWar(DEP3); } /** * Creates WAR for test login module with MD5 hashing enabled - HEX coding used. * * @return */ @Deployment(name = DEP4) public static WebArchive appDeployment4() { return createWar(DEP4); } /** * Test default login module settings. */ @OperateOnDeployment(DEP1) @Test public void testDefault(@ArquillianResource URL url) throws Exception { testAccess(url); } /** * Test login module setting with MD5 hashing enabled. */ @OperateOnDeployment(DEP2) @Test public void testHashed(@ArquillianResource URL url) throws Exception { testAccess(url); } /** * Test login module setting with MD5 hashing enabled - Base64 coding used. */ @OperateOnDeployment(DEP3) @Test public void testHashedBase64(@ArquillianResource URL url) throws Exception { testAccess(url); } /** * Test login module setting with MD5 hashing enabled - HEX coding used. */ @OperateOnDeployment(DEP4) @Test public void testHashedHex(@ArquillianResource URL url) throws Exception { testAccess(url); } // Private methods ------------------------------------------------------- /** * Tests access to a protected servlet. * * @param url * @throws MalformedURLException * @throws ClientProtocolException * @throws IOException * @throws URISyntaxException */ private void testAccess(URL url) throws IOException, URISyntaxException { final URL servletUrl = new URL(url.toExternalForm() + SimpleSecuredServlet.SERVLET_PATH.substring(1)); //successful authentication and authorization assertEquals("Response body is not correct.", SimpleSecuredServlet.RESPONSE_BODY, Utils.makeCallWithBasicAuthn(servletUrl, ANIL, ANIL, 200)); //successful authentication and unsuccessful authorization Utils.makeCallWithBasicAuthn(servletUrl, MARCUS, MARCUS, 403); //unsuccessful authentication Utils.makeCallWithBasicAuthn(servletUrl, ANIL, MARCUS, 401); Utils.makeCallWithBasicAuthn(servletUrl, ANIL, Utils.hash(ANIL, MD5, Coding.BASE_64), 401); Utils.makeCallWithBasicAuthn(servletUrl, ANIL, Utils.hash(ANIL, MD5, Coding.HEX), 401); } /** * Creates {@link WebArchive} (WAR) for given deployment name. * * @param deployment * @return */ private static WebArchive createWar(final String deployment) { final WebArchive war = ShrinkWrap.create(WebArchive.class, deployment + ".war"); war.addClasses(SimpleSecuredServlet.class, SimpleServlet.class); war.addAsWebInfResource(DatabaseLoginModuleTestCase.class.getPackage(), "web-basic-authn.xml", "web.xml"); war.addAsWebInfResource(new StringAsset("<jboss-web>" + // "<security-domain>" + deployment + "</security-domain>" + // "</jboss-web>"), "jboss-web.xml"); return war; } // Embedded classes ------------------------------------------------------ /** * A {@link ServerSetupTask} instance which creates security domains for this test case. * * @author Josef Cacek */ static class SecurityDomainsSetup extends AbstractSecurityDomainsServerSetupTask { /** * Returns SecurityDomains configuration for this testcase. * * @see org.jboss.as.test.integration.security.common.AbstractSecurityDomainsServerSetupTask#getSecurityDomains() */ @Override protected SecurityDomain[] getSecurityDomains() { final SecurityModule.Builder loginModuleBuilder = new SecurityModule.Builder().name("Database").options( getLoginModuleOptions(DEP1)); final SecurityDomain sd1 = new SecurityDomain.Builder().name(DEP1).loginModules(loginModuleBuilder.build()).build(); loginModuleBuilder.options(getLoginModuleOptions(DEP2)).putOption("hashAlgorithm", MD5); final SecurityDomain sd2 = new SecurityDomain.Builder().name(DEP2).loginModules(loginModuleBuilder.build()).build(); loginModuleBuilder.options(getLoginModuleOptions(DEP3)).putOption("hashAlgorithm", MD5) .putOption("hashEncoding", "base64"); final SecurityDomain sd3 = new SecurityDomain.Builder().name(DEP3).loginModules(loginModuleBuilder.build()).build(); loginModuleBuilder.options(getLoginModuleOptions(DEP4)).putOption("hashAlgorithm", MD5) .putOption("hashEncoding", "hex"); final SecurityDomain sd4 = new SecurityDomain.Builder().name(DEP4).loginModules(loginModuleBuilder.build()).build(); return new SecurityDomain[]{sd1, sd2, sd3, sd4}; } /** * Generates common login module options. * * @param deployment * @return */ private Map<String, String> getLoginModuleOptions(String deployment) { final Map<String, String> options = new HashMap<String, String>(); options.put("dsJndiName", "java:jboss/datasources/" + DATASOURCE_NAME); options.put("principalsQuery", "select Password from Principals" + deployment + " where PrincipalID=?"); options.put("rolesQuery", "select Role, RoleGroup from Roles where PrincipalID=?"); return options; } } /** * Datasource setup task for H2 DB. */ static class DataSourcesSetup extends AbstractDataSourceServerSetupTask { @Override protected DataSource[] getDataSourceConfigurations(ManagementClient managementClient, String containerId) { return new DataSource[]{new DataSource.Builder() .name(DATASOURCE_NAME) .connectionUrl( "jdbc:h2:tcp://" + Utils.getSecondaryTestAddress(managementClient) + "/mem:" + DATASOURCE_NAME) .driver("h2").username("sa").password("sa").build()}; } } /** * H2 DB configuration setup task. */ static class DBSetup implements ServerSetupTask { private Server server; public void setup(ManagementClient managementClient, String containerId) throws Exception { server = Server.createTcpServer("-tcpAllowOthers").start(); final String dbUrl = "jdbc:h2:mem:" + DATASOURCE_NAME + ";DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE"; LOGGER.trace("Creating database " + dbUrl); final Connection conn = DriverManager.getConnection(dbUrl, "sa", "sa"); executeUpdate(conn, "CREATE TABLE Roles(PrincipalID Varchar(50), Role Varchar(50), RoleGroup Varchar(50))"); executeUpdate(conn, "INSERT INTO Roles VALUES ('anil','" + SimpleSecuredServlet.ALLOWED_ROLE + "','Roles')"); executeUpdate(conn, "INSERT INTO Roles VALUES ('marcus','superuser','Roles')"); createPrincipalsTab(conn, DEP1, null); createPrincipalsTab(conn, DEP2, Coding.BASE_64); createPrincipalsTab(conn, DEP3, Coding.BASE_64); createPrincipalsTab(conn, DEP4, Coding.HEX); conn.close(); } public void tearDown(ManagementClient managementClient, String containerId) throws Exception { server.shutdown(); server = null; } private void createPrincipalsTab(Connection conn, String dep, Coding coding) throws SQLException { executeUpdate(conn, "CREATE TABLE Principals" + dep + "(PrincipalID Varchar(50), Password Varchar(50))"); executeUpdate(conn, "INSERT INTO Principals" + dep + " VALUES ('anil','" + Utils.hashMD5(ANIL, coding) + "')"); executeUpdate(conn, "INSERT INTO Principals" + dep + " VALUES ('marcus','" + Utils.hashMD5(MARCUS, coding) + "')"); } private void executeUpdate(Connection connection, String query) throws SQLException { final Statement statement = connection.createStatement(); final int updateResult = statement.executeUpdate(query); LOGGER.trace("Result: " + updateResult + ". SQL statement: " + query); statement.close(); } } }