You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ace.apache.org by ja...@apache.org on 2012/04/12 10:14:21 UTC

svn commit: r1325151 [2/2] - in /ace/trunk: ./ ace-authentication-api/ ace-authentication-api/src/ ace-authentication-api/src/main/ ace-authentication-api/src/main/java/ ace-authentication-api/src/main/java/org/ ace-authentication-api/src/main/java/org...

Added: ace/trunk/ace-authenticationprocessor-clientcert/src/test/java/org/apache/ace/authenticationprocessor/clientcert/ClientCertAuthenticationProcessorTest.java
URL: http://svn.apache.org/viewvc/ace/trunk/ace-authenticationprocessor-clientcert/src/test/java/org/apache/ace/authenticationprocessor/clientcert/ClientCertAuthenticationProcessorTest.java?rev=1325151&view=auto
==============================================================================
--- ace/trunk/ace-authenticationprocessor-clientcert/src/test/java/org/apache/ace/authenticationprocessor/clientcert/ClientCertAuthenticationProcessorTest.java (added)
+++ ace/trunk/ace-authenticationprocessor-clientcert/src/test/java/org/apache/ace/authenticationprocessor/clientcert/ClientCertAuthenticationProcessorTest.java Thu Apr 12 08:14:17 2012
@@ -0,0 +1,415 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ace.authenticationprocessor.clientcert;
+
+import static org.apache.ace.authenticationprocessor.clientcert.ClientCertAuthenticationProcessor.ATTRIBUTE_CIPHER_SUITE;
+import static org.apache.ace.authenticationprocessor.clientcert.ClientCertAuthenticationProcessor.ATTRIBUTE_X509_CERTIFICATE;
+import static org.apache.ace.authenticationprocessor.clientcert.ClientCertAuthenticationProcessor.PROPERTY_KEY_PUBLICKEY;
+import static org.apache.ace.authenticationprocessor.clientcert.ClientCertAuthenticationProcessor.PROPERTY_KEY_USERNAME;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.security.KeyPair;
+import java.security.PublicKey;
+import java.security.cert.X509Certificate;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Properties;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.log.LogService;
+import org.osgi.service.useradmin.User;
+import org.osgi.service.useradmin.UserAdmin;
+
+/**
+ * Test cases for {@link ClientCertAuthenticationProcessor}.
+ */
+public class ClientCertAuthenticationProcessorTest {
+
+    private static MemoryKeyStore m_keystore;
+
+    private LogService m_log;
+    private UserAdmin m_userAdmin;
+    private HttpServletRequest m_servletRequest;
+
+    /**
+     * @return the day after tomorrow, never <code>null</code>.
+     */
+    private static Date dayAfterTomorrow() {
+        Calendar cal = getToday();
+        cal.add(Calendar.DAY_OF_MONTH, +2);
+        return cal.getTime();
+    }
+
+    /**
+     * @return the day before yesterday, never <code>null</code>.
+     */
+    private static Date dayBeforeYesterday() {
+        Calendar cal = getToday();
+        cal.add(Calendar.DAY_OF_MONTH, -2);
+        return cal.getTime();
+    }
+
+    /**
+     * @return today as date, without time component, never <code>null</code>.
+     */
+    private static Calendar getToday() {
+        Calendar cal = Calendar.getInstance();
+        cal.set(Calendar.HOUR_OF_DAY, 12);
+        cal.set(Calendar.MINUTE, 0);
+        cal.set(Calendar.SECOND, 0);
+        cal.set(Calendar.MILLISECOND, 0);
+        return cal;
+    }
+
+    /**
+     * @return the date of tomorrow, never <code>null</code>.
+     */
+    private static Date tomorrow() {
+        Calendar cal = getToday();
+        cal.add(Calendar.DAY_OF_MONTH, +1);
+        return cal.getTime();
+    }
+
+    /**
+     * @return the date of yesterday, never <code>null</code>.
+     */
+    private static Date yesterday() {
+        Calendar cal = getToday();
+        cal.add(Calendar.DAY_OF_MONTH, -1);
+        return cal.getTime();
+    }
+
+    /**
+     * Creates an in-memory keystore for this test case.
+     */
+    @BeforeClass
+    public static void init() {
+        m_keystore = new MemoryKeyStore("cn=testCA", dayBeforeYesterday(), dayAfterTomorrow());
+    }
+
+    /**
+     * Set up for each individual test.
+     */
+    @Before
+    public void setUp() {
+        m_log = mock(LogService.class);
+
+        m_userAdmin = mock(UserAdmin.class);
+        m_servletRequest = mock(HttpServletRequest.class);
+
+        when(m_servletRequest.getAuthType()).thenReturn(HttpServletRequest.CLIENT_CERT_AUTH);
+        when(m_servletRequest.getAttribute(ATTRIBUTE_CIPHER_SUITE)).thenReturn("bogus-cipher-suite");
+    }
+
+    /**
+     * Tests that a null certificate chain will yield null.
+     */
+    @Test
+    public void testAuthenticateNoCertificateChainYieldsNull() {
+        User result = createAuthorizationProcessor().authenticate(m_userAdmin, m_servletRequest);
+        assertNull(result);
+    }
+
+    /**
+     * Tests that an empty certificate chain will yield null.
+     */
+    @Test
+    public void testAuthenticateEmptyCertificateChainYieldsNull() {
+        when(m_servletRequest.getAttribute(ATTRIBUTE_X509_CERTIFICATE)).thenReturn(new X509Certificate[0]);
+
+        User result = createAuthorizationProcessor().authenticate(m_userAdmin, m_servletRequest);
+        assertNull(result);
+    }
+
+    /**
+     * Tests that authenticating a known user with an invalid (expired) certificate will yield null.
+     */
+    @Test
+    public void testAuthenticateKnownUserWithExpiredCertificateYieldsNull() {
+        X509Certificate[] certificateChain = createExpiredCertificateChain("bob");
+        PublicKey publickey = certificateChain[0].getPublicKey();
+
+        when(m_servletRequest.getAttribute(ATTRIBUTE_X509_CERTIFICATE)).thenReturn(certificateChain);
+
+        User user = mock(User.class);
+        when(user.getName()).thenReturn("bob");
+        when(user.hasCredential(eq("publickey"), eq(publickey.getEncoded()))).thenReturn(Boolean.TRUE);
+
+        when(m_userAdmin.getUser(eq("username"), eq("bob"))).thenReturn(user);
+
+        User result = createAuthorizationProcessor().authenticate(m_userAdmin, m_servletRequest);
+        assertNull(result);
+    }
+
+    /**
+     * Tests that authenticating a known user with an invalid (not valid) certificate will yield null.
+     */
+    @Test
+    public void testAuthenticateKnownUserWithNotValidCertificateYieldsNull() {
+        X509Certificate[] certificateChain = createExpiredCertificateChain("bob");
+        PublicKey publickey = certificateChain[0].getPublicKey();
+
+        when(m_servletRequest.getAttribute(ATTRIBUTE_X509_CERTIFICATE)).thenReturn(
+            createNotValidCertificateChain("bob"));
+
+        User user = mock(User.class);
+        when(user.getName()).thenReturn("bob");
+        when(user.hasCredential(eq("publickey"), eq(publickey.getEncoded()))).thenReturn(Boolean.TRUE);
+
+        when(m_userAdmin.getUser(eq("username"), eq("bob"))).thenReturn(user);
+
+        User result = createAuthorizationProcessor().authenticate(m_userAdmin, m_servletRequest);
+        assertNull(result);
+    }
+
+    /**
+     * Tests that authenticating a known user with a valid certificate will not yield null.
+     */
+    @Test
+    public void testAuthenticateKnownUserYieldsValidResult() {
+        X509Certificate[] certChain = createValidCertificateChain("bob");
+        PublicKey publicKey = certChain[0].getPublicKey();
+
+        when(m_servletRequest.getAttribute(ATTRIBUTE_X509_CERTIFICATE)).thenReturn(certChain);
+
+        User user = mock(User.class);
+        when(user.getName()).thenReturn("bob");
+        when(user.hasCredential(eq("publickey"), eq(publicKey.getEncoded()))).thenReturn(Boolean.TRUE);
+
+        when(m_userAdmin.getUser(eq("username"), eq("bob"))).thenReturn(user);
+
+        User result = createAuthorizationProcessor().authenticate(m_userAdmin, m_servletRequest);
+        assertNotNull(result);
+
+        assertEquals("bob", user.getName());
+    }
+
+    /**
+     * Tests that a missing cipher suite header will the authenticate method to yield null.
+     */
+    @Test
+    public void testAuthenticateMissingCipherSuiteHeaderYieldsNull() {
+        when(m_servletRequest.getAttribute(ATTRIBUTE_CIPHER_SUITE)).thenReturn(null);
+        when(m_servletRequest.getAttribute(ATTRIBUTE_X509_CERTIFICATE)).thenReturn(createValidCertificateChain("bob"));
+
+        User result = createAuthorizationProcessor().authenticate(m_userAdmin, m_servletRequest);
+        assertNull(result);
+    }
+
+    /**
+     * Tests that a class cast exception is thrown for invalid context when calling authenticate.
+     */
+    @Test(expected = ClassCastException.class)
+    public void testAuthenticateThrowsClassCastForInvalidContext() {
+        createAuthorizationProcessor().authenticate(m_userAdmin, new Object());
+    }
+
+    /**
+     * Tests that an unknown user will yield null.
+     */
+    @Test
+    public void testAuthenticateUnknownUserYieldsNull() {
+        when(m_servletRequest.getAttribute(ATTRIBUTE_X509_CERTIFICATE)).thenReturn(createValidCertificateChain("bob"));
+
+        User result = createAuthorizationProcessor().authenticate(m_userAdmin, m_servletRequest);
+        assertNull(result);
+    }
+
+    /**
+     * Tests that canHandle yields false for any object other than {@link HttpServletRequest}.
+     */
+    @Test
+    public void testCanHandleDoesAcceptServletRequest() {
+        assertTrue(createAuthorizationProcessor().canHandle(mock(HttpServletRequest.class)));
+    }
+
+    /**
+     * Tests that canHandle throws an {@link IllegalArgumentException} for an empty context.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testCanHandleDoesNotAcceptEmptyArray() {
+        createAuthorizationProcessor().canHandle(new Object[0]);
+    }
+
+    /**
+     * Tests that canHandle throws an {@link IllegalArgumentException} for a null context.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testCanHandleDoesNotAcceptNull() {
+        createAuthorizationProcessor().canHandle((Object[]) null);
+    }
+
+    /**
+     * Tests that canHandle yields false for any object other than {@link HttpServletRequest}.
+     */
+    @Test
+    public void testCanHandleDoesNotAcceptUnhandledContext() {
+        assertFalse(createAuthorizationProcessor().canHandle(new Object()));
+    }
+
+    /**
+     * Tests that updated does not throw an exception for a correct configuration.
+     */
+    @Test
+    public void testUpdatedDoesAcceptCorrectProperties() throws ConfigurationException {
+        final String keyUsername = "foo";
+        final String keyPublicKey = "bar";
+
+        Properties props = new Properties();
+        props.put(PROPERTY_KEY_USERNAME, keyUsername);
+        props.put(PROPERTY_KEY_PUBLICKEY, keyPublicKey);
+
+        ClientCertAuthenticationProcessor processor = createAuthorizationProcessor();
+
+        processor.updated(props);
+
+        X509Certificate[] certificateChain = createValidCertificateChain("bob");
+        PublicKey publickey = certificateChain[0].getPublicKey();
+
+        // Test whether we can use the new properties...
+        when(m_servletRequest.getAttribute(ATTRIBUTE_X509_CERTIFICATE)).thenReturn(certificateChain);
+
+        User user = mock(User.class);
+        when(user.getName()).thenReturn("bob");
+        when(user.hasCredential(eq(keyPublicKey), eq(publickey.getEncoded()))).thenReturn(Boolean.TRUE);
+
+        when(m_userAdmin.getUser(eq(keyUsername), eq("bob"))).thenReturn(user);
+
+        User result = processor.authenticate(m_userAdmin, m_servletRequest);
+        assertNotNull(result);
+
+        assertEquals("bob", user.getName());
+    }
+
+    /**
+     * Tests that updated throws an exception for missing "key.password" property.
+     */
+    @Test(expected = ConfigurationException.class)
+    public void testUpdatedDoesNotAcceptEmptyKeyPassword() throws ConfigurationException {
+        Properties props = new Properties();
+        props.put(PROPERTY_KEY_USERNAME, "foo");
+        props.put(PROPERTY_KEY_PUBLICKEY, "");
+
+        createAuthorizationProcessor().updated(props);
+    }
+
+    /**
+     * Tests that updated throws an exception for missing "key.username" property.
+     */
+    @Test(expected = ConfigurationException.class)
+    public void testUpdatedDoesNotAcceptEmptyKeyUsername() throws ConfigurationException {
+        Properties props = new Properties();
+        props.put(PROPERTY_KEY_USERNAME, "");
+        props.put(PROPERTY_KEY_PUBLICKEY, "foo");
+
+        createAuthorizationProcessor().updated(props);
+    }
+
+    /**
+     * Tests that updated throws an exception for missing "key.password" property.
+     */
+    @Test(expected = ConfigurationException.class)
+    public void testUpdatedDoesNotAcceptMissingKeyPassword() throws ConfigurationException {
+        Properties props = new Properties();
+        props.put(PROPERTY_KEY_USERNAME, "foo");
+
+        createAuthorizationProcessor().updated(props);
+    }
+
+    /**
+     * Tests that updated throws an exception for missing "key.username" property.
+     */
+    @Test(expected = ConfigurationException.class)
+    public void testUpdatedDoesNotAcceptMissingKeyUsername() throws ConfigurationException {
+        Properties props = new Properties();
+        props.put(PROPERTY_KEY_PUBLICKEY, "foo");
+
+        createAuthorizationProcessor().updated(props);
+    }
+
+    /**
+     * Creates a new {@link ClientCertAuthenticationProcessor} instance.
+     * 
+     * @return a new authentication processor instance, never <code>null</code>.
+     */
+    private ClientCertAuthenticationProcessor createAuthorizationProcessor() {
+        return new ClientCertAuthenticationProcessor(m_log);
+    }
+
+    /**
+     * Creates a new certificate.
+     * 
+     * @param name the (common) name of the certificate;
+     * @param notBefore the date after which the certificate is valid;
+     * @param notAfter the date until the certificate is valid.
+     * @return a new {@link X509Certificate}, never <code>null</code>.
+     */
+    private X509Certificate createCertificate(String name, final Date notBefore, final Date notAfter) {
+        KeyPair keypair = m_keystore.generateKeyPair();
+        return m_keystore.createCertificate("alias", "cn=" + name, notBefore, notAfter, keypair.getPublic());
+    }
+
+    /**
+     * Creates a new (valid) certificate valid from yesterday until tomorrow.
+     * 
+     * @param name the (common) name of the certificate;
+     * @return a new {@link X509Certificate}, never <code>null</code>.
+     */
+    private X509Certificate[] createValidCertificateChain(String name) {
+        X509Certificate[] result = new X509Certificate[1];
+        result[0] = createCertificate(name, yesterday(), tomorrow());
+        return result;
+    }
+
+    /**
+     * Creates a new (expired) certificate valid from two days ago until yesterday.
+     * 
+     * @param name the (common) name of the certificate;
+     * @return a new {@link X509Certificate}, never <code>null</code>.
+     */
+    private X509Certificate[] createExpiredCertificateChain(String name) {
+        X509Certificate[] result = new X509Certificate[1];
+        result[0] = createCertificate(name, dayBeforeYesterday(), yesterday());
+        return result;
+    }
+
+    /**
+     * Creates a new (not yet valid) certificate valid from tomorrow until the day after tomorrow.
+     * 
+     * @param name the (common) name of the certificate;
+     * @return a new {@link X509Certificate}, never <code>null</code>.
+     */
+    private X509Certificate[] createNotValidCertificateChain(String name) {
+        X509Certificate[] result = new X509Certificate[1];
+        result[0] = createCertificate(name, tomorrow(), dayAfterTomorrow());
+        return result;
+    }
+}

