/*
* 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.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import org.apache.commons.io.FileUtils;
import org.apache.http.client.ClientProtocolException;
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.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.SecurityDomain;
import org.jboss.as.test.integration.security.common.config.SecurityModule;
import org.jboss.as.test.integration.security.common.config.SecurityModule.Builder;
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 UserRoles login module.
*
* @author Jan Lanik
* @author Josef Cacek
*/
@RunWith(Arquillian.class)
@ServerSetup({ UsersRolesLoginModuleTestCase.PropertyFilesSetup.class, UsersRolesLoginModuleTestCase.SecurityDomainsSetup.class })
@RunAsClient
@Category(CommonCriteria.class)
public class UsersRolesLoginModuleTestCase {
private static Logger LOGGER = Logger.getLogger(UsersRolesLoginModuleTestCase.class);
private static final String DEP1 = "UsersRoles-externalFiles";
private static final String DEP2 = "UsersRoles-MD5";
private static final String DEP3 = "UsersRoles-MD5-hex";
private static final String DEP4 = "UsersRoles-MD5-base64";
private static final String DEP5a = "UsersRoles-hashUserPassword-false";
private static final String DEP5b = "UsersRoles-hashUserPassword-true";
private static final String DEP6a = "UsersRoles-hashOnlyStorePassword";
private static final String DEP6b = "UsersRoles-hashStorePassword";
private static final String DEP7a = "UsersRoles-ignorePasswordCase-true";
private static final String DEP7b = "UsersRoles-ignorePasswordCase-false";
private static final String ANIL = "anil";
private static final String MARCUS = "marcus";
private static final String ANIL_PWD = "anilPwd";
private static final String MARCUS_PWD = "marcusPwd";
private static final String ROLES = ANIL + "=" + SimpleSecuredServlet.ALLOWED_ROLE + "\n" + MARCUS + "=testRole";
private static final String USERS_EXT = ANIL + "=" + MARCUS_PWD + "\n" + MARCUS + "=" + ANIL_PWD;
private static final String ROLES_EXT = MARCUS + "=" + SimpleSecuredServlet.ALLOWED_ROLE + "\n" + ANIL + "=testRole";
/**
* plaintext login with no additional options
*/
@Deployment(name = DEP1)
public static WebArchive appDeployment1() {
return createWar(DEP1, null);
}
/**
* passwords stored as MD5, no additional options
*/
@Deployment(name = DEP2)
public static WebArchive appDeployment2() {
return createWar(DEP2, Coding.BASE_64);
}
/**
* passwords stored as MD5 in HEX encoding, no additional options
*/
@Deployment(name = DEP3)
public static WebArchive appDeployment3() {
return createWar(DEP3, Coding.HEX);
}
/**
* passwords stored as MD5 in BASE64 encoding, no additional options
*/
@Deployment(name = DEP4)
public static WebArchive appDeployment4() {
return createWar(DEP4, Coding.BASE_64);
}
/**
* tests hashUserPassword=false option
*/
@Deployment(name = DEP5a)
public static WebArchive appDeployment5a() {
return createWar(DEP5a, null);
}
/**
* tests hashUserPassword=true option
*/
@Deployment(name = DEP5b)
public static WebArchive appDeployment5b() {
return createWar(DEP5b, Coding.BASE_64);
}
/**
* tests hashUserPassword option
*/
@Deployment(name = DEP6a)
public static WebArchive appDeployment6a() {
return createWar(DEP6a, null);
}
/**
* tests hashUserPassword option
*/
@Deployment(name = DEP6b)
public static WebArchive appDeployment6b() {
return createWar(DEP6b, null);
}
/**
* tests ignorePasswordCase=true option
*/
@Deployment(name = DEP7a)
public static WebArchive appDeployment7a() {
return createWar(DEP7a, null);
}
/**
* tests ignorePasswordCase=false option
*/
@Deployment(name = DEP7b)
public static WebArchive appDeployment7b() {
return createWar(DEP7b, null);
}
/**
* testExternalFiles
*
* @see #USERS_EXT
* @see #ROLES_EXT
* @throws Exception
*/
@OperateOnDeployment(DEP1)
@Test
public void testExternalFiles(@ArquillianResource URL url) throws Exception {
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, MARCUS, ANIL_PWD, 200));
//successful authentication and unsuccessful authorization
Utils.makeCallWithBasicAuthn(servletUrl, ANIL, MARCUS_PWD, 403);
//tests related to case insensitiveness
Utils.makeCallWithBasicAuthn(servletUrl, ANIL, MARCUS_PWD.toUpperCase(Locale.ENGLISH), 401);
Utils.makeCallWithBasicAuthn(servletUrl, MARCUS, ANIL_PWD.toLowerCase(Locale.ENGLISH), 401);
//unsuccessful authentication
Utils.makeCallWithBasicAuthn(servletUrl, MARCUS, MARCUS_PWD, 401);
Utils.makeCallWithBasicAuthn(servletUrl, ANIL, MARCUS, 401);
Utils.makeCallWithBasicAuthn(servletUrl, ANIL_PWD, MARCUS_PWD, 401);
Utils.makeCallWithBasicAuthn(servletUrl, ANIL, Utils.hashMD5(MARCUS_PWD, Coding.BASE_64), 401);
Utils.makeCallWithBasicAuthn(servletUrl, ANIL, Utils.hashMD5(MARCUS_PWD, Coding.HEX), 401);
}
/**
* testMD5Password
*
* @throws Exception
*/
@OperateOnDeployment(DEP2)
@Test
public void testMD5Password(@ArquillianResource URL url) throws Exception {
testAccess(url, false);
}
/**
* testMD5PasswordHex
*
* @throws Exception
*/
@OperateOnDeployment(DEP3)
@Test
public void testMD5PasswordHex(@ArquillianResource URL url) throws Exception {
testAccess(url, false);
}
/**
* testMD5PasswordBase64
*
* @throws Exception
*/
@OperateOnDeployment(DEP4)
@Test
public void testMD5PasswordBase64(@ArquillianResource URL url) throws Exception {
testAccess(url, false);
}
/**
* testHashUserPasswordTrue
*
* @throws Exception
*/
@OperateOnDeployment(DEP5a)
@Test
public void testHashUserPasswordTrue(@ArquillianResource URL url) throws Exception {
testAccess(url, false);
}
/**
* testHashUserPassword
*
* @throws Exception
*/
@OperateOnDeployment(DEP5b)
@Test
public void testHashUserPasswordFalse(@ArquillianResource URL url) throws Exception {
testAccess(url, false);
}
/**
* testHashOnlyStorePassword
*
* @throws Exception
*/
@OperateOnDeployment(DEP6a)
@Test
public void testHashOnlyStorePassword(@ArquillianResource URL url) throws Exception {
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, Utils.hashMD5(ANIL_PWD, Coding.BASE_64), 200));
//successful authentication and unsuccessful authorization
Utils.makeCallWithBasicAuthn(servletUrl, MARCUS, Utils.hashMD5(MARCUS_PWD, Coding.BASE_64), 403);
//unsuccessful authentication
Utils.makeCallWithBasicAuthn(servletUrl, ANIL, ANIL_PWD, 401);
Utils.makeCallWithBasicAuthn(servletUrl, MARCUS, MARCUS_PWD, 401);
}
/**
* testHashStorePassword
*
* @throws Exception
*/
@OperateOnDeployment(DEP6b)
@Test
public void testHashStorePassword(@ArquillianResource URL url) throws Exception {
testAccess(url, false);
}
/**
* testIgnorePasswordCaseTrue
*
* @throws Exception
*/
@OperateOnDeployment(DEP7a)
@Test
public void testIgnorePasswordCaseTrue(@ArquillianResource URL url) throws Exception {
testAccess(url, true);
}
/**
* testIgnorePasswordCaseFalse
*
* @throws Exception
*/
@OperateOnDeployment(DEP7b)
@Test
public void testIgnorePasswordCaseFalse(@ArquillianResource URL url) throws Exception {
testAccess(url, false);
}
// Private methods -------------------------------------------------------
/**
* Tests access to a protected servlet.
*
* @param url
* @param ignoreCase flag which says if the password should be case insensitive
* @throws MalformedURLException
* @throws ClientProtocolException
* @throws IOException
* @throws URISyntaxException
*/
private void testAccess(URL url, boolean ignoreCase) throws MalformedURLException, ClientProtocolException, 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_PWD, 200));
//successful authentication and unsuccessful authorization
Utils.makeCallWithBasicAuthn(servletUrl, MARCUS, MARCUS_PWD, 403);
//tests related to case (in)sensitiveness
if (ignoreCase) {
assertEquals("Response body is not correct.", SimpleSecuredServlet.RESPONSE_BODY,
Utils.makeCallWithBasicAuthn(servletUrl, ANIL, ANIL_PWD.toUpperCase(Locale.ENGLISH), 200));
Utils.makeCallWithBasicAuthn(servletUrl, MARCUS, MARCUS_PWD.toLowerCase(Locale.ENGLISH), 403);
} else {
Utils.makeCallWithBasicAuthn(servletUrl, ANIL, ANIL_PWD.toUpperCase(Locale.ENGLISH), 401);
Utils.makeCallWithBasicAuthn(servletUrl, MARCUS, MARCUS_PWD.toLowerCase(Locale.ENGLISH), 401);
}
//unsuccessful authentication
Utils.makeCallWithBasicAuthn(servletUrl, ANIL, MARCUS_PWD, 401);
Utils.makeCallWithBasicAuthn(servletUrl, ANIL, MARCUS, 401);
Utils.makeCallWithBasicAuthn(servletUrl, MARCUS_PWD, MARCUS, 401);
Utils.makeCallWithBasicAuthn(servletUrl, ANIL, Utils.hashMD5(ANIL, Coding.BASE_64), 401);
Utils.makeCallWithBasicAuthn(servletUrl, ANIL, Utils.hashMD5(ANIL, Coding.HEX), 401);
}
/**
* Creates {@link WebArchive} (WAR) for given deployment name.
*
* @param deployment
* @return
*/
private static WebArchive createWar(final String deployment, Coding coding) {
LOGGER.info("Starting deployment " + deployment);
final String users = ANIL + "=" + Utils.hashMD5(ANIL_PWD, coding) + "\n" + MARCUS + "="
+ Utils.hashMD5(MARCUS_PWD, coding);
final WebArchive war = ShrinkWrap.create(WebArchive.class, deployment + ".war");
war.addClasses(SimpleSecuredServlet.class, SimpleServlet.class);
war.addAsWebInfResource(UsersRolesLoginModuleTestCase.class.getPackage(), "web-basic-authn.xml", "web.xml");
war.addAsResource(new StringAsset(users), "users.properties");
war.addAsResource(new StringAsset(ROLES), "roles.properties");
war.addAsWebInfResource(new StringAsset("<jboss-web>" + //
"<security-domain>" + deployment + "</security-domain>" + //
"</jboss-web>"), "jboss-web.xml");
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(war.toString(true));
}
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 Map<String, String> lmOptions = new HashMap<String, String>();
final Builder loginModuleBuilder = new SecurityModule.Builder().name("UsersRoles").options(lmOptions);
lmOptions.put("usersProperties", PropertyFilesSetup.FILE_USERS.getAbsolutePath());
lmOptions.put("rolesProperties", PropertyFilesSetup.FILE_ROLES.getAbsolutePath());
final SecurityDomain sd1 = new SecurityDomain.Builder().name(DEP1).loginModules(loginModuleBuilder.build()).build();
lmOptions.remove("usersProperties");
lmOptions.remove("rolesProperties");
lmOptions.put("hashAlgorithm", "MD5");
final SecurityDomain sd2 = new SecurityDomain.Builder().name(DEP2).loginModules(loginModuleBuilder.build()).build();
lmOptions.put("hashEncoding", "hex");
final SecurityDomain sd3 = new SecurityDomain.Builder().name(DEP3).loginModules(loginModuleBuilder.build()).build();
lmOptions.put("hashEncoding", "base64");
final SecurityDomain sd4 = new SecurityDomain.Builder().name(DEP4).loginModules(loginModuleBuilder.build()).build();
lmOptions.remove("hashEncoding");
lmOptions.put("hashUserPassword", "false");
final SecurityDomain sd5a = new SecurityDomain.Builder().name(DEP5a).loginModules(loginModuleBuilder.build())
.build();
lmOptions.put("hashUserPassword", "true");
final SecurityDomain sd5b = new SecurityDomain.Builder().name(DEP5b).loginModules(loginModuleBuilder.build())
.build();
lmOptions.put("hashUserPassword", "false");
lmOptions.put("hashStorePassword", "true");
final SecurityDomain sd6a = new SecurityDomain.Builder().name(DEP6a).loginModules(loginModuleBuilder.build())
.build();
lmOptions.remove("hashUserPassword");
final SecurityDomain sd6b = new SecurityDomain.Builder().name(DEP6b).loginModules(loginModuleBuilder.build())
.build();
lmOptions.remove("hashStorePassword");
lmOptions.remove("hashAlgorithm");
lmOptions.put("ignorePasswordCase", "true");
final SecurityDomain sd7a = new SecurityDomain.Builder().name(DEP7a).loginModules(loginModuleBuilder.build())
.build();
lmOptions.put("ignorePasswordCase", "false");
final SecurityDomain sd7b = new SecurityDomain.Builder().name(DEP7b).loginModules(loginModuleBuilder.build())
.build();
return new SecurityDomain[] { sd1, sd2, sd3, sd4, sd5a, sd5b, sd6a, sd6b, sd7a, sd7b };
}
}
/**
* A {@link ServerSetupTask} instance which creates property files with users and roles.
*
* @author Josef Cacek
*/
static class PropertyFilesSetup implements ServerSetupTask {
public static final File FILE_USERS = new File("test-users.properties");
public static final File FILE_ROLES = new File("test-roles.properties");
/**
* Generates property files.
*/
public void setup(ManagementClient managementClient, String containerId) throws Exception {
FileUtils.writeStringToFile(FILE_USERS, USERS_EXT, "ISO-8859-1");
FileUtils.writeStringToFile(FILE_ROLES, ROLES_EXT, "ISO-8859-1");
}
/**
* Removes generated property files.
*/
public void tearDown(ManagementClient managementClient, String containerId) throws Exception {
FILE_USERS.delete();
FILE_ROLES.delete();
}
}
}