You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ace.apache.org by ma...@apache.org on 2012/07/05 14:10:06 UTC
svn commit: r1357570 [7/34] - in /ace/sandbox/marrs: cnf/ cnf/ext/ cnf/lib/
cnf/releaserepo/ cnf/repo/ cnf/repo/.obrcache/
cnf/repo/.obrcache/http%3A%2F%2Fbundles.bndtools.org.s3.amazonaws.com%2Fcom.jcraft.jsch/
cnf/repo/.obrcache/http%3A%2F%2Fbundles....
Added: ace/sandbox/marrs/org.apache.ace.authenticationprocessor.clientcert/pom.xml
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.authenticationprocessor.clientcert/pom.xml?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.authenticationprocessor.clientcert/pom.xml (added)
+++ ace/sandbox/marrs/org.apache.ace.authenticationprocessor.clientcert/pom.xml Thu Jul 5 12:09:30 2012
@@ -0,0 +1,86 @@
+<?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.clientcert</artifactId>
+
+ <name>Apache ACE :: Authentication :: Client certificate</name>
+ <description>Provides an authentication processor that can handle client certificate authentication.</description>
+ <packaging>bundle</packaging>
+
+ <scm>
+ <connection>scm:svn:http://svn.apache.org/repos/asf/ace/trunk/ace-authentication-clientcert</connection>
+ <developerConnection>scm:svn:https://svn.apache.org/repos/asf/ace/trunk/ace-authentication-clientcert</developerConnection>
+ <url>http://svn.apache.org/repos/asf/ace/trunk/ace-authentication-clientcert</url>
+ </scm>
+
+ <properties>
+ <import.package>
+ *
+ </import.package>
+ <private.package>
+ org.apache.ace.authenticationprocessor.clientcert
+ </private.package>
+ <bundle.activator>org.apache.ace.authenticationprocessor.clientcert.Activator</bundle.activator>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.ace</groupId>
+ <artifactId>org.apache.ace.authentication.api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-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>bouncycastle</groupId>
+ <artifactId>bcprov-jdk14</artifactId>
+ <version>140</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.ace</groupId>
+ <artifactId>org.apache.ace.util</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
Added: ace/sandbox/marrs/org.apache.ace.authenticationprocessor.clientcert/src/org/apache/ace/authenticationprocessor/clientcert/Activator.java
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.authenticationprocessor.clientcert/src/org/apache/ace/authenticationprocessor/clientcert/Activator.java?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.authenticationprocessor.clientcert/src/org/apache/ace/authenticationprocessor/clientcert/Activator.java (added)
+++ ace/sandbox/marrs/org.apache.ace.authenticationprocessor.clientcert/src/org/apache/ace/authenticationprocessor/clientcert/Activator.java Thu Jul 5 12:09:30 2012
@@ -0,0 +1,64 @@
+/*
+ * 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.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;
+import org.osgi.service.log.LogService;
+
+/**
+ * Provides a bundle activator for the {@link ClientCertAuthenticationProcessor}.
+ */
+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, ClientCertAuthenticationProcessor.PID);
+
+// @formatter:off
+ manager.add(createComponent()
+ .setInterface(new String[]{ AuthenticationProcessor.class.getName(), ManagedService.class.getName() }, props)
+ .setImplementation(new ClientCertAuthenticationProcessor())
+ .add(createServiceDependency()
+ .setService(LogService.class)
+ .setRequired(false)
+ )
+ );
+// @formatter:on
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void destroy(BundleContext context, DependencyManager manager) throws Exception {
+ // Nop
+ }
+}
Added: ace/sandbox/marrs/org.apache.ace.authenticationprocessor.clientcert/src/org/apache/ace/authenticationprocessor/clientcert/ClientCertAuthenticationProcessor.java
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.authenticationprocessor.clientcert/src/org/apache/ace/authenticationprocessor/clientcert/ClientCertAuthenticationProcessor.java?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.authenticationprocessor.clientcert/src/org/apache/ace/authenticationprocessor/clientcert/ClientCertAuthenticationProcessor.java (added)
+++ ace/sandbox/marrs/org.apache.ace.authenticationprocessor.clientcert/src/org/apache/ace/authenticationprocessor/clientcert/ClientCertAuthenticationProcessor.java Thu Jul 5 12:09:30 2012
@@ -0,0 +1,252 @@
+/*
+ * 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.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.X509Certificate;
+import java.util.Dictionary;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.ace.authentication.api.AuthenticationProcessor;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
+import org.osgi.service.log.LogService;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+import org.osgi.service.useradmin.UserAdmin;
+
+/**
+ * Provides an {@link AuthenticationProcessor} that implements authentication based on certificates
+ * and looks up a user in the {@link UserAdmin} service using (by default, can be configured
+ * otherwise) the key "username". If a matching user is found, it is considered authenticated.
+ */
+public class ClientCertAuthenticationProcessor implements AuthenticationProcessor, ManagedService {
+
+ public static final String PID = "org.apache.ace.authenticationprocessor.clientcert";
+
+ static final String ATTRIBUTE_X509_CERTIFICATE = "javax.servlet.request.X509Certificate";
+ static final String ATTRIBUTE_CIPHER_SUITE = "javax.servlet.request.cipher_suite";
+
+ static final String PROPERTY_USERNAME_LOOKUPKEY = "user.name.lookupKey";
+ static final String PROPERTY_USERNAME_MATCH_POLICY = "user.name.matchPolicy";
+ static final String PROPERTY_VERIFY_CERT_VALIDITY = "certificate.verifyValidity";
+
+ private static final String DEFAULT_PROPERTY_USERNAME_LOOKUPKEY = "username";
+ private static final String DEFAULT_PROPERTY_USERNAME_MATCHPOLICY = "cn";
+ private static final boolean DEFAULT_PROPERTY_VERIFY_CERT_VALIDITY = true;
+
+ private volatile String m_nameLookupKey = DEFAULT_PROPERTY_USERNAME_LOOKUPKEY;
+ private volatile String m_nameMatchPolicy = DEFAULT_PROPERTY_USERNAME_MATCHPOLICY;
+ private volatile boolean m_verifyCertValidity = DEFAULT_PROPERTY_VERIFY_CERT_VALIDITY;
+ private volatile LogService m_log;
+
+ /**
+ * Creates a new {@link ClientCertAuthenticationProcessor} instance.
+ */
+ public ClientCertAuthenticationProcessor() {
+ // Nop
+ }
+
+ /**
+ * Creates a new {@link ClientCertAuthenticationProcessor} with a given logger.
+ */
+ ClientCertAuthenticationProcessor(LogService log) {
+ m_log = log;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean canHandle(Object... context) {
+ if (context == null || context.length == 0) {
+ throw new IllegalArgumentException("Invalid context!");
+ }
+
+ if (!(context[0] instanceof HttpServletRequest)) {
+ return false;
+ }
+
+ final HttpServletRequest request = (HttpServletRequest) context[0];
+ return (request.getAttribute(ATTRIBUTE_CIPHER_SUITE) != null) && (request.getAttribute(ATTRIBUTE_X509_CERTIFICATE) != null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public User authenticate(UserAdmin userAdmin, Object... context) {
+ final HttpServletRequest request = (HttpServletRequest) context[0];
+
+ String cipherSuite = (String) request.getAttribute(ATTRIBUTE_CIPHER_SUITE);
+ if (cipherSuite == null) {
+ // No SSL connection?!
+ m_log.log(LogService.LOG_DEBUG, "No SSL connection (no cipher suite found)?!");
+ return null;
+ }
+
+ X509Certificate certificateChain[] = (X509Certificate[]) request.getAttribute(ATTRIBUTE_X509_CERTIFICATE);
+ if (certificateChain == null || certificateChain.length == 0) {
+ // No certificates given...
+ m_log.log(LogService.LOG_DEBUG, "Failed to obtain X509 certificate chain from request!");
+ return null;
+ }
+
+ // Validate the certificate chain...
+ // TODO there should be more checks performed here...
+ final X509Certificate cert = validateCertificateChain(certificateChain);
+ if (cert == null) {
+ // Invalid certificate(chain)...
+ m_log.log(LogService.LOG_DEBUG, "Failed to validate X509 certificate chain!");
+ return null;
+ }
+
+ String name = getName(cert);
+ if (name == null) {
+ // No common name given; cannot retrieve user credentials...
+ m_log.log(LogService.LOG_DEBUG, "Failed to obtain common name of X509 certificate!");
+ return null;
+ }
+
+ User user = getUser(userAdmin, name);
+ if (user == null) {
+ // Invalid/unknown user!
+ m_log.log(LogService.LOG_DEBUG, "Failed to validate user using certificate!");
+ return null;
+ }
+
+ return user;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void updated(Dictionary dictionary) throws ConfigurationException {
+ if (dictionary != null) {
+ String usernameLookupKey = (String) dictionary.get(PROPERTY_USERNAME_LOOKUPKEY);
+ if (usernameLookupKey == null || "".equals(usernameLookupKey.trim())) {
+ throw new ConfigurationException(PROPERTY_USERNAME_LOOKUPKEY, "Missing property");
+ }
+
+ String usernameMatchPolicy = (String) dictionary.get(PROPERTY_USERNAME_MATCH_POLICY);
+ if (usernameMatchPolicy == null || "".equals(usernameMatchPolicy.trim())) {
+ throw new ConfigurationException(PROPERTY_USERNAME_MATCH_POLICY, "Missing property");
+ }
+
+ Object verifyCertValidity = dictionary.get(PROPERTY_VERIFY_CERT_VALIDITY);
+ if (verifyCertValidity == null || !("true".equals(verifyCertValidity) || "false".equals(verifyCertValidity))) {
+ throw new ConfigurationException(PROPERTY_VERIFY_CERT_VALIDITY, "Missing or invalid property!");
+ }
+
+ m_nameLookupKey = usernameLookupKey;
+ m_nameMatchPolicy = usernameMatchPolicy;
+ m_verifyCertValidity = Boolean.parseBoolean((String) verifyCertValidity);
+ }
+ else {
+ m_nameLookupKey = DEFAULT_PROPERTY_USERNAME_LOOKUPKEY;
+ m_nameMatchPolicy = DEFAULT_PROPERTY_USERNAME_MATCHPOLICY;
+ m_verifyCertValidity = DEFAULT_PROPERTY_VERIFY_CERT_VALIDITY;
+ }
+ }
+
+ /**
+ * Retrieves the name for the given certificate.
+ *
+ * @param certificate the certificate to get its name for, cannot be <code>null</code>.
+ * @return the name for the given certificate, can be <code>null</code>.
+ */
+ private String getName(X509Certificate certificate) {
+ try {
+ String dn = certificate.getSubjectX500Principal().getName();
+ if ("dn".equalsIgnoreCase(m_nameMatchPolicy)) {
+ return dn;
+ }
+
+ LdapName ldapDN = new LdapName(dn);
+ for (Rdn rdn : ldapDN.getRdns()) {
+ if (m_nameMatchPolicy.equalsIgnoreCase(rdn.getType())) {
+ return (String) rdn.getValue();
+ }
+ }
+ }
+ catch (InvalidNameException e) {
+ // Ignore...
+ }
+ return null;
+ }
+
+ /**
+ * Searches for a user with a given name.
+ * <p>
+ * This method first looks whether there's a user with the property
+ * "m_keyUsername" that matches the given username, if not found, it will
+ * try to retrieve a role with the given name.
+ * </p>
+ *
+ * @param userAdmin the {@link UserAdmin} service to get users from;
+ * @param name the name of the user to retrieve.
+ * @return a {@link User}, can be <code>null</code> if no such user is found.
+ */
+ private User getUser(UserAdmin userAdmin, String name) {
+ Role user = null;
+ if (m_nameLookupKey != null) {
+ user = userAdmin.getUser(m_nameLookupKey, name);
+ }
+ if (user == null) {
+ user = userAdmin.getRole(name);
+ }
+ return (user instanceof User) ? (User) user : null;
+ }
+
+ /**
+ * Validates the certificate chain whether all certificates are valid and not expired.
+ *
+ * @param certificateChain the chain of certificates to validate, cannot be <code>null</code>.
+ * @return if the chain is valid, the first certificate, <code>null</code> otherwise.
+ */
+ private X509Certificate validateCertificateChain(X509Certificate[] certificateChain) {
+ try {
+ for (X509Certificate cert : certificateChain) {
+ if (cert == null) {
+ // Bogus certificate given...
+ return null;
+ }
+ if (m_verifyCertValidity) {
+ cert.checkValidity();
+ }
+ }
+ }
+ catch (CertificateExpiredException e) {
+ // Refuse to go further with expired certificates...
+ m_log.log(LogService.LOG_DEBUG, "Certificate expired!", e);
+ return null;
+ }
+ catch (CertificateNotYetValidException e) {
+ // Refuse to go further with invalid certificates...
+ m_log.log(LogService.LOG_DEBUG, "Certificate not yet valid!", e);
+ return null;
+ }
+
+ // This *might* be a valid certificate chain; return the first certificate...
+ return certificateChain[0];
+ }
+}
Added: ace/sandbox/marrs/org.apache.ace.authenticationprocessor.clientcert/test/org/apache/ace/authenticationprocessor/clientcert/ClientCertAuthenticationProcessorTest.java
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.authenticationprocessor.clientcert/test/org/apache/ace/authenticationprocessor/clientcert/ClientCertAuthenticationProcessorTest.java?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.authenticationprocessor.clientcert/test/org/apache/ace/authenticationprocessor/clientcert/ClientCertAuthenticationProcessorTest.java (added)
+++ ace/sandbox/marrs/org.apache.ace.authenticationprocessor.clientcert/test/org/apache/ace/authenticationprocessor/clientcert/ClientCertAuthenticationProcessorTest.java Thu Jul 5 12:09:30 2012
@@ -0,0 +1,498 @@
+/*
+ * 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_USERNAME_LOOKUPKEY;
+import static org.apache.ace.authenticationprocessor.clientcert.ClientCertAuthenticationProcessor.PROPERTY_USERNAME_MATCH_POLICY;
+import static org.apache.ace.authenticationprocessor.clientcert.ClientCertAuthenticationProcessor.PROPERTY_VERIFY_CERT_VALIDITY;
+import static org.apache.ace.test.utils.TestUtils.UNIT;
+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.security.auth.x500.X500Principal;
+import javax.servlet.http.HttpServletRequest;
+
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.log.LogService;
+import org.osgi.service.useradmin.User;
+import org.osgi.service.useradmin.UserAdmin;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+/**
+ * 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(alwaysRun = true)
+ public static void init() {
+ m_keystore = new MemoryKeyStore("cn=testCA", dayBeforeYesterday(), dayAfterTomorrow());
+ }
+
+ /**
+ * Set up for each individual test.
+ */
+ @BeforeMethod(alwaysRun = true)
+ public void setUp() {
+ m_log = mock(LogService.class);
+
+ m_userAdmin = mock(UserAdmin.class);
+ m_servletRequest = mock(HttpServletRequest.class);
+
+ when(m_servletRequest.getAttribute(ATTRIBUTE_CIPHER_SUITE)).thenReturn("bogus-cipher-suite");
+ }
+
+ /**
+ * Tests that a null certificate chain will yield null.
+ */
+ @Test(groups = { UNIT })
+ public void testAuthenticateNoCertificateChainYieldsNull() {
+ User result = createAuthorizationProcessor().authenticate(m_userAdmin, m_servletRequest);
+ assert result == null : "Did not expect a valid user to be returned!";
+ }
+
+ /**
+ * Tests that an empty certificate chain will yield null.
+ */
+ @Test(groups = { UNIT })
+ public void testAuthenticateEmptyCertificateChainYieldsNull() {
+ when(m_servletRequest.getAttribute(ATTRIBUTE_X509_CERTIFICATE)).thenReturn(new X509Certificate[0]);
+
+ User result = createAuthorizationProcessor().authenticate(m_userAdmin, m_servletRequest);
+ assert result == null : "Did not expect a valid user to be returned!";
+ }
+
+ /**
+ * Tests that authenticating a known user with an invalid (expired) certificate will yield null.
+ */
+ @Test(groups = { UNIT })
+ 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);
+ assert result == null : "Did not expect a valid user to be returned!";
+ }
+
+ /**
+ * Tests that authenticating a known user with an invalid (not valid) certificate will yield null.
+ */
+ @Test(groups = { UNIT })
+ 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);
+ assert result == null : "Did not expect a valid user to be returned!";
+ }
+
+ /**
+ * Tests that authenticating a known user with a valid certificate will not yield null.
+ */
+ @Test(groups = { UNIT })
+ public void testAuthenticateKnownUserYieldsValidResult() {
+ X509Certificate[] certChain = createValidCertificateChain("bob");
+
+ when(m_servletRequest.getAttribute(ATTRIBUTE_X509_CERTIFICATE)).thenReturn(certChain);
+
+ User user = mock(User.class);
+ when(user.getName()).thenReturn("bob");
+
+ when(m_userAdmin.getUser(eq("username"), eq("bob"))).thenReturn(user);
+
+ User result = createAuthorizationProcessor().authenticate(m_userAdmin, m_servletRequest);
+ assert result != null : "Expected a valid user to be returned!";
+
+ assert "bob".equals(user.getName()) : "Expected bob to be returned as user!";
+ }
+
+ /**
+ * Tests that authenticating a known user with a valid certificate chain will not yield null.
+ */
+ @Test(groups = { UNIT })
+ public void testAuthenticateKnownUserWithValidCertificateChainYieldsValidResult() throws ConfigurationException {
+ ClientCertAuthenticationProcessor processor = createAuthorizationProcessor();
+
+ final String lookupKey = "anyKey";
+ final String matchPolicy = "dn";
+
+ Properties props = new Properties();
+ props.put(PROPERTY_USERNAME_LOOKUPKEY, lookupKey);
+ props.put(PROPERTY_USERNAME_MATCH_POLICY, matchPolicy);
+ props.put(PROPERTY_VERIFY_CERT_VALIDITY, "true");
+ processor.updated(props);
+
+ X509Certificate[] certChain = createValidCertificateChainWithDN("cn=Alice,dc=acme,dc=corp", "cn=Fido,ou=dev,dc=acme,dc=corp", "cn=Bob,ou=dev,dc=acme,dc=corp");
+
+ when(m_servletRequest.getAttribute(ATTRIBUTE_X509_CERTIFICATE)).thenReturn(certChain);
+
+ User user = mock(User.class);
+ when(user.getName()).thenReturn("bob");
+
+ when(m_userAdmin.getUser(eq(lookupKey), eq("CN=Bob,OU=dev,DC=acme,DC=corp"))).thenReturn(user);
+
+ User result = processor.authenticate(m_userAdmin, m_servletRequest);
+ assert result != null : "Expected a valid user to be returned!";
+
+ assert "bob".equals(user.getName()) : "Expected bob to be returned as user!";
+ }
+
+ /**
+ * Tests that a missing cipher suite header will the authenticate method to yield null.
+ */
+ @Test(groups = { UNIT })
+ 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);
+ assert result == null : "Did not expect a valid user to be returned!";
+ }
+
+ /**
+ * Tests that a class cast exception is thrown for invalid context when calling authenticate.
+ */
+ @Test(groups = { UNIT }, expectedExceptions = ClassCastException.class)
+ public void testAuthenticateThrowsClassCastForInvalidContext() {
+ createAuthorizationProcessor().authenticate(m_userAdmin, new Object());
+ }
+
+ /**
+ * Tests that an unknown user will yield null.
+ */
+ @Test(groups = { UNIT })
+ public void testAuthenticateUnknownUserYieldsNull() {
+ when(m_servletRequest.getAttribute(ATTRIBUTE_X509_CERTIFICATE)).thenReturn(createValidCertificateChain("bob"));
+
+ User result = createAuthorizationProcessor().authenticate(m_userAdmin, m_servletRequest);
+ assert result == null : "Did not expect a valid user to be returned!";
+ }
+
+ /**
+ * Tests that canHandle yields false for any object other than {@link HttpServletRequest}.
+ */
+ @Test(groups = { UNIT })
+ public void testCanHandleDoesAcceptServletRequest() {
+ when(m_servletRequest.getAttribute(ATTRIBUTE_X509_CERTIFICATE)).thenReturn(createValidCertificateChain("alice"));
+
+ assert createAuthorizationProcessor().canHandle(m_servletRequest);
+ }
+
+ /**
+ * Tests that canHandle throws an {@link IllegalArgumentException} for an empty context.
+ */
+ @Test(groups = { UNIT }, expectedExceptions = IllegalArgumentException.class)
+ public void testCanHandleDoesNotAcceptEmptyArray() {
+ createAuthorizationProcessor().canHandle(new Object[0]);
+ }
+
+ /**
+ * Tests that canHandle throws an {@link IllegalArgumentException} for a null context.
+ */
+ @Test(groups = { UNIT }, expectedExceptions = IllegalArgumentException.class)
+ public void testCanHandleDoesNotAcceptNull() {
+ createAuthorizationProcessor().canHandle((Object[]) null);
+ }
+
+ /**
+ * Tests that canHandle yields false for any object other than {@link HttpServletRequest}.
+ */
+ @Test(groups = { UNIT })
+ public void testCanHandleDoesNotAcceptUnhandledContext() {
+ assert createAuthorizationProcessor().canHandle(new Object()) == false;
+ }
+
+ /**
+ * Tests that updated does not throw an exception for a correct configuration.
+ */
+ @Test(groups = { UNIT })
+ public void testUpdatedDoesAcceptCorrectProperties() throws ConfigurationException {
+ final String lookupKey = "anyKey";
+ final String matchPolicy = "cn";
+
+ Properties props = new Properties();
+ props.put(PROPERTY_USERNAME_LOOKUPKEY, lookupKey);
+ props.put(PROPERTY_USERNAME_MATCH_POLICY, matchPolicy);
+ props.put(PROPERTY_VERIFY_CERT_VALIDITY, "true");
+
+ ClientCertAuthenticationProcessor processor = createAuthorizationProcessor();
+
+ processor.updated(props);
+
+ X509Certificate[] certificateChain = createValidCertificateChain("alice");
+
+ // 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("alice");
+
+ when(m_userAdmin.getUser(eq(lookupKey), eq("alice"))).thenReturn(user);
+
+ User result = processor.authenticate(m_userAdmin, m_servletRequest);
+ assert result != null : "Expected a valid user to be returned!";
+
+ assert "alice".equals(user.getName()) : "Expected alice to be returned as user!";
+ }
+
+ /**
+ * Tests that updated throws an exception for missing "username match policy" property.
+ */
+ @Test(groups = { UNIT }, expectedExceptions = ConfigurationException.class)
+ public void testUpdatedDoesNotAcceptEmptyMatchPolicy() throws ConfigurationException {
+ Properties props = new Properties();
+ props.put(PROPERTY_USERNAME_LOOKUPKEY, "foo");
+ props.put(PROPERTY_USERNAME_MATCH_POLICY, "");
+ props.put(PROPERTY_VERIFY_CERT_VALIDITY, "true");
+
+ createAuthorizationProcessor().updated(props);
+ }
+
+ /**
+ * Tests that updated throws an exception for missing "username lookup key" property.
+ */
+ @Test(groups = { UNIT }, expectedExceptions = ConfigurationException.class)
+ public void testUpdatedDoesNotAcceptEmptyLookupKey() throws ConfigurationException {
+ Properties props = new Properties();
+ props.put(PROPERTY_USERNAME_LOOKUPKEY, "");
+ props.put(PROPERTY_USERNAME_MATCH_POLICY, "foo");
+ props.put(PROPERTY_VERIFY_CERT_VALIDITY, "true");
+
+ createAuthorizationProcessor().updated(props);
+ }
+
+ /**
+ * Tests that updated throws an exception for missing "verify cert validity" property.
+ */
+ @Test(groups = { UNIT }, expectedExceptions = ConfigurationException.class)
+ public void testUpdatedDoesNotAcceptEmptyVerifyCertValidity() throws ConfigurationException {
+ Properties props = new Properties();
+ props.put(PROPERTY_USERNAME_LOOKUPKEY, "foo");
+ props.put(PROPERTY_USERNAME_MATCH_POLICY, "bar");
+ props.put(PROPERTY_VERIFY_CERT_VALIDITY, "");
+
+ createAuthorizationProcessor().updated(props);
+ }
+
+ /**
+ * Tests that updated throws an exception for missing "username match policy" property.
+ */
+ @Test(groups = { UNIT }, expectedExceptions = ConfigurationException.class)
+ public void testUpdatedDoesNotAcceptMissingMatchPolicy() throws ConfigurationException {
+ Properties props = new Properties();
+ props.put(PROPERTY_USERNAME_LOOKUPKEY, "foo");
+ props.put(PROPERTY_VERIFY_CERT_VALIDITY, "true");
+
+ createAuthorizationProcessor().updated(props);
+ }
+
+ /**
+ * Tests that updated throws an exception for missing "user name lookup key" property.
+ */
+ @Test(groups = { UNIT }, expectedExceptions = ConfigurationException.class)
+ public void testUpdatedDoesNotAcceptMissingUsernameLookupKey() throws ConfigurationException {
+ Properties props = new Properties();
+ props.put(PROPERTY_USERNAME_MATCH_POLICY, "foo");
+ props.put(PROPERTY_VERIFY_CERT_VALIDITY, "true");
+
+ createAuthorizationProcessor().updated(props);
+ }
+
+ /**
+ * Tests that updated throws an exception for missing "verify cert validity" property.
+ */
+ @Test(groups = { UNIT }, expectedExceptions = ConfigurationException.class)
+ public void testUpdatedDoesNotAcceptMissingVerifyCertValidity() throws ConfigurationException {
+ Properties props = new Properties();
+ props.put(PROPERTY_USERNAME_LOOKUPKEY, "foo");
+ props.put(PROPERTY_USERNAME_MATCH_POLICY, "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) chain with certificate(s) valid from yesterday until tomorrow.
+ *
+ * @param dns the distinguished names of the certificates in the returned chain.
+ * @return a new chain with {@link X509Certificate}s, never <code>null</code>.
+ */
+ private X509Certificate[] createValidCertificateChainWithDN(String... dns) {
+ X509Certificate[] result = new X509Certificate[dns.length];
+
+ X500Principal signerDN = m_keystore.getCA_DN();
+ KeyPair signerKeyPair = m_keystore.getCA_KeyPair();
+
+ for (int i = 0; i < result.length; i++) {
+ KeyPair certKeyPair = m_keystore.generateKeyPair();
+
+ String alias = String.format("alias%d", i);
+ String dn = dns[i];
+ int idx = result.length - i - 1;
+
+ result[idx] = m_keystore.createCertificate(signerDN, signerKeyPair.getPrivate(), alias, dn, yesterday(), tomorrow(), certKeyPair.getPublic());
+
+ signerDN = result[idx].getSubjectX500Principal();
+ signerKeyPair = certKeyPair;
+ }
+ return result;
+ }
+
+ /**
+ * 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;
+ }
+}
Added: ace/sandbox/marrs/org.apache.ace.authenticationprocessor.clientcert/test/org/apache/ace/authenticationprocessor/clientcert/MemoryKeyStore.java
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.authenticationprocessor.clientcert/test/org/apache/ace/authenticationprocessor/clientcert/MemoryKeyStore.java?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.authenticationprocessor.clientcert/test/org/apache/ace/authenticationprocessor/clientcert/MemoryKeyStore.java (added)
+++ ace/sandbox/marrs/org.apache.ace.authenticationprocessor.clientcert/test/org/apache/ace/authenticationprocessor/clientcert/MemoryKeyStore.java Thu Jul 5 12:09:30 2012
@@ -0,0 +1,139 @@
+/*
+ * 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.PrivateKey;
+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";
+
+ 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);
+ }
+ }
+
+ /**
+ * @return the {@link KeyPair} of the CA, never <code>null</code>.
+ */
+ public KeyPair getCA_KeyPair() {
+ return m_caKey;
+ }
+
+ /**
+ * @return
+ */
+ public X500Principal getCA_DN() {
+ return m_rootCert.getIssuerX500Principal();
+ }
+
+ /**
+ * Generates a new 1024-bit keypair.
+ *
+ * @return a new {@link KeyPair}, never <code>null</code>.
+ */
+ public 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 IllegalArgumentException {
+ return createCertificate(getCA_DN(), m_caKey.getPrivate(), alias, name, before, after, key);
+ }
+
+ /**
+ * @throws IllegalStateException if an internal exception occurs.
+ * @throws IllegalArgumentException if the alias already exists.
+ */
+ public X509Certificate createCertificate(X500Principal issuerDN, PrivateKey issuerKey, String alias, String name, Date notBefore, Date notAfter, PublicKey key) throws IllegalArgumentException {
+ try {
+ m_certGen.reset();
+ m_certGen.setSerialNumber(BigInteger.valueOf(++m_serial));
+ m_certGen.setIssuerDN(issuerDN);
+ m_certGen.setNotBefore(notBefore);
+ m_certGen.setNotAfter(notAfter);
+ m_certGen.setSubjectDN(new X500Principal(name));
+ m_certGen.setPublicKey(key);
+ m_certGen.setSignatureAlgorithm(SIGNATURE_ALGORITHM);
+
+ X509Certificate cert = m_certGen.generate(issuerKey);
+
+ 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());
+ }
+}
Added: ace/sandbox/marrs/org.apache.ace.authenticationprocessor.password/.classpath
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.authenticationprocessor.password/.classpath?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.authenticationprocessor.password/.classpath (added)
+++ ace/sandbox/marrs/org.apache.ace.authenticationprocessor.password/.classpath Thu Jul 5 12:09:30 2012
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="src" output="bin_test" path="test"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry kind="con" path="aQute.bnd.classpath.container"/>
+ <classpathentry kind="con" path="org.testng.TESTNG_CONTAINER"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
Added: ace/sandbox/marrs/org.apache.ace.authenticationprocessor.password/.project
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.authenticationprocessor.password/.project?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.authenticationprocessor.password/.project (added)
+++ ace/sandbox/marrs/org.apache.ace.authenticationprocessor.password/.project Thu Jul 5 12:09:30 2012
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.apache.ace.authenticationprocessor.password</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>bndtools.core.bndbuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ <nature>bndtools.core.bndnature</nature>
+ </natures>
+</projectDescription>
Added: ace/sandbox/marrs/org.apache.ace.authenticationprocessor.password/bnd.bnd
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.authenticationprocessor.password/bnd.bnd?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.authenticationprocessor.password/bnd.bnd (added)
+++ ace/sandbox/marrs/org.apache.ace.authenticationprocessor.password/bnd.bnd Thu Jul 5 12:09:30 2012
@@ -0,0 +1,12 @@
+-buildpath: osgi.core,\
+ osgi.cmpn,\
+ org.apache.ace.util;version=latest,\
+ org.apache.felix.dependencymanager,\
+ org.apache.ace.authentication.api;version=latest,\
+ ../cnf/lib/commons-codec-1.4.jar;version=file,\
+ org.mockito.mockito-all
+Private-Package: org.apache.ace.authenticationprocessor.password,\
+ org.apache.commons.codec,\
+ org.apache.commons.codec.binary,\
+ org.apache.commons.codec.digest
+Bundle-Activator: org.apache.ace.authenticationprocessor.password.Activator
\ No newline at end of file
Added: ace/sandbox/marrs/org.apache.ace.authenticationprocessor.password/build.xml
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.authenticationprocessor.password/build.xml?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.authenticationprocessor.password/build.xml (added)
+++ ace/sandbox/marrs/org.apache.ace.authenticationprocessor.password/build.xml Thu Jul 5 12:09:30 2012
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="project" default="build">
+ <import file="../cnf/build.xml"/>
+</project>
Added: ace/sandbox/marrs/org.apache.ace.authenticationprocessor.password/pom.xml
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.authenticationprocessor.password/pom.xml?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.authenticationprocessor.password/pom.xml (added)
+++ ace/sandbox/marrs/org.apache.ace.authenticationprocessor.password/pom.xml Thu Jul 5 12:09:30 2012
@@ -0,0 +1,87 @@
+<?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>
+ <dependency>
+ <groupId>org.apache.ace</groupId>
+ <artifactId>org.apache.ace.util</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
Added: ace/sandbox/marrs/org.apache.ace.authenticationprocessor.password/src/org/apache/ace/authenticationprocessor/password/Activator.java
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.authenticationprocessor.password/src/org/apache/ace/authenticationprocessor/password/Activator.java?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.authenticationprocessor.password/src/org/apache/ace/authenticationprocessor/password/Activator.java (added)
+++ ace/sandbox/marrs/org.apache.ace.authenticationprocessor.password/src/org/apache/ace/authenticationprocessor/password/Activator.java Thu Jul 5 12:09:30 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
+ }
+}
Added: ace/sandbox/marrs/org.apache.ace.authenticationprocessor.password/src/org/apache/ace/authenticationprocessor/password/PasswordAuthenticationProcessor.java
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.authenticationprocessor.password/src/org/apache/ace/authenticationprocessor/password/PasswordAuthenticationProcessor.java?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.authenticationprocessor.password/src/org/apache/ace/authenticationprocessor/password/PasswordAuthenticationProcessor.java (added)
+++ ace/sandbox/marrs/org.apache.ace.authenticationprocessor.password/src/org/apache/ace/authenticationprocessor/password/PasswordAuthenticationProcessor.java Thu Jul 5 12:09:30 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
+ }
+}
Added: ace/sandbox/marrs/org.apache.ace.authenticationprocessor.password/test/org/apache/ace/authenticationprocessor/password/PasswordAuthenticationProcessorTest.java
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.authenticationprocessor.password/test/org/apache/ace/authenticationprocessor/password/PasswordAuthenticationProcessorTest.java?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.authenticationprocessor.password/test/org/apache/ace/authenticationprocessor/password/PasswordAuthenticationProcessorTest.java (added)
+++ ace/sandbox/marrs/org.apache.ace.authenticationprocessor.password/test/org/apache/ace/authenticationprocessor/password/PasswordAuthenticationProcessorTest.java Thu Jul 5 12:09:30 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.PROPERTY_KEY_PASSWORD;
+import static org.apache.ace.authenticationprocessor.password.PasswordAuthenticationProcessor.PROPERTY_KEY_USERNAME;
+import static org.apache.ace.authenticationprocessor.password.PasswordAuthenticationProcessor.PROPERTY_PASSWORD_HASHMETHOD;
+import static org.apache.ace.test.utils.TestUtils.UNIT;
+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.mockito.Mockito;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.useradmin.User;
+import org.osgi.service.useradmin.UserAdmin;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+/**
+ * Test cases for {@link PasswordAuthenticationProcessor}.
+ */
+public class PasswordAuthenticationProcessorTest {
+
+ private UserAdmin m_userAdmin;
+
+ @BeforeMethod(alwaysRun = true)
+ public void setUp() {
+ m_userAdmin = mock(UserAdmin.class);
+ Mockito.reset(m_userAdmin);
+ }
+
+ /**
+ * Tests that authenticating with a empty username will yield null.
+ */
+ @Test(groups = { UNIT })
+ public void testAuthenticateEmptyUserNameYieldsNull() {
+ User result = new PasswordAuthenticationProcessor().authenticate(m_userAdmin, "", "secret");
+ assert result == null : "Expected no valid user to be returned!";
+ }
+
+ /**
+ * Tests that authenticating a known user with an invalid password will yield null.
+ */
+ @Test(groups = { UNIT })
+ 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");
+ assert result == null : "Expected no valid user to be returned!";
+ }
+
+ /**
+ * Tests that authenticating a known user with a correct password will not yield null.
+ */
+ @Test(groups = { UNIT })
+ 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");
+ assert result != null : "Expected a valid user to be returned!";
+
+ assert "bob".equals(user.getName()) : "Expected bob to be returned!";
+ }
+
+ /**
+ * Tests that authenticating with a null password will yield null.
+ */
+ @Test(groups = { UNIT })
+ public void testAuthenticateNullPasswordYieldsNull() {
+ User result = new PasswordAuthenticationProcessor().authenticate(m_userAdmin, "bob", null);
+ assert result == null : "Expected no valid user to be returned!";
+ }
+
+ /**
+ * Tests that authenticating with a null username will yield null.
+ */
+ @Test(groups = { UNIT })
+ public void testAuthenticateNullUserNameYieldsNull() {
+ User result = new PasswordAuthenticationProcessor().authenticate(m_userAdmin, null, "secret");
+ assert result == null : "Expected no valid user to be returned!";
+ }
+
+ /**
+ * Tests that a class cast exception is thrown for invalid context when calling authenticate.
+ */
+ @Test(groups = { UNIT }, expectedExceptions = ClassCastException.class)
+ public void testAuthenticateThrowsClassCastForInvalidContext() {
+ new PasswordAuthenticationProcessor().authenticate(m_userAdmin, new Object(), "foo");
+ }
+
+ /**
+ * Tests that authenticating an unknown user will yield null.
+ */
+ @Test(groups = { UNIT })
+ public void testAuthenticateUnknownUserYieldsNull() {
+ User result = new PasswordAuthenticationProcessor().authenticate(m_userAdmin, "alice", "secret");
+ assert result == null : "Expected no valid user to be returned!";
+ }
+
+ /**
+ * Tests that canHandle yields true for string and byte array.
+ */
+ @Test(groups = { UNIT })
+ public void testCanHandleDoesAcceptStringAndByteArray() {
+ assert new PasswordAuthenticationProcessor().canHandle("foo", "bar".getBytes()) : "Expected the processor to handle a byte array!";
+ }
+
+ /**
+ * Tests that canHandle yields true for two strings.
+ */
+ @Test(groups = { UNIT })
+ public void testCanHandleDoesAcceptTwoStrings() {
+ assert new PasswordAuthenticationProcessor().canHandle("foo", "bar") : "Expected the processor to handle a string!";
+ }
+
+ /**
+ * Tests that canHandle throws an {@link IllegalArgumentException} for an empty context.
+ */
+ @Test(groups = { UNIT }, expectedExceptions = IllegalArgumentException.class)
+ public void testCanHandleDoesNotAcceptEmptyArray() {
+ new PasswordAuthenticationProcessor().canHandle(new Object[0]);
+ }
+
+ /**
+ * Tests that canHandle throws an {@link IllegalArgumentException} for a null context.
+ */
+ @Test(groups = { UNIT }, expectedExceptions = IllegalArgumentException.class)
+ public void testCanHandleDoesNotAcceptNull() {
+ new PasswordAuthenticationProcessor().canHandle((Object[]) null);
+ }
+
+ /**
+ * Tests that canHandle yields false for too few arguments.
+ */
+ @Test(groups = { UNIT })
+ public void testCanHandleDoesNotAcceptSingleArgument() {
+ assert new PasswordAuthenticationProcessor().canHandle(new Object()) == false : "Expected the processor to NOT handle any object!";
+ }
+
+ /**
+ * Tests that canHandle yields false for a string and other object.
+ */
+ @Test(groups = { UNIT })
+ public void testCanHandleDoesNotAcceptStringAndOtherObject() {
+ assert new PasswordAuthenticationProcessor().canHandle("foo", new Object()) == false : "Expected the processor to NOT handle any object!";
+ }
+
+ /**
+ * Tests that canHandle yields false for any object other than {@link HttpServletRequest}.
+ */
+ @Test(groups = { UNIT })
+ public void testCanHandleDoesNotAcceptWrongTypes() {
+ assert new PasswordAuthenticationProcessor().canHandle(new Object(), new Object()) == false : "Expected the processor to NOT handle any object!";
+ }
+
+ /**
+ * Tests that updated does not throw an exception for a correct configuration.
+ */
+ @Test(groups = { UNIT })
+ 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");
+ assert result != null : "Expected a valid user to be returned!";
+
+ assert "bob".equals(user.getName()) : "Expected bob to be returned!";
+ }
+
+ /**
+ * Tests that updated throws an exception for missing "key.password" property.
+ */
+ @Test(groups = { UNIT }, expectedExceptions = 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(groups = { UNIT }, expectedExceptions = 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(groups = { UNIT }, expectedExceptions = 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(groups = { UNIT }, expectedExceptions = 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(groups = { UNIT }, expectedExceptions = 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(groups = { UNIT }, expectedExceptions = 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);
+ }
+}
Added: ace/sandbox/marrs/org.apache.ace.builder/pom.xml
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.builder/pom.xml?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.builder/pom.xml (added)
+++ ace/sandbox/marrs/org.apache.ace.builder/pom.xml Thu Jul 5 12:09:30 2012
@@ -0,0 +1,49 @@
+<?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.builder</artifactId>
+
+ <name>Apache ACE :: Builders</name>
+ <description>Collection of builders that can be used in various contexts.</description>
+ <packaging>jar</packaging>
+
+ <scm>
+ <connection>scm:svn:http://svn.apache.org/repos/asf/ace/trunk/ace-builder</connection>
+ <developerConnection>scm:svn:https://svn.apache.org/repos/asf/ace/trunk/ace-builder</developerConnection>
+ <url>http://svn.apache.org/repos/asf/ace/trunk/ace-builder</url>
+ </scm>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.ace</groupId>
+ <artifactId>org.apache.ace.util</artifactId>
+ </dependency>
+ </dependencies>
+</project>
Added: ace/sandbox/marrs/org.apache.ace.builder/src/org/apache/ace/builder/ArtifactData.java
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.builder/src/org/apache/ace/builder/ArtifactData.java?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.builder/src/org/apache/ace/builder/ArtifactData.java (added)
+++ ace/sandbox/marrs/org.apache.ace.builder/src/org/apache/ace/builder/ArtifactData.java Thu Jul 5 12:09:30 2012
@@ -0,0 +1,98 @@
+/*
+ * 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.builder;
+
+import java.net.URL;
+
+public class ArtifactData {
+ private final URL m_url;
+ private boolean m_isBundle;
+ private String m_filename;
+ private String m_bundleSymbolicName;
+ private String m_bundleVersion;
+ private boolean m_isCustomizer;
+ private String m_processorPID;
+
+ private ArtifactData(URL url, String filename) {
+ m_url = url;
+ m_filename = filename;
+ }
+
+ public static ArtifactData createBundle(URL url, String filename, String bundleSymbolicName, String bundleVersion) {
+ ArtifactData data = new ArtifactData(url, filename);
+ data.setBundleMetadata(bundleSymbolicName, bundleVersion);
+ return data;
+ }
+
+ public static ArtifactData createResourceProcessor(URL url, String filename, String bundleSymbolicName, String bundleVersion, String processorPID) {
+ ArtifactData data = new ArtifactData(url, filename);
+ data.setBundleMetadata(bundleSymbolicName, bundleVersion);
+ data.setResourceProcessor(processorPID);
+ return data;
+ }
+
+ public static ArtifactData createArtifact(URL url, String filename, String processorPID) {
+ ArtifactData data = new ArtifactData(url, filename);
+ data.setArtifactResourceProcessor(processorPID);
+ return data;
+ }
+
+ public URL getURL() {
+ return m_url;
+ }
+
+ public boolean isBundle() {
+ return m_isBundle;
+ }
+
+ public String getFilename() {
+ return m_filename;
+ }
+
+ public String getSymbolicName() {
+ return m_bundleSymbolicName;
+ }
+
+ public String getVersion() {
+ return m_bundleVersion;
+ }
+
+ public boolean isCustomizer() {
+ return m_isCustomizer;
+ }
+
+ public String getProcessorPid() {
+ return m_processorPID;
+ }
+
+ private void setBundleMetadata(String bundleSymbolicName, String bundleVersion) {
+ m_isBundle = true;
+ m_bundleSymbolicName = bundleSymbolicName;
+ m_bundleVersion = bundleVersion;
+ }
+
+ private void setResourceProcessor(String processorPID) {
+ m_isCustomizer = true;
+ m_processorPID = processorPID;
+ }
+
+ private void setArtifactResourceProcessor(String processorPID) {
+ m_processorPID = processorPID;
+ }
+}