Propchange: ace/trunk/ace-authenticationprocessor-clientcert/src/test/java/org/apache/ace/authenticationprocessor/clientcert/ClientCertAuthenticationProcessorTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: ace/trunk/ace-authenticationprocessor-clientcert/src/test/java/org/apache/ace/authenticationprocessor/clientcert/MemoryKeyStore.java
URL: http://svn.apache.org/viewvc/ace/trunk/ace-authenticationprocessor-clientcert/src/test/java/org/apache/ace/authenticationprocessor/clientcert/MemoryKeyStore.java?rev=1325151&view=auto
==============================================================================
--- ace/trunk/ace-authenticationprocessor-clientcert/src/test/java/org/apache/ace/authenticationprocessor/clientcert/MemoryKeyStore.java (added)
+++ ace/trunk/ace-authenticationprocessor-clientcert/src/test/java/org/apache/ace/authenticationprocessor/clientcert/MemoryKeyStore.java Thu Apr 12 08:14:17 2012
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.ace.authenticationprocessor.clientcert;
+
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PublicKey;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.bouncycastle.x509.X509V1CertificateGenerator;
+
+/**
+ * Provides a memory-only certificate keystore.
+ */
+final class MemoryKeyStore {
+    private static final String SIGNATURE_ALGORITHM = "SHA1withRSA"; // MD5withRSA
+
+    private final X509V1CertificateGenerator m_certGen = new X509V1CertificateGenerator();
+    private final KeyPair m_caKey;
+    private final X509Certificate m_rootCert;
+    private int m_serial = 0;
+
+    private final KeyPairGenerator m_generator;
+
+    /**
+     * Creates a new {@link MemoryKeyStore} instance.
+     */
+    public MemoryKeyStore(String name, Date notBefore, Date notAfter) {
+        try {
+            m_generator = KeyPairGenerator.getInstance("RSA");
+            m_generator.initialize(1024);
+
+            m_caKey = generateKeyPair();
+
+            m_rootCert = generateRootCertificate(name, notBefore, notAfter);
+        }
+        catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Generates a new 512-bit keypair.
+     * 
+     * @return a new {@link KeyPair}, never <code>null</code>.
+     */
+    public final KeyPair generateKeyPair() {
+        try {
+            return m_generator.generateKeyPair();
+        }
+        catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * @throws IllegalStateException if an internal exception occurs.
+     * @throws IllegalArgumentException if the alias already exists.
+     */
+    public X509Certificate createCertificate(String alias, String name, Date before, Date after, PublicKey key)
+        throws IllegalStateException, IllegalArgumentException {
+        try {
+            m_certGen.reset();
+            m_certGen.setSerialNumber(BigInteger.valueOf(++m_serial));
+            m_certGen.setIssuerDN(m_rootCert.getIssuerX500Principal());
+            m_certGen.setNotBefore(before);
+            m_certGen.setNotAfter(after);
+            m_certGen.setSubjectDN(new X500Principal(name));
+            m_certGen.setPublicKey(key);
+            m_certGen.setSignatureAlgorithm(SIGNATURE_ALGORITHM);
+
+            X509Certificate cert = m_certGen.generate(m_caKey.getPrivate());
+
+            return cert;
+        }
+        catch (IllegalArgumentException e) {
+            throw e;
+        }
+        catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private X509Certificate generateRootCertificate(String name, Date notBefore, Date notAfter) throws Exception {
+        m_certGen.reset();
+        m_certGen.setSerialNumber(BigInteger.valueOf(1));
+        m_certGen.setIssuerDN(new X500Principal(name));
+        m_certGen.setNotBefore(notBefore);
+        m_certGen.setNotAfter(notAfter);
+        m_certGen.setSubjectDN(new X500Principal(name));
+        m_certGen.setPublicKey(m_caKey.getPublic());
+        m_certGen.setSignatureAlgorithm(SIGNATURE_ALGORITHM);
+
+        return m_certGen.generate(m_caKey.getPrivate());
+    }
+}

Propchange: ace/trunk/ace-authenticationprocessor-clientcert/src/test/java/org/apache/ace/authenticationprocessor/clientcert/MemoryKeyStore.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: ace/trunk/ace-authenticationprocessor-password/
------------------------------------------------------------------------------
--- svn:ignore (added)
+++ svn:ignore Thu Apr 12 08:14:17 2012
@@ -0,0 +1,13 @@
+.metadata
+.settings
+bin
+.classpath
+.project
+target
+*.ipr
+*.iws
+*.iml
+store
+test-output
+velocity.log
+

Added: ace/trunk/ace-authenticationprocessor-password/pom.xml
URL: http://svn.apache.org/viewvc/ace/trunk/ace-authenticationprocessor-password/pom.xml?rev=1325151&view=auto
==============================================================================
--- ace/trunk/ace-authenticationprocessor-password/pom.xml (added)
+++ ace/trunk/ace-authenticationprocessor-password/pom.xml Thu Apr 12 08:14:17 2012
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <!--
+
+        Licensed to the Apache Software Foundation (ASF) under one or more
+        contributor license agreements.  See the NOTICE file distributed with
+        this work for additional information regarding copyright ownership.
+        The ASF licenses this file to You under the Apache License, Version 2.0
+        (the "License"); you may not use this file except in compliance with
+        the License.  You may obtain a copy of the License at
+
+           http://www.apache.org/licenses/LICENSE-2.0
+
+        Unless required by applicable law or agreed to in writing, software
+        distributed under the License is distributed on an "AS IS" BASIS,
+        WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+        See the License for the specific language governing permissions and
+        limitations under the License.
+    -->
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.ace</groupId>
+        <artifactId>ace-pom</artifactId>
+        <version>0.8.1-SNAPSHOT</version>
+        <relativePath>../pom/pom.xml</relativePath>
+    </parent>
+
+    <artifactId>org.apache.ace.authenticationprocessor.password</artifactId>
+
+    <name>Apache ACE :: Authentication :: Password</name>
+    <description>Provides an authentication processor that performs simple username/password authentication.</description>
+    <packaging>bundle</packaging>
+
+    <scm>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/ace/trunk/ace-authenticationprocessor-password</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/ace/trunk/ace-authenticationprocessor-password</developerConnection>
+        <url>http://svn.apache.org/repos/asf/ace/trunk/ace-authenticationprocessor-password</url>
+    </scm>
+        
+    <properties>
+        <import.package>
+        	org.apache.ace.authentication.api,
+        	org.apache.felix.dm,
+        	org.osgi.framework,
+        	org.osgi.service.cm,
+        	org.osgi.service.useradmin
+        </import.package>
+        <private.package>
+            org.apache.ace.authenticationprocessor.password,
+            org.apache.commons.codec,
+            org.apache.commons.codec.binary,
+			org.apache.commons.codec.digest
+        </private.package>
+        <bundle.activator>org.apache.ace.authenticationprocessor.password.Activator</bundle.activator>
+    </properties>
+    
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.ace</groupId>
+            <artifactId>org.apache.ace.authentication.api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.dependencymanager</artifactId>
+        </dependency>
+		<dependency>
+			<groupId>commons-codec</groupId>
+			<artifactId>commons-codec</artifactId>
+		</dependency>
+    </dependencies>
+</project>

Propchange: ace/trunk/ace-authenticationprocessor-password/pom.xml
------------------------------------------------------------------------------
    svn:eol-style = native

Added: ace/trunk/ace-authenticationprocessor-password/src/main/java/org/apache/ace/authenticationprocessor/password/Activator.java
URL: http://svn.apache.org/viewvc/ace/trunk/ace-authenticationprocessor-password/src/main/java/org/apache/ace/authenticationprocessor/password/Activator.java?rev=1325151&view=auto
==============================================================================
--- ace/trunk/ace-authenticationprocessor-password/src/main/java/org/apache/ace/authenticationprocessor/password/Activator.java (added)
+++ ace/trunk/ace-authenticationprocessor-password/src/main/java/org/apache/ace/authenticationprocessor/password/Activator.java Thu Apr 12 08:14:17 2012
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ace.authenticationprocessor.password;
+
+import java.util.Properties;
+
+import org.apache.ace.authentication.api.AuthenticationProcessor;
+import org.apache.felix.dm.DependencyActivatorBase;
+import org.apache.felix.dm.DependencyManager;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.service.cm.ManagedService;
+
+/**
+ * Provides a bundle activator for the {@link BasicHttpAuthenticationProcessor}.
+ */
+public class Activator extends DependencyActivatorBase {
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void init(BundleContext context, DependencyManager manager) throws Exception {
+        Properties props = new Properties();
+        props.put(Constants.SERVICE_PID, PasswordAuthenticationProcessor.PID);
+        
+// @formatter:off
+        manager.add(createComponent()
+            .setInterface(new String[]{ AuthenticationProcessor.class.getName(), ManagedService.class.getName() }, props)
+            .setImplementation(new PasswordAuthenticationProcessor())
+        );
+// @formatter:on
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void destroy(BundleContext context, DependencyManager manager) throws Exception {
+        // Nop
+    }
+}

Propchange: ace/trunk/ace-authenticationprocessor-password/src/main/java/org/apache/ace/authenticationprocessor/password/Activator.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: ace/trunk/ace-authenticationprocessor-password/src/main/java/org/apache/ace/authenticationprocessor/password/PasswordAuthenticationProcessor.java
URL: http://svn.apache.org/viewvc/ace/trunk/ace-authenticationprocessor-password/src/main/java/org/apache/ace/authenticationprocessor/password/PasswordAuthenticationProcessor.java?rev=1325151&view=auto
==============================================================================
--- ace/trunk/ace-authenticationprocessor-password/src/main/java/org/apache/ace/authenticationprocessor/password/PasswordAuthenticationProcessor.java (added)
+++ ace/trunk/ace-authenticationprocessor-password/src/main/java/org/apache/ace/authenticationprocessor/password/PasswordAuthenticationProcessor.java Thu Apr 12 08:14:17 2012
@@ -0,0 +1,192 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ace.authenticationprocessor.password;
+
+import java.util.Dictionary;
+
+import org.apache.ace.authentication.api.AuthenticationProcessor;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
+import org.osgi.service.useradmin.User;
+import org.osgi.service.useradmin.UserAdmin;
+
+/**
+ * Provides an {@link AuthenticationProcessor} that implements simple username/password-based
+ * authentication and looks up a user in the {@link UserAdmin} service using (by default, can be
+ * configured otherwise) the keys "username" and "password". It also supports (MD5, SHA1, SHA256, 
+ * SHA384 or SHA512) hashed passwords.
+ */
+public class PasswordAuthenticationProcessor implements AuthenticationProcessor, ManagedService {
+
+    public static final String PID = "org.apache.ace.authenticationprocessor.password";
+
+    static final String PROPERTY_KEY_USERNAME = "key.username";
+    static final String PROPERTY_KEY_PASSWORD = "key.password";
+    static final String PROPERTY_PASSWORD_HASHMETHOD = "password.hashmethod";
+
+    private static final String DEFAULT_PROPERTY_KEY_USERNAME = "username";
+    private static final String DEFAULT_PROPERTY_KEY_PASSWORD = "password";
+    private static final String DEFAULT_PROPERTY_PASSWORD_HASHMETHOD = "none";
+
+    private volatile String m_keyUsername = DEFAULT_PROPERTY_KEY_USERNAME;
+    private volatile String m_keyPassword = DEFAULT_PROPERTY_KEY_PASSWORD;
+    private volatile String m_passwordHashMethod = DEFAULT_PROPERTY_PASSWORD_HASHMETHOD;
+
+    /**
+     * {@inheritDoc}
+     */
+    public User authenticate(UserAdmin userAdmin, Object... context) {
+        final String username = (String) context[0];
+        final Object password = context[1];
+
+        if (username == null || "".equals(username.trim())) {
+            // Invalid/no username given!
+            return null;
+        }
+
+        if (password == null) {
+            // Invalid/no password given!
+            return null;
+        }
+
+        User user = userAdmin.getUser(m_keyUsername, username);
+        if (user == null || !user.hasCredential(m_keyPassword, hashPassword(password))) {
+            // Invalid/unknown user!
+            return null;
+        }
+
+        return user;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean canHandle(Object... context) {
+        if (context == null || context.length == 0) {
+            throw new IllegalArgumentException("Invalid context!");
+        }
+
+        if (context.length != 2) {
+            return false;
+        }
+
+        if (!(context[0] instanceof String)) {
+            return false;
+        }
+
+        return ((context[1] instanceof String) || (context[1] instanceof byte[]));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void updated(Dictionary dictionary) throws ConfigurationException {
+        if (dictionary != null) {
+            String keyUsername = (String) dictionary.get(PROPERTY_KEY_USERNAME);
+            if (keyUsername == null || "".equals(keyUsername.trim())) {
+                throw new ConfigurationException(PROPERTY_KEY_USERNAME, "Missing property");
+            }
+
+            String keyPassword = (String) dictionary.get(PROPERTY_KEY_PASSWORD);
+            if (keyPassword == null || "".equals(keyPassword.trim())) {
+                throw new ConfigurationException(PROPERTY_KEY_PASSWORD, "Missing property");
+            }
+
+            String passwordHashType = (String) dictionary.get(PROPERTY_PASSWORD_HASHMETHOD);
+            if (passwordHashType == null || "".equals(passwordHashType.trim())) {
+                throw new ConfigurationException(PROPERTY_PASSWORD_HASHMETHOD, "Missing property");
+            }
+            if (!isValidHashMethod(passwordHashType)) {
+                throw new ConfigurationException(PROPERTY_PASSWORD_HASHMETHOD, "Invalid hash method!");
+            }
+
+            m_keyUsername = keyUsername;
+            m_keyPassword = keyPassword;
+            m_passwordHashMethod = passwordHashType;
+        }
+        else {
+            m_keyUsername = DEFAULT_PROPERTY_KEY_USERNAME;
+            m_keyPassword = DEFAULT_PROPERTY_KEY_PASSWORD;
+            m_passwordHashMethod = DEFAULT_PROPERTY_PASSWORD_HASHMETHOD;
+        }
+    }
+
+    /**
+     * Hashes a given password using the current set hash method.
+     * 
+     * @param password the password to hash, should not be <code>null</code>.
+     * @return the hashed password, never <code>null</code>.
+     */
+    private Object hashPassword(Object password) {
+        if ("none".equalsIgnoreCase(m_passwordHashMethod)) {
+            // Very special ROT26 hashing method...
+            return password;
+        }
+
+        if ("md5".equalsIgnoreCase(m_passwordHashMethod)) {
+            if (password instanceof byte[]) {
+                return DigestUtils.md5((byte[]) password);
+            }
+            return DigestUtils.md5((String) password);
+        }
+        if ("sha1".equalsIgnoreCase(m_passwordHashMethod)) {
+            if (password instanceof byte[]) {
+                return DigestUtils.sha((byte[]) password);
+            }
+            return DigestUtils.sha((String) password);
+        }
+        if ("sha256".equalsIgnoreCase(m_passwordHashMethod)) {
+            if (password instanceof byte[]) {
+                return DigestUtils.sha256((byte[]) password);
+            }
+            return DigestUtils.sha256((String) password);
+        }
+        if ("sha384".equalsIgnoreCase(m_passwordHashMethod)) {
+            if (password instanceof byte[]) {
+                return DigestUtils.sha384((byte[]) password);
+            }
+            return DigestUtils.sha384((String) password);
+        }
+        if ("sha512".equalsIgnoreCase(m_passwordHashMethod)) {
+            if (password instanceof byte[]) {
+                return DigestUtils.sha512((byte[]) password);
+            }
+            return DigestUtils.sha512((String) password);
+        }
+        return password;
+    }
+
+    /**
+     * Determines whether the given hash method is valid.
+     * 
+     * @param hashMethod the hash method to test, can be <code>null</code> or empty.
+     * @return <code>true</code> if the given hash method is valid/supported, <code>false</code> otherwise.
+     */
+    private boolean isValidHashMethod(String hashMethod) {
+// @formatter:off
+      return "none".equalsIgnoreCase(hashMethod) 
+          || "md5".equalsIgnoreCase(hashMethod) 
+          || "sha1".equalsIgnoreCase(hashMethod) 
+          || "sha256".equalsIgnoreCase(hashMethod) 
+          || "sha384".equalsIgnoreCase(hashMethod) 
+          || "sha512".equalsIgnoreCase(hashMethod);
+// @formatter:on
+    }
+}

Propchange: ace/trunk/ace-authenticationprocessor-password/src/main/java/org/apache/ace/authenticationprocessor/password/PasswordAuthenticationProcessor.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: ace/trunk/ace-authenticationprocessor-password/src/test/java/org/apache/ace/authenticationprocessor/password/PasswordAuthenticationProcessorTest.java
URL: http://svn.apache.org/viewvc/ace/trunk/ace-authenticationprocessor-password/src/test/java/org/apache/ace/authenticationprocessor/password/PasswordAuthenticationProcessorTest.java?rev=1325151&view=auto
==============================================================================
--- ace/trunk/ace-authenticationprocessor-password/src/test/java/org/apache/ace/authenticationprocessor/password/PasswordAuthenticationProcessorTest.java (added)
+++ ace/trunk/ace-authenticationprocessor-password/src/test/java/org/apache/ace/authenticationprocessor/password/PasswordAuthenticationProcessorTest.java Thu Apr 12 08:14:17 2012
@@ -0,0 +1,290 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ace.authenticationprocessor.password;
+
+import static org.apache.ace.authenticationprocessor.password.PasswordAuthenticationProcessor.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.Properties;
+
+import org.apache.commons.codec.digest.DigestUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.useradmin.User;
+import org.osgi.service.useradmin.UserAdmin;
+
+/**
+ * Test cases for {@link PasswordAuthenticationProcessor}.
+ */
+public class PasswordAuthenticationProcessorTest {
+    
+    private UserAdmin m_userAdmin;
+
+    @Before
+    public void setUp() {
+        m_userAdmin = mock(UserAdmin.class);
+    }
+
+    /**
+     * Tests that authenticating with a empty username will yield null.
+     */
+    @Test
+    public void testAuthenticateEmptyUserNameYieldsNull() {
+        User result = new PasswordAuthenticationProcessor().authenticate(m_userAdmin, "", "secret");
+        assertNull(result);
+    }
+
+    /**
+     * Tests that authenticating a known user with an invalid password will yield null.
+     */
+    @Test
+    public void testAuthenticateKnownUserWithInvalidPasswordYieldsNull() {
+        User user = mock(User.class);
+        when(user.getName()).thenReturn("bob");
+        when(user.hasCredential(eq("password"), eq("otherSecret"))).thenReturn(Boolean.TRUE);
+
+        when(m_userAdmin.getUser(eq("username"), eq("bob"))).thenReturn(user);
+
+        User result = new PasswordAuthenticationProcessor().authenticate(m_userAdmin, "bob", "secret");
+        assertNull(result);
+    }
+
+    /**
+     * Tests that authenticating a known user with a correct password will not yield null.
+     */
+    @Test
+    public void testAuthenticateKnownUserYieldsValidResult() {
+        User user = mock(User.class);
+        when(user.getName()).thenReturn("bob");
+        when(user.hasCredential(eq("password"), eq("secret"))).thenReturn(Boolean.TRUE);
+
+        when(m_userAdmin.getUser(eq("username"), eq("bob"))).thenReturn(user);
+
+        User result = new PasswordAuthenticationProcessor().authenticate(m_userAdmin, "bob", "secret");
+        assertNotNull(result);
+        
+        assertEquals("bob", user.getName());
+    }
+
+    /**
+     * Tests that authenticating with a null password will yield null.
+     */
+    @Test
+    public void testAuthenticateNullPasswordYieldsNull() {
+        User result = new PasswordAuthenticationProcessor().authenticate(m_userAdmin, "bob", null);
+        assertNull(result);
+    }
+
+    /**
+     * Tests that authenticating with a null username will yield null.
+     */
+    @Test
+    public void testAuthenticateNullUserNameYieldsNull() {
+        User result = new PasswordAuthenticationProcessor().authenticate(m_userAdmin, null, "secret");
+        assertNull(result);
+    }
+
+    /**
+     * Tests that a class cast exception is thrown for invalid context when calling authenticate.
+     */
+    @Test(expected = ClassCastException.class)
+    public void testAuthenticateThrowsClassCastForInvalidContext() {
+        new PasswordAuthenticationProcessor().authenticate(m_userAdmin, new Object(), "foo");
+    }
+
+    /**
+     * Tests that authenticating an unknown user will yield null.
+     */
+    @Test
+    public void testAuthenticateUnknownUserYieldsNull() {
+        User result = new PasswordAuthenticationProcessor().authenticate(m_userAdmin, "bob", "secret");
+        assertNull(result);
+    }
+
+    /**
+     * Tests that canHandle yields true for string and byte array.
+     */
+    @Test
+    public void testCanHandleDoesAcceptStringAndByteArray() {
+        assertTrue(new PasswordAuthenticationProcessor().canHandle("foo", "bar".getBytes()));
+    }
+
+    /**
+     * Tests that canHandle yields true for two strings.
+     */
+    @Test
+    public void testCanHandleDoesAcceptTwoStrings() {
+        assertTrue(new PasswordAuthenticationProcessor().canHandle("foo", "bar"));
+    }
+
+    /**
+     * Tests that canHandle throws an {@link IllegalArgumentException} for an empty context.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testCanHandleDoesNotAcceptEmptyArray() {
+        new PasswordAuthenticationProcessor().canHandle(new Object[0]);
+    }
+
+    /**
+     * Tests that canHandle throws an {@link IllegalArgumentException} for a null context.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testCanHandleDoesNotAcceptNull() {
+        new PasswordAuthenticationProcessor().canHandle((Object[]) null);
+    }
+
+    /**
+     * Tests that canHandle yields false for too few arguments. 
+     */
+    @Test
+    public void testCanHandleDoesNotAcceptSingleArgument() {
+        assertFalse(new PasswordAuthenticationProcessor().canHandle(new Object()));
+    }
+    
+    /**
+     * Tests that canHandle yields false for a string and other object. 
+     */
+    @Test
+    public void testCanHandleDoesNotAcceptStringAndOtherObject() {
+        assertFalse(new PasswordAuthenticationProcessor().canHandle("foo", new Object()));
+    }
+
+    /**
+     * Tests that canHandle yields false for any object other than {@link HttpServletRequest}.
+     */
+    @Test
+    public void testCanHandleDoesNotAcceptWrongTypes() {
+        assertFalse(new PasswordAuthenticationProcessor().canHandle(new Object(), new Object()));
+    }
+    
+    /**
+     * Tests that updated does not throw an exception for a correct configuration.
+     */
+    @Test
+    public void testUpdatedDoesAcceptCorrectProperties() throws ConfigurationException {
+        final String keyUsername = "foo";
+        final String keyPassword = "bar";
+        
+        Properties props = new Properties();
+        props.put(PROPERTY_KEY_USERNAME, keyUsername);
+        props.put(PROPERTY_KEY_PASSWORD, keyPassword);
+        props.put(PROPERTY_PASSWORD_HASHMETHOD, "sha1");
+
+        PasswordAuthenticationProcessor processor = new PasswordAuthenticationProcessor();
+
+        processor.updated(props);
+
+        byte[] hashedPw = DigestUtils.sha("secret");
+        
+        // Test whether we can use the new properties...
+        User user = mock(User.class);
+        when(user.getName()).thenReturn("bob");
+        when(user.hasCredential(eq(keyPassword), eq(hashedPw))).thenReturn(Boolean.TRUE);
+
+        when(m_userAdmin.getUser(eq(keyUsername), eq("bob"))).thenReturn(user);
+
+        User result = processor.authenticate(m_userAdmin, "bob", "secret");
+        assertNotNull(result);
+        
+        assertEquals("bob", user.getName());
+    }
+    
+    /**
+     * Tests that updated throws an exception for missing "key.password" property. 
+     */
+    @Test(expected = ConfigurationException.class)
+    public void testUpdatedDoesNotAcceptEmptyKeyPassword() throws ConfigurationException {
+        Properties props = new Properties();
+        props.put(PROPERTY_KEY_USERNAME, "foo");
+        props.put(PROPERTY_KEY_PASSWORD, "");
+        props.put(PROPERTY_PASSWORD_HASHMETHOD, "none");
+        
+        new PasswordAuthenticationProcessor().updated(props);
+    }
+    
+    /**
+     * Tests that updated throws an exception for missing "key.username" property. 
+     */
+    @Test(expected = ConfigurationException.class)
+    public void testUpdatedDoesNotAcceptEmptyKeyUsername() throws ConfigurationException {
+        Properties props = new Properties();
+        props.put(PROPERTY_KEY_USERNAME, "");
+        props.put(PROPERTY_KEY_PASSWORD, "foo");
+        props.put(PROPERTY_PASSWORD_HASHMETHOD, "none");
+        
+        new PasswordAuthenticationProcessor().updated(props);
+    }
+    
+    /**
+     * Tests that updated throws an exception for missing "password.hashtype" property. 
+     */
+    @Test(expected = ConfigurationException.class)
+    public void testUpdatedDoesNotAcceptEmptyPasswordHashType() throws ConfigurationException {
+        Properties props = new Properties();
+        props.put(PROPERTY_KEY_USERNAME, "foo");
+        props.put(PROPERTY_KEY_PASSWORD, "bar");
+        props.put(PROPERTY_PASSWORD_HASHMETHOD, "");
+        
+        new PasswordAuthenticationProcessor().updated(props);
+    }
+    
+    /**
+     * Tests that updated throws an exception for missing "key.password" property. 
+     */
+    @Test(expected = ConfigurationException.class)
+    public void testUpdatedDoesNotAcceptMissingKeyPassword() throws ConfigurationException {
+        Properties props = new Properties();
+        props.put(PROPERTY_KEY_USERNAME, "foo");
+        props.put(PROPERTY_PASSWORD_HASHMETHOD, "none");
+
+        new PasswordAuthenticationProcessor().updated(props);
+    }
+    
+    /**
+     * Tests that updated throws an exception for missing "key.username" property. 
+     */
+    @Test(expected = ConfigurationException.class)
+    public void testUpdatedDoesNotAcceptMissingKeyUsername() throws ConfigurationException {
+        Properties props = new Properties();
+        props.put(PROPERTY_KEY_PASSWORD, "foo");
+        props.put(PROPERTY_PASSWORD_HASHMETHOD, "none");
+
+        new PasswordAuthenticationProcessor().updated(props);
+    }
+    
+    /**
+     * Tests that updated throws an exception for missing "password.hashtype" property. 
+     */
+    @Test(expected = ConfigurationException.class)
+    public void testUpdatedDoesNotAcceptMissingPasswordHashType() throws ConfigurationException {
+        Properties props = new Properties();
+        props.put(PROPERTY_KEY_USERNAME, "foo");
+        props.put(PROPERTY_KEY_PASSWORD, "foo");
+
+        new PasswordAuthenticationProcessor().updated(props);
+    }
+}

Propchange: ace/trunk/ace-authenticationprocessor-password/src/test/java/org/apache/ace/authenticationprocessor/password/PasswordAuthenticationProcessorTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: ace/trunk/ace-client-rest/pom.xml
URL: http://svn.apache.org/viewvc/ace/trunk/ace-client-rest/pom.xml?rev=1325151&r1=1325150&r2=1325151&view=diff
==============================================================================
--- ace/trunk/ace-client-rest/pom.xml (original)
+++ ace/trunk/ace-client-rest/pom.xml Thu Apr 12 08:14:17 2012
@@ -56,6 +56,10 @@
     <dependencies>
         <dependency>
             <groupId>org.apache.ace</groupId>
+            <artifactId>org.apache.ace.authentication.api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.ace</groupId>
             <artifactId>org.apache.ace.client.repository.api</artifactId>
         </dependency>
          <dependency>

Modified: ace/trunk/ace-client-rest/src/main/java/org/apache/ace/client/rest/RESTClientServlet.java
URL: http://svn.apache.org/viewvc/ace/trunk/ace-client-rest/src/main/java/org/apache/ace/client/rest/RESTClientServlet.java?rev=1325151&r1=1325150&r2=1325151&view=diff
==============================================================================
--- ace/trunk/ace-client-rest/src/main/java/org/apache/ace/client/rest/RESTClientServlet.java (original)
+++ ace/trunk/ace-client-rest/src/main/java/org/apache/ace/client/rest/RESTClientServlet.java Thu Apr 12 08:14:17 2012
@@ -291,7 +291,7 @@ public class RESTClientServlet extends H
         }
 
         if (pathElements.length == 1) {
-            createWorkspace(resp);
+            createWorkspace(req, resp);
         }
         else {
             // more than one path elements...
@@ -380,10 +380,10 @@ public class RESTClientServlet extends H
     /**
      * Creates a new workspace.
      * 
-     * @param resp the servlet repsonse to write the response data to.
+     * @param resp the servlet response to write the response data to.
      * @throws IOException in case of I/O errors.
      */
-    private void createWorkspace(HttpServletResponse resp) throws IOException {
+    private void createWorkspace(final HttpServletRequest req, final HttpServletResponse resp) throws IOException {
         // TODO get data from post body (if no data, assume latest??) -> for now always assume latest
         final String sessionID;
         final Workspace workspace;
@@ -400,7 +400,11 @@ public class RESTClientServlet extends H
         m_sessionFactory.createSession(sessionID);
         m_dm.add(component);
 
-        resp.sendRedirect(buildPathFromElements(WORK_FOLDER, sessionID));
+        if (!workspace.login(req)) {
+            resp.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+        } else {
+            resp.sendRedirect(buildPathFromElements(WORK_FOLDER, sessionID));
+        }
     }
 
     /**

Modified: ace/trunk/ace-client-rest/src/main/java/org/apache/ace/client/rest/Workspace.java
URL: http://svn.apache.org/viewvc/ace/trunk/ace-client-rest/src/main/java/org/apache/ace/client/rest/Workspace.java?rev=1325151&r1=1325150&r2=1325151&view=diff
==============================================================================
--- ace/trunk/ace-client-rest/src/main/java/org/apache/ace/client/rest/Workspace.java (original)
+++ ace/trunk/ace-client-rest/src/main/java/org/apache/ace/client/rest/Workspace.java Thu Apr 12 08:14:17 2012
@@ -27,6 +27,9 @@ import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.ace.authentication.api.AuthenticationService;
 import org.apache.ace.client.repository.ObjectRepository;
 import org.apache.ace.client.repository.RepositoryAdmin;
 import org.apache.ace.client.repository.RepositoryObject;
@@ -62,6 +65,7 @@ public class Workspace {
     private final String m_distributionRepositoryName;
     private final String m_deploymentRepositoryName;
     private final String m_serverUser;
+    private volatile AuthenticationService m_authenticationService;
     private volatile DependencyManager m_manager;
     private volatile RepositoryAdmin m_repositoryAdmin;
     private volatile ArtifactRepository m_artifactRepository;
@@ -118,13 +122,30 @@ public class Workspace {
         addSessionDependency(component, Artifact2FeatureAssociationRepository.class, true);
         addSessionDependency(component, Feature2DistributionAssociationRepository.class, true);
         addSessionDependency(component, Distribution2TargetAssociationRepository.class, true);
+        addDependency(component, AuthenticationService.class, true);
         addDependency(component, UserAdmin.class, true);
         addDependency(component, LogService.class, false);
     }
     
     public void start() {
+    }
+    
+    public void destroy() {
+    }
+    
+    public boolean login(HttpServletRequest request) {
         try {
-            User user = m_userAdmin.getUser("username", m_serverUser);
+            User user = m_authenticationService.authenticate(request);
+            if (user == null) {
+                // No user obtained through request; try fallback scenario...
+                // TODO this shouldn't be here, but otherwise we break all existing clients
+                user = m_userAdmin.getUser("username", m_serverUser);
+                if (user == null) {
+                    // Still no user obtained; no succesful login...
+                    return false;
+                }
+            }
+
             m_repositoryAdmin.login(m_repositoryAdmin.createLoginContext(user)
                 .setObrBase(new URL(m_obrURL))
                 .addShopRepository(new URL(m_repositoryURL), m_customerName, m_storeRepositoryName, true)
@@ -137,13 +158,12 @@ public class Workspace {
             e.printStackTrace();
             m_log.log(LogService.LOG_ERROR, "Could not login and checkout. Workspace will probably not work correctly.", e);
         }
-    }
-    
-    public void destroy() {
+        
+        return true;
     }
     
     public void checkout() throws IOException {
-    	m_repositoryAdmin.checkout();
+        m_repositoryAdmin.checkout();
     }
 
     public void commit() throws IOException {

Modified: ace/trunk/ace-target-devserver/pom.xml
URL: http://svn.apache.org/viewvc/ace/trunk/ace-target-devserver/pom.xml?rev=1325151&r1=1325150&r2=1325151&view=diff
==============================================================================
--- ace/trunk/ace-target-devserver/pom.xml (original)
+++ ace/trunk/ace-target-devserver/pom.xml Thu Apr 12 08:14:17 2012
@@ -174,6 +174,21 @@
         </dependency>
         <dependency>
             <groupId>org.apache.ace</groupId>
+            <artifactId>org.apache.ace.authentication</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.ace</groupId>
+            <artifactId>org.apache.ace.authenticationprocessor.password</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.ace</groupId>
+            <artifactId>org.apache.ace.authenticationprocessor.basicauth</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.ace</groupId>
             <artifactId>org.apache.ace.client.repository.api</artifactId>
             <scope>runtime</scope>
         </dependency>

Modified: ace/trunk/ace-webui-vaadin/pom.xml
URL: http://svn.apache.org/viewvc/ace/trunk/ace-webui-vaadin/pom.xml?rev=1325151&r1=1325150&r2=1325151&view=diff
==============================================================================
--- ace/trunk/ace-webui-vaadin/pom.xml (original)
+++ ace/trunk/ace-webui-vaadin/pom.xml Thu Apr 12 08:14:17 2012
@@ -87,6 +87,14 @@
             <artifactId>org.apache.ace.httplistener</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.apache.ace</groupId>
+            <artifactId>org.apache.ace.authentication.api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.ace</groupId>
+            <artifactId>org.apache.ace.authentication</artifactId>
+        </dependency>
+        <dependency>
             <groupId>com.vaadin</groupId>
             <artifactId>vaadin</artifactId>
         </dependency>

Modified: ace/trunk/ace-webui-vaadin/src/main/java/org/apache/ace/webui/vaadin/VaadinClient.java
URL: http://svn.apache.org/viewvc/ace/trunk/ace-webui-vaadin/src/main/java/org/apache/ace/webui/vaadin/VaadinClient.java?rev=1325151&r1=1325150&r2=1325151&view=diff
==============================================================================
--- ace/trunk/ace-webui-vaadin/src/main/java/org/apache/ace/webui/vaadin/VaadinClient.java (original)
+++ ace/trunk/ace-webui-vaadin/src/main/java/org/apache/ace/webui/vaadin/VaadinClient.java Thu Apr 12 08:14:17 2012
@@ -28,6 +28,7 @@ import java.util.Properties;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
 
+import org.apache.ace.authentication.api.AuthenticationService;
 import org.apache.ace.client.repository.RepositoryAdmin;
 import org.apache.ace.client.repository.RepositoryAdminLoginContext;
 import org.apache.ace.client.repository.RepositoryObject;
@@ -51,6 +52,7 @@ import org.apache.ace.client.repository.
 import org.apache.ace.test.utils.FileUtils;
 import org.apache.ace.webui.NamedObject;
 import org.apache.ace.webui.UIExtensionFactory;
+import org.apache.ace.webui.vaadin.LoginWindow.LoginFunction;
 import org.apache.ace.webui.vaadin.component.ArtifactsPanel;
 import org.apache.ace.webui.vaadin.component.DistributionsPanel;
 import org.apache.ace.webui.vaadin.component.FeaturesPanel;
@@ -101,7 +103,7 @@ import com.vaadin.ui.Window;
  - Add buttons to create new items in all of the tables (done for those that make sense)
  */
 @SuppressWarnings("serial")
-public class VaadinClient extends com.vaadin.Application implements AssociationRemover {
+public class VaadinClient extends com.vaadin.Application implements AssociationRemover, LoginFunction {
 
     private static final long serialVersionUID = 1L;
 
@@ -113,6 +115,7 @@ public class VaadinClient extends com.va
     private static String customerName = "apache";
     private static String endpoint = "/repository";
 
+    private volatile AuthenticationService m_authenticationService;
     private volatile DependencyManager m_manager;
     private volatile BundleContext m_context;
     private volatile SessionFactory m_sessionFactory;
@@ -234,12 +237,10 @@ public class VaadinClient extends com.va
      * Shows the login window on the center of the main window.
      */
     private void showLoginWindow() {
-        LoginWindow loginWindow = new LoginWindow(m_log, new LoginWindow.LoginFunction() {
-            public boolean login(String name, String password) {
-                return VaadinClient.this.login(name, password);
-            }
-        });
+        LoginWindow loginWindow = new LoginWindow(m_log, this);
+        
         m_mainWindow.addWindow(loginWindow);
+        
         loginWindow.center();
     }
 
@@ -448,15 +449,16 @@ public class VaadinClient extends com.va
         m_feature2distributionAssociationRepository.remove(association);
     }
 
-    boolean login(String username, String password) {
+    /**
+     * {@inheritDoc}
+     */
+    public boolean login(String username, String password) {
         try {
-            User user = m_userAdmin.getUser("username", username);
+            User user = m_authenticationService.authenticate(username, password);
             if (user == null) {
                 return false;
             }
-            if (!user.hasCredential("password", password)) {
-                return false;
-            }
+
             RepositoryAdminLoginContext context = m_admin.createLoginContext(user);
 
             // @formatter:off
@@ -465,13 +467,15 @@ public class VaadinClient extends com.va
                 .addTargetRepository(new URL(m_aceHost, endpoint), customerName, targetRepo, true)
                 .addDeploymentRepository(new URL(m_aceHost, endpoint), customerName, deployRepo, true);
             // @formatter:on
+            
             m_admin.login(context);
             initGrid(user);
             m_admin.checkout();
+            
             return true;
         }
         catch (IOException e) {
-            e.printStackTrace();
+            m_log.log(LogService.LOG_WARNING, "Login failed!", e);
             return false;
         }
     }

Modified: ace/trunk/ace-webui-vaadin/src/main/java/org/apache/ace/webui/vaadin/VaadinServlet.java
URL: http://svn.apache.org/viewvc/ace/trunk/ace-webui-vaadin/src/main/java/org/apache/ace/webui/vaadin/VaadinServlet.java?rev=1325151&r1=1325150&r2=1325151&view=diff
==============================================================================
--- ace/trunk/ace-webui-vaadin/src/main/java/org/apache/ace/webui/vaadin/VaadinServlet.java (original)
+++ ace/trunk/ace-webui-vaadin/src/main/java/org/apache/ace/webui/vaadin/VaadinServlet.java Thu Apr 12 08:14:17 2012
@@ -18,9 +18,14 @@
  */
 package org.apache.ace.webui.vaadin;
 
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Dictionary;
+
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 
+import org.apache.ace.authentication.api.AuthenticationService;
 import org.apache.ace.client.repository.SessionFactory;
 import org.apache.felix.dm.DependencyManager;
 import org.osgi.service.cm.ConfigurationException;
@@ -31,10 +36,6 @@ import org.osgi.service.useradmin.UserAd
 import com.vaadin.Application;
 import com.vaadin.terminal.gwt.server.AbstractApplicationServlet;
 
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.Dictionary;
-
 public class VaadinServlet extends AbstractApplicationServlet implements ManagedService {
     private static final long serialVersionUID = 1L;
     
@@ -68,6 +69,10 @@ public class VaadinServlet extends Abstr
                 .setRequired(true)
             )
             .add(m_manager.createServiceDependency()
+                .setService(AuthenticationService.class)
+                .setRequired(true)
+            )
+            .add(m_manager.createServiceDependency()
                 .setService(LogService.class)
                 .setRequired(false)
             )

Modified: ace/trunk/pom.xml
URL: http://svn.apache.org/viewvc/ace/trunk/pom.xml?rev=1325151&r1=1325150&r2=1325151&view=diff
==============================================================================
--- ace/trunk/pom.xml (original)
+++ ace/trunk/pom.xml Thu Apr 12 08:14:17 2012
@@ -38,6 +38,12 @@
         <module>ace-ant-tasks</module>
         <module>ace-builder</module>
 
+        <module>ace-authentication-api</module>
+        <module>ace-authentication</module>
+        <module>ace-authenticationprocessor-basicauth</module>
+        <module>ace-authenticationprocessor-clientcert</module>
+        <module>ace-authenticationprocessor-password</module>
+
         <module>ace-range-api</module>
 
         <module>ace-repository-api</module>
@@ -198,6 +204,9 @@
             <modules>
                 <module>pom</module>
                 <module>ace-util</module>
+                <module>ace-authentication</module>
+                <module>ace-authenticationprocessor-basicauth</module>
+                <module>ace-authenticationprocessor-password</module>
                 <module>ace-client-repository-impl</module>
                 <module>ace-client-repository-helper-bundle</module>
                 <module>ace-configurator</module>

Modified: ace/trunk/pom/pom.xml
URL: http://svn.apache.org/viewvc/ace/trunk/pom/pom.xml?rev=1325151&r1=1325150&r2=1325151&view=diff
==============================================================================
--- ace/trunk/pom/pom.xml (original)
+++ ace/trunk/pom/pom.xml Thu Apr 12 08:14:17 2012
@@ -134,6 +134,7 @@
         <deployment.providesresourceprocessor />
         <sourceReleaseAssemblyDescriptor>source-release-zip-tar</sourceReleaseAssemblyDescriptor>
         <commons-collections.version>3.2.1</commons-collections.version>
+        <commons-codec.version>1.5</commons-codec.version>
         <commons-lang.version>2.4</commons-lang.version>
         <commons-logging.version>1.1.1</commons-logging.version>
         <commons-io.version>2.0.1</commons-io.version>
@@ -325,6 +326,31 @@
             <!-- ACE modules -->
             <dependency>
                 <groupId>org.apache.ace</groupId>
+                <artifactId>org.apache.ace.authentication.api</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.ace</groupId>
+                <artifactId>org.apache.ace.authentication</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.ace</groupId>
+                <artifactId>org.apache.ace.authenticationprocessor.basicauth</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.ace</groupId>
+                <artifactId>org.apache.ace.authenticationprocessor.clientcert</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.ace</groupId>
+                <artifactId>org.apache.ace.authenticationprocessor.password</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.ace</groupId>
                 <artifactId>org.apache.ace.client.automation</artifactId>
                 <version>${project.version}</version>
             </dependency>
@@ -687,6 +713,11 @@
 
             <!-- Commons -->
             <dependency>
+                <groupId>commons-codec</groupId>
+                <artifactId>commons-codec</artifactId>
+                <version>${commons-codec.version}</version>
+            </dependency>
+            <dependency>
                 <groupId>commons-collections</groupId>
                 <artifactId>commons-collections</artifactId>
                 <version>${commons-collections.version}</version>