diff --git a/ccm-core/src/main/java/com/arsdigita/kernel/security/SecurityConfig.java b/ccm-core/src/main/java/com/arsdigita/kernel/security/SecurityConfig.java index 20e79cdb1..dea1471a3 100644 --- a/ccm-core/src/main/java/com/arsdigita/kernel/security/SecurityConfig.java +++ b/ccm-core/src/main/java/com/arsdigita/kernel/security/SecurityConfig.java @@ -26,6 +26,8 @@ import com.arsdigita.util.parameter.SpecificClassParameter; import com.arsdigita.util.parameter.StringArrayParameter; import com.arsdigita.util.parameter.StringParameter; +import org.libreccm.core.authentication.LocalLoginModule; + import java.util.Arrays; import java.util.List; @@ -79,7 +81,8 @@ public class SecurityConfig extends AbstractConfig { private final Parameter m_loginConfig = new StringArrayParameter( "waf.login_config", Parameter.REQUIRED, new String[]{ - "Register:com.arsdigita.kernel.security.LocalLoginModule:requisite",}); + String.format("Register:%s:requisite", + LocalLoginModule.class.getName())}); private final Parameter m_adminEmail = new StringParameter( "waf.admin.contact_email", Parameter.OPTIONAL, null); @@ -101,13 +104,13 @@ public class SecurityConfig extends AbstractConfig { */ private final Parameter m_hashAlgorithm = new StringParameter( "waf.security.hash_algorithm", Parameter.REQUIRED, "SHA-512"); - + /** * Default length of the salt for new passwords. */ private final Parameter m_saltLength = new IntegerParameter( "waf.security.salt_length", Parameter.REQUIRED, 256); - + /** * Constructs an empty SecurityConfig object */ @@ -123,7 +126,7 @@ public class SecurityConfig extends AbstractConfig { register(m_autoRegistrationOn); register(m_userBanOn); register(m_enableQuestion); - + register(m_hashAlgorithm); register(m_saltLength); @@ -185,7 +188,7 @@ public class SecurityConfig extends AbstractConfig { public String getAdminContactEmail() { String email = (String) get(m_adminEmail); - + // Return empty string instead of looking up into the database. If no // email if configured for the admin we consider that as a configuration // issue. @@ -223,7 +226,6 @@ public class SecurityConfig extends AbstractConfig { // } // return s_systemAdministratorEmailAddress; // } - public final boolean isAutoRegistrationOn() { return ((Boolean) get(m_autoRegistrationOn)).booleanValue(); } @@ -231,9 +233,9 @@ public class SecurityConfig extends AbstractConfig { public String getHashAlgorithm() { return (String) get(m_hashAlgorithm); } - + public Integer getSaltLength() { return (Integer) get(m_saltLength); } - + } diff --git a/ccm-core/src/main/java/com/arsdigita/runtime/ConfigError.java b/ccm-core/src/main/java/com/arsdigita/runtime/ConfigError.java new file mode 100644 index 000000000..5f98d6963 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/runtime/ConfigError.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.runtime; + +import com.arsdigita.util.Assert; +import org.apache.log4j.Logger; + +/** + * Subject to change. + * + * An error to indicate invalid configurations. + * + * Usage: throw new ConfigError( "message" ); + * + * @author Justin Ross <jross@redhat.com> + */ +public class ConfigError extends Error { + + private static final long serialVersionUID = 5224480607138738741L; + + /** + * Constructs a new configuration error with the content + * message. + * + * @param message A String describing what's wrong; it cannot + * be null + */ + public ConfigError(final String message) { + super(message); + + Assert.exists(message, String.class); + } + + /** + * Constructs a new configuration error with a default message. + */ + public ConfigError() { + super("Configuration is invalid"); + } + +} diff --git a/ccm-core/src/main/java/org/libreccm/core/authentication/LoginConfig.java b/ccm-core/src/main/java/org/libreccm/core/authentication/LoginConfig.java index 7f03d710e..32f763c4e 100644 --- a/ccm-core/src/main/java/org/libreccm/core/authentication/LoginConfig.java +++ b/ccm-core/src/main/java/org/libreccm/core/authentication/LoginConfig.java @@ -18,197 +18,42 @@ */ package org.libreccm.core.authentication; -import java.util.ArrayList; -import java.util.Arrays; +import com.arsdigita.kernel.security.SecurityConfig; + import java.util.HashMap; -import java.util.List; import java.util.Map; + import javax.security.auth.login.AppConfigurationEntry; import javax.security.auth.login.Configuration; +import javax.security.auth.spi.LoginModule; /** - * Parses a string array/list of strings to create an configuration for JAAS. + * Configuration for JAAS containing all active {@link LoginModule} + * implementations. * - * This class is based on the {@code com.arsdigita.kernel.security.LoginConfig} - * class. The code itself has been heavily refactored using features like - * Generics and other things added to the Java language and the Java Standard - * Library in the last ten years since the original class was written. + * The active modules are stored in the {@link SecurityConfig} as an array + * of strings. The original {@code com.arsdigita.kernel.security.LoginConfig} + * class parsed this string array in its constructor. For LibreCCM 7 the code + * has been split up. The logic for parsing the configuration is now provided + * by the {@link LoginConfigBuilder} class. This allowed us the greatly + * simplify this class. Also now we don't have a constructor which throws + * Exceptions. * * @author Jens Pelzetter */ public class LoginConfig extends Configuration { /** - * Maps application names to {@code AppConfigurationEntry[]}. - * - * ToDo: To create a configuration this class parses strings. Of course the - * parsing may fails. In this case the constructor throws an exception. - * Throwing exceptions from a constructor is considered a bad practise - * because this may leaves a party created object. Instead the better option - * would be a factory method or a builder. + * The configuration entries. */ - private final Map appConfigs = new HashMap<>(); + private final Map appConfigs; private LoginConfig() { - //Nothing. + this.appConfigs = new HashMap<>(); } - - /** - * Creates a new login configuration from a list of string. {@code Request} - * and {@code Register} are mandatory contexts, WAF refuses to start if they - * are not configured. Each login context can span multiple modules. - * - * The input list comprises of strings adhering to the following format: - * - *
-     *    context:moduleName:controlFlag[:option1[:option2[:...]]]
-     * 
- * - *
- * - *
context
- *
String
- * - *
moduleName
- *
String
- * - *
controlFlag
- *
"required"
- *
"requisite"
- *
"sufficient"
- *
"optional"
- * - *
option
- *
"key=value"
- *
- * - *

- * Example:

- * - *
-     *     Request:com.arsdigita.kernel.security.CredentialLoginModule:requisite:debug=true
-     *     Register:com.arsdigita.kernel.security.LocalLoginModule:requisite
-     *     Register:com.arsdigita.kernel.security.UserIDLoginModule:requisite
-     *     Register:com.arsdigita.kernel.security.CredentialLoginModule:optional
-     * 
- * - * @param config The configuration in string format. - * - */ - public LoginConfig(final List config) { - final Map> contextConfigs = new HashMap<>(); - - for (String tuple : config) { - final int pos = tuple.indexOf(':'); - final String context = tuple.substring(0, pos); - final String moduleConfig = tuple.substring(pos + 1); - final List contextConfig = retrieveContextConfig( - context, contextConfigs); - - contextConfig.add(moduleConfig); - } - - for (final Map.Entry> entry : contextConfigs. - entrySet()) { - addAppConfig(entry.getKey(), entry.getValue()); - } - } - - private List retrieveContextConfig( - final String context, final Map> contextConfigs) { - List contextConfig = contextConfigs.get(context); - - if (contextConfig == null) { - contextConfig = new ArrayList<>(); - contextConfigs.put(context, contextConfig); - } - - return contextConfig; - } - - private void addAppConfig(final String name, final List entries) { - final AppConfigurationEntry[] configEntries - = new AppConfigurationEntry[entries.size()]; - for (int i = 0; i < entries.size(); i++) { - final List entry = Arrays.asList(entries.get(i).split(":")); - configEntries[i] = loadAppConfigEntry(entry); - - } - appConfigs.put(name, configEntries); - } - - private AppConfigurationEntry loadAppConfigEntry(final List entry) { - if (entry.size() < 2) { - throw new LoginConfigMalformedException("LoginConfig is malformed."); - } - - final String name = entry.get(0); - final AppConfigurationEntry.LoginModuleControlFlag flag = parseFlag( - entry.get(1)); - final Map options = new HashMap<>(); - - if (entry.size() > 2) { - for (int i = 2; i < entry.size(); i++) { - addOption(entry.get(i), options); - } - } - - return new AppConfigurationEntry(name, flag, options); - } - - private AppConfigurationEntry.LoginModuleControlFlag parseFlag( - final String flagStr) { - switch (flagStr) { - case "REQUISITE": - return AppConfigurationEntry.LoginModuleControlFlag.REQUISITE; - - case "REQUIRED": - return AppConfigurationEntry.LoginModuleControlFlag.REQUIRED; - - case "SUFFICIENT": - return AppConfigurationEntry.LoginModuleControlFlag.SUFFICIENT; - - case "OPTIONAL": - return AppConfigurationEntry.LoginModuleControlFlag.OPTIONAL; - - default: - throw new LoginConfigMalformedException(String.format( - "Unknown flag \"%s\". Valid flags are: REQUISITE, " - + "REQUIRED, SUFFICIENT, OPTIONAL", - flagStr)); - } - } - - private void addOption(final String option, - final Map options) { - final int index = option.indexOf('='); - if (index == -1) { - throw new LoginConfigMalformedException(String.format( - "The option string \"%s\" is malformed.", option)); - } - - final String key = option.substring(0, index); - final String value = option.substring(index + 1); - options.put(key, value); - } - - public static void foo(final LoginConfig config) { - if (config.appConfigs.get("foo") != null) { - config.appConfigs.remove("foo"); - } - } - - /** - * Convenient constructor taking an arrays of strings containing the - * configuration. Internally this constructor converts the array to a list - * and calls {@link #LoginConfig(java.util.List)}. - * - * @param config The configuration in string form. - * - * @see #LoginConfig(java.util.List) - */ - public LoginConfig(final String[] config) { - this(Arrays.asList(config)); + + LoginConfig(final Map appConfigs) { + this.appConfigs = appConfigs; } @Override diff --git a/ccm-core/src/main/java/org/libreccm/core/authentication/LoginConfigBuilder.java b/ccm-core/src/main/java/org/libreccm/core/authentication/LoginConfigBuilder.java new file mode 100644 index 000000000..1dddc4620 --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/core/authentication/LoginConfigBuilder.java @@ -0,0 +1,308 @@ +/* + * Copyright (C) 2015 LibreCCM Foundation. + * + * 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.libreccm.core.authentication; + +import com.arsdigita.kernel.security.SecurityConfig; +import com.arsdigita.runtime.ConfigError; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.security.auth.login.AppConfigurationEntry; +import javax.security.auth.spi.LoginModule; + +/** + * Creates a {@link LoginConfig} instance from an array or an list of strings. + * + * The process of creating a {@link LoginConfig} instance from the string values + * like those stored in the {@link SecurityConfig} is a complex process. + * Originally the constructor of the + * {@code com.arsdigita.kernel.security.LoginConfig} class has done all this + * work. Some parts were outsourced to private methods. + * + * The problem with this approach is that several of this method may throw an + * exception/error. But throwing exceptions from a constructor is considered a + * bad practise. Also the private support methods made the original + * {@code LoginConfig} class quite big and complex. Therefore the code has been + * split up. The construction process is now done by this class which creates + * {@code LoginConfig} from a provided list or array of strings. The strings + * must be in the correct format: + * + *
+ * context:moduleName:controlFlag[:option1[:option2[:...]]]
+ * 
+ * + *
+ *
{@code context}
+ *
String
+ * + *
{@code moduleName}
+ *
Fully qualified class name of a {@link LoginModule}.
+ * + *
{@code controlFlag}
+ *
+ * One of the following flags: + *
    + *
  • {@code required}
  • + *
  • {@code requisite}
  • + *
  • {@code sufficient}
  • + *
  • {@code optional}
  • + *
+ *
+ * + *
option
+ *
Options for the module in the following format: {@code key=value}
+ *
+ * + * Examples: + * + *
+ *     Request:com.arsdigita.kernel.security.CredentialLoginModule:requisite:debug=true
+ *     Register:com.arsdigita.kernel.security.LocalLoginModule:requisite
+ *     Register:com.arsdigita.kernel.security.UserIDLoginModule:requisite
+ *     Register:com.arsdigita.kernel.security.CredentialLoginModule:optional
+ * 
+ * + * The build a {@link LoginConfig} first construct an instance of this class and + * pass the string array containing the configuration. For example: + * + *
+ *      final LoginConfigBuilder loginConfigBuilder =
+ *          new LoginConfigBuilder(SecurityConfig.getInstance().getLoginConfig());
+ * 
+ * + * Then call the {@link #build()} method which does all the work: + * + *
+ *      final LoginConfig loginConfig = loginConfigBuilder.build();
+ * 
+ * + * @author Jens Pelzetter + */ +public class LoginConfigBuilder { + + private final transient String[] config; + + /** + * Creates a new {@code LoginConfigBuilder} for the provided configuration. + * + * @param config The configuration for which an {@link LoginConfig} should + * be created, as array of strings as provided by + * {@link SecurityConfig#getLoginConfig()}. + */ + public LoginConfigBuilder(final String[] config) { + this.config = config; + } + + /** + * Creates a {@link LoginConfig} from {@link #config}. + * + * If one entry of the {@link #config} is malformed a {@link ConfigError} + * will be thrown. + * + * @return A {@link LoginConfig} object. + */ + public LoginConfig build() { + //Temporary storage for the data extracted from the config string array. + final Map> contextConfigs = new HashMap<>(); + + //Parse the tuples in the config string array. + for (final String tuple : config) { + //Find the index of the first ':'. + final int index = tuple.indexOf(':'); + //Extract context and module config parts from the tuple. + final String context = tuple.substring(0, index); + final String moduleConfig = tuple.substring(index + 1); + + //Put them in the list for the context. + final List contextConfig = retrieveContextConfig( + context, contextConfigs); + contextConfig.add(moduleConfig); + + } + + //Create the map of AppConfigurationEntry objects. + final Map appConfigs = new HashMap<>(); + for (final Map.Entry> entry : contextConfigs + .entrySet()) { + //Add the config entry. The helper method called creates the + //AppConfigurationEntry object from the string value. + addAppConfig(appConfigs, entry.getKey(), entry.getValue()); + } + + //Create the LoginConfig object with the Map of AppConfigurationEntries. + return new LoginConfig(appConfigs); + + } + + /** + * Helper method for retrieving a list for specific context from the context + * maps. Used by {@link #build()}. If the map has no entry with the provided + * name a new list is created a put into the list. + * + * @param context The name of the context, used as key in the + * provided map. + * @param contextConfigs The map of context configs. + * + * @return The context configs list for the provided context. + */ + private List retrieveContextConfig( + final String context, final Map> contextConfigs) { + List contextConfig = contextConfigs.get(context); + + if (contextConfig == null) { + contextConfig = new ArrayList<>(); + contextConfigs.put(context, contextConfig); + } + + return contextConfig; + } + + /** + * Helper method for creating an {@link AppConfigurationEntry} object from a + * string. + * + * @param appConfigs The map of {@link AppConfigurationEntry} objects in + * which the created entry will be stored. + * @param name The name of the context for which the + * {@link AppConfigurationEntry} is created. + * @param entries The list of configuration entries to parse. + */ + private void addAppConfig( + final Map appConfigs, + final String name, + final List entries) { + + //Map containing the parsed entries + final AppConfigurationEntry[] configEntries + = new AppConfigurationEntry[entries + .size()]; + + //Parse all entries. We use a "traditional" for loop here because we + //need the position in the array. + for (int i = 0; i < entries.size(); i++) { + //Load the current configuration entry. + configEntries[i] = loadAppConfigEntry(entries.get(i)); + } + + //Put the parsed entires into the map + appConfigs.put(name, configEntries); + } + + /** + * Helper method for parsing a single configuration entry. The + * + * tokens entry + * + * @return + */ + private AppConfigurationEntry loadAppConfigEntry(final String entry) { + //Split the string tokens. The tokens are limited by the ':' character. + final String[] tokens = entry.split(":"); + + //If there less then two tokens the entry is malformed and we throw an + //ConfigError. + if (tokens.length < 2) { + final StringBuilder builder = new StringBuilder(); + for (final String str : tokens) { + builder.append(str); + } + throw new ConfigError(String.format( + "Malformed SecurityConfig entry: %s", builder.toString())); + } + + //Extract the name of the configured module (the first token) + final String name = tokens[0]; + //Extract the flat (second token) + final AppConfigurationEntry.LoginModuleControlFlag flag = parseFlag( + tokens[1]); + + //Extract the provided options if any + final Map options = new HashMap<>(); + if (tokens.length > 2) { + for (int i = 2; i < tokens.length; i++) { + //The the option to the map of options. + addOption(tokens[i], options); + } + } + + //Create an AppConfguration using the extracted data. + return new AppConfigurationEntry(name, flag, options); + } + + /** + * Helper method to convert a string to a + * {@link AppConfigurationEntry.LoginModuleControlFlag}. If the provided + * string is not a valid flag a {@link ConfigError} is thrown. + * + * @param flag The string to convert. + * + * @return {@link AppConfigurationEntry.LoginModuleControlFlag} instance. + */ + private AppConfigurationEntry.LoginModuleControlFlag parseFlag( + final String flag) { + switch (flag.toUpperCase()) { + case "REQUISITE": + return AppConfigurationEntry.LoginModuleControlFlag.REQUISITE; + + case "REQUIRED": + return AppConfigurationEntry.LoginModuleControlFlag.REQUIRED; + + case "SUFFICIENT": + return AppConfigurationEntry.LoginModuleControlFlag.SUFFICIENT; + + case "OPTIONAL": + return AppConfigurationEntry.LoginModuleControlFlag.OPTIONAL; + + default: + throw new ConfigError(String.format( + "Unknown flag \"%s\". Valid flags are: REQUISITE, " + + "REQUIRED, SUFFICIENT, OPTIONAL", + flag)); + } + + } + + /** + * Helper method for extracting the key and value parts from an module + * option string. If the option string is malformed an {@link ConfigError} + * is thrown. + * + * @param option The option string to parse. + * @param options The map of options to which the parsed option we be added. + */ + private void addOption(final String option, + final Map options) { + //Find the index of the '=' character. + final int index = option.indexOf('='); + //If there is no '=' in the string the option string is invalid + if (index == -1) { + throw new ConfigError(String.format( + "The option string \"%s\" is malformed.", option)); + } + + //Extract key and value an put them into the options map. + final String key = option.substring(0, index); + final String value = option.substring(index + 1); + options.put(key, value); + } + +} diff --git a/ccm-core/src/main/java/org/libreccm/core/authentication/LoginConfigMalformedException.java b/ccm-core/src/main/java/org/libreccm/core/authentication/LoginConfigMalformedException.java deleted file mode 100644 index c349284c8..000000000 --- a/ccm-core/src/main/java/org/libreccm/core/authentication/LoginConfigMalformedException.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2015 LibreCCM Foundation. - * - * 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.libreccm.core.authentication; - -/** - * - * @author Jens Pelzetter - */ -public class LoginConfigMalformedException extends RuntimeException { - private static final long serialVersionUID = 8573796343232732323L; - - /** - * Creates a new instance of NewException without detail - * message. - */ - public LoginConfigMalformedException() { - super(); - } - - /** - * Constructs an instance of NewException with the specified - * detail message. - * - * @param msg the detail message. - */ - public LoginConfigMalformedException(final String msg) { - super(msg); - } -} diff --git a/ccm-core/src/main/java/org/libreccm/core/authentication/LoginManager.java b/ccm-core/src/main/java/org/libreccm/core/authentication/LoginManager.java index 6eb98a0ed..7ef2616c6 100644 --- a/ccm-core/src/main/java/org/libreccm/core/authentication/LoginManager.java +++ b/ccm-core/src/main/java/org/libreccm/core/authentication/LoginManager.java @@ -53,13 +53,13 @@ public class LoginManager { /** * Name of the register login context. */ - private static final String REGISTER_LOGIN_CONTEXT = "RegisterLoginContext"; + private static final String REGISTER_LOGIN_CONTEXT = "Register"; @Inject private transient CcmSessionContext sessionContext; - @Inject - private transient UserRepository userRepository; +// @Inject +// private transient UserRepository userRepository; public void login(final String username, final String password) throws LoginException { diff --git a/ccm-core/src/test/java/com/arsdigita/kernel/security/SecurityConfigTest.java b/ccm-core/src/test/java/com/arsdigita/kernel/security/SecurityConfigTest.java index b6767aa75..c37b5e395 100644 --- a/ccm-core/src/test/java/com/arsdigita/kernel/security/SecurityConfigTest.java +++ b/ccm-core/src/test/java/com/arsdigita/kernel/security/SecurityConfigTest.java @@ -103,6 +103,7 @@ public class SecurityConfigTest { .getPackage()) .addPackage(org.libreccm.tests.categories.IntegrationTest.class .getPackage()) + .addPackage(org.libreccm.core.authentication.LocalLoginModule.class.getPackage()) .addAsLibraries(libs) .addAsResource( "configtests/com/arsdigita/kernel/security/SecurityConfigTest/ccm-core.config", diff --git a/ccm-core/src/test/java/org/libreccm/core/UserManagerTest.java b/ccm-core/src/test/java/org/libreccm/core/UserManagerTest.java index 7f6b284b4..e9db1ef5f 100644 --- a/ccm-core/src/test/java/org/libreccm/core/UserManagerTest.java +++ b/ccm-core/src/test/java/org/libreccm/core/UserManagerTest.java @@ -112,7 +112,7 @@ public class UserManagerTest { return ShrinkWrap .create(WebArchive.class, - "LibreCCM-org.libreccm.core.UserRepositoryTest.war") + "LibreCCM-org.libreccm.core.UserManagerTest.war") .addPackage(User.class.getPackage()) .addPackage(org.libreccm.web.Application.class.getPackage()) .addPackage(org.libreccm.categorization.Category.class. diff --git a/ccm-core/src/test/java/org/libreccm/core/authentication/LoginManagerTest.java b/ccm-core/src/test/java/org/libreccm/core/authentication/LoginManagerTest.java new file mode 100644 index 000000000..d557a1251 --- /dev/null +++ b/ccm-core/src/test/java/org/libreccm/core/authentication/LoginManagerTest.java @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2015 LibreCCM Foundation. + * + * 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.libreccm.core.authentication; + +import static org.hamcrest.Matchers.*; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.arquillian.junit.InSequence; +import org.jboss.arquillian.persistence.PersistenceTest; +import org.jboss.arquillian.persistence.UsingDataSet; +import org.jboss.arquillian.transaction.api.annotation.TransactionMode; +import org.jboss.arquillian.transaction.api.annotation.Transactional; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.EmptyAsset; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.jboss.shrinkwrap.resolver.api.maven.Maven; +import org.jboss.shrinkwrap.resolver.api.maven.PomEquippedResolveStage; +import org.junit.After; +import org.junit.AfterClass; + +import static org.junit.Assert.*; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.libreccm.core.CcmObject; +import org.libreccm.core.CcmSessionContext; +import org.libreccm.core.EmailAddress; +import org.libreccm.tests.categories.IntegrationTest; + +import java.io.File; + +import javax.inject.Inject; +import javax.security.auth.login.Configuration; +import javax.security.auth.login.LoginException; + +/** + * + * @author Jens Pelzetter + */ +@Category(IntegrationTest.class) +@RunWith(Arquillian.class) +@PersistenceTest +@Transactional(TransactionMode.COMMIT) +public class LoginManagerTest { + + @Inject + private transient LoginManager loginManager; + + @Inject + private transient CcmSessionContext ccmSessionContext; + + public LoginManagerTest() { + } + + @BeforeClass + public static void setUpClass() { + final String[] config = new String[]{ + String.format("Register:%s:requisite", + LocalLoginModule.class.getName())}; + final LoginConfigBuilder loginConfigBuilder = new LoginConfigBuilder( + config); + Configuration.setConfiguration(loginConfigBuilder.build()); + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + @Deployment + public static WebArchive createDeployment() { + final PomEquippedResolveStage pom = Maven + .resolver() + .loadPomFromFile("pom.xml"); + final PomEquippedResolveStage dependencies = pom + .importCompileAndRuntimeDependencies(); + final File[] libs = dependencies.resolve().withTransitivity().asFile(); + + for (File lib : libs) { + System.err.printf("Adding file '%s' to test archive...%n", + lib.getName()); + } + + return ShrinkWrap + .create(WebArchive.class, + "LibreCCM-org.libreccm.core.authentication.LoginManagerTest.war") + .addPackage(CcmObject.class.getPackage()) + .addPackage(org.libreccm.web.Application.class.getPackage()) + .addPackage(org.libreccm.categorization.Category.class. + getPackage()) + .addPackage(org.libreccm.l10n.LocalizedString.class.getPackage()). + addPackage(org.libreccm.jpa.EntityManagerProducer.class + .getPackage()) + .addPackage(org.libreccm.jpa.utils.MimeTypeConverter.class + .getPackage()) + .addPackage(org.libreccm.testutils.EqualsVerifier.class. + getPackage()) + .addPackage(org.libreccm.core.authentication.LoginManager.class + .getPackage()) + .addPackage(com.arsdigita.kernel.KernelConfig.class.getPackage()) + .addPackage(com.arsdigita.runtime.AbstractConfig.class.getPackage()) + .addPackage(com.arsdigita.util.parameter.AbstractParameter.class + .getPackage()) + .addPackage(com.arsdigita.util.UncheckedWrapperException.class + .getPackage()) + .addPackage(org.libreccm.tests.categories.IntegrationTest.class + .getPackage()) + .addPackage(com.arsdigita.web.CCMApplicationContextListener.class + .getPackage()) + .addAsLibraries(libs) + .addAsResource("test-persistence.xml", + "META-INF/persistence.xml") + .addAsResource( + "configtests/com/arsdigita/kernel/KernelConfigTest/ccm-core.config", + "ccm-core.config") + .addAsWebInfResource( + "configtests/com/arsdigita/kernel/KernelConfigTest/registry.properties", + "conf/registry/registry.properties") + .addAsWebInfResource( + "configtests/com/arsdigita/kernel/KernelConfigTest/kernel.properties", + "conf/registry/ccm-core/kernel.properties") + .addAsResource( + "com/arsdigita/kernel/KernelConfig_parameter.properties", + "com/arsdigita/kernel/KernelConfig_parameter.properties") + .addAsWebInfResource("test-web.xml", "WEB-INF/web.xml") + .addAsWebInfResource(EmptyAsset.INSTANCE, "WEB-INF/beans.xml"); + } + + @InSequence(1) + public void isLoginManagerInjected() { + assertThat(loginManager, is(not(nullValue()))); + } + + @InSequence(2) + public void isCcmSessionContextInjected() { + assertThat(ccmSessionContext, is(not(nullValue()))); + } + + @Test + @UsingDataSet( + "datasets/org/libreccm/core/authentication/LoginManagerTest/data.json") + @InSequence(10) + public void loginValidCredentials() throws LoginException { + loginManager.login("jdoe@example.com", "correct-pw"); + + assertThat(ccmSessionContext.getCurrentParty(), is(not(nullValue()))); + final EmailAddress emailAddress = new EmailAddress(); + emailAddress.setAddress("jdoe@example.org"); + emailAddress.setBouncing(false); + emailAddress.setVerified(true); + assertThat(ccmSessionContext.getCurrentParty().getEmailAddresses(), + contains(equalTo(emailAddress))); + } + + @Test + @UsingDataSet( + "datasets/org/libreccm/core/authentication/LoginManagerTest/data.json") + @InSequence(20) + public void loginWrongCredentials() throws LoginException { + try { + loginManager.login("jdoe@example.com", "wrong-pw"); + } catch (LoginException ex) { + assertThat(ccmSessionContext.getCurrentParty(), is(nullValue())); + } + + fail("No login exception was thrown."); + } + + @Test + @UsingDataSet( + "datasets/org/libreccm/core/authentication/LoginManagerTest/data.json") + @InSequence(30) + public void loginEmptyPassword() { + try { + loginManager.login("jdoe@example.com", ""); + } catch (LoginException ex) { + assertThat(ccmSessionContext.getCurrentParty(), is(nullValue())); + } + + fail("No login exception was thrown."); + } + + @Test + @UsingDataSet( + "datasets/org/libreccm/core/authentication/LoginManagerTest/data.json") + @InSequence(40) + public void loginEmptyUserName() { + try { + loginManager.login("", "correct-pw"); + } catch (LoginException ex) { + assertThat(ccmSessionContext.getCurrentParty(), is(nullValue())); + } + + fail("No login exception was thrown."); + } + + @Test + @UsingDataSet( + "datasets/org/libreccm/core/authentication/LoginManagerTest/data.json") + @InSequence(50) + public void loginNullPassword() { + try { + loginManager.login("jdoe@example.com", null); + } catch (LoginException ex) { + assertThat(ccmSessionContext.getCurrentParty(), is(nullValue())); + } + + fail("No login exception was thrown."); + } + + @Test + @UsingDataSet( + "datasets/org/libreccm/core/authentication/LoginManagerTest/data.json") + @InSequence(60) + public void loginNullUsername() { + try { + loginManager.login(null, "correct-pw"); + } catch (LoginException ex) { + assertThat(ccmSessionContext.getCurrentParty(), is(nullValue())); + } + + fail("No login exception was thrown."); + } + +} diff --git a/ccm-core/src/test/resources/datasets/org/libreccm/core/authentication/LoginManagerTest/data.json b/ccm-core/src/test/resources/datasets/org/libreccm/core/authentication/LoginManagerTest/data.json new file mode 100644 index 000000000..228b31a2a --- /dev/null +++ b/ccm-core/src/test/resources/datasets/org/libreccm/core/authentication/LoginManagerTest/data.json @@ -0,0 +1,30 @@ +{ + "subjects": + [ + { + "subject_id": -10 + } + ], + "subject_email_addresses": + [ + { + "subject_id": -10, + "email_address": "jdoe@example.com", + "bouncing": false, + "verified": true + } + ], + "ccm_users": + [ + { + "banned": false, + "hash_algorithm": "SHA-512", + "family_name": "Doe", + "given_name": "John", + "password": "abc8f796612f5c5b5d0c89cf86ea3b8d1c7d15f9c06851f85708013b0248b2e6d9b315b48f586168fe6cc29296e5a9090a5aab14a85b3ffd0633ca8ccc587a09", + "salt": "fiagaifa", + "screen_name": "jdoe", + "subject_id": -10 + } + ] +} \ No newline at end of file