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- * context:moduleName:controlFlag[:option1[:option2[:...]]] - *- * - *
- * 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
+ * context:moduleName:controlFlag[:option1[:option2[:...]]] + *+ * + *
+ * 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
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