You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by tb...@apache.org on 2006/12/12 16:24:14 UTC

svn commit: r486187 [38/49] - in /directory/trunks/triplesec: ./ admin-api/ admin-api/src/ admin-api/src/main/ admin-api/src/main/java/ admin-api/src/main/java/org/ admin-api/src/main/java/org/safehaus/ admin-api/src/main/java/org/safehaus/triplesec/ a...

Added: directory/trunks/triplesec/verifier/src/test/java/org/safehaus/triplesec/verifier/hotp/HotpSamVerifierITest.java
URL: http://svn.apache.org/viewvc/directory/trunks/triplesec/verifier/src/test/java/org/safehaus/triplesec/verifier/hotp/HotpSamVerifierITest.java?view=auto&rev=486187
==============================================================================
--- directory/trunks/triplesec/verifier/src/test/java/org/safehaus/triplesec/verifier/hotp/HotpSamVerifierITest.java (added)
+++ directory/trunks/triplesec/verifier/src/test/java/org/safehaus/triplesec/verifier/hotp/HotpSamVerifierITest.java Tue Dec 12 07:23:31 2006
@@ -0,0 +1,361 @@
+/*
+ *  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.safehaus.triplesec.verifier.hotp;
+
+
+import java.io.IOException;
+import java.io.File;
+import java.util.*;
+
+import javax.naming.NamingException;
+import javax.naming.Context;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.InitialDirContext;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttributes;
+import javax.security.auth.kerberos.KerberosKey;
+import javax.security.auth.kerberos.KerberosPrincipal;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.directory.server.kerberos.shared.crypto.encryption.EncryptionEngine;
+import org.apache.directory.server.kerberos.shared.crypto.encryption.EncryptionEngineFactory;
+import org.apache.directory.server.kerberos.shared.crypto.encryption.EncryptionType;
+import org.apache.directory.server.kerberos.shared.exceptions.KerberosException;
+import org.apache.directory.server.kerberos.shared.io.encoder.EncryptedDataEncoder;
+import org.apache.directory.server.kerberos.shared.io.encoder.EncryptedTimestampEncoder;
+import org.apache.directory.server.kerberos.shared.messages.value.EncryptedData;
+import org.apache.directory.server.kerberos.shared.messages.value.EncryptedTimeStamp;
+import org.apache.directory.server.kerberos.shared.messages.value.EncryptedTimeStampModifier;
+import org.apache.directory.server.kerberos.shared.messages.value.EncryptionKey;
+import org.apache.directory.server.kerberos.shared.messages.value.KerberosTime;
+import org.apache.directory.server.kerberos.sam.SamException;
+import org.apache.directory.server.kerberos.sam.TimestampChecker;
+import org.apache.directory.shared.ldap.message.LockableAttributeImpl;
+import org.apache.directory.shared.ldap.message.LockableAttributesImpl;
+import org.apache.directory.server.core.configuration.Configuration;
+import org.apache.directory.server.core.configuration.MutablePartitionConfiguration;
+import org.apache.directory.server.core.configuration.MutableStartupConfiguration;
+import org.apache.directory.server.core.configuration.ShutdownConfiguration;
+import org.apache.directory.server.core.schema.bootstrap.ApacheSchema;
+import org.apache.directory.server.core.schema.bootstrap.CoreSchema;
+import org.apache.directory.server.core.schema.bootstrap.CosineSchema;
+import org.apache.directory.server.core.schema.bootstrap.InetorgpersonSchema;
+import org.apache.directory.server.core.schema.bootstrap.Krb5kdcSchema;
+import org.apache.directory.server.core.schema.bootstrap.SystemSchema;
+import org.apache.directory.server.protocol.shared.store.Krb5KdcEntryFilter;
+import org.apache.directory.server.protocol.shared.store.LdifFileLoader;
+import org.safehaus.otp.Hotp;
+import org.safehaus.otp.HotpErrorConstants;
+import org.safehaus.otp.ResynchParameters;
+import org.safehaus.profile.ServerProfile;
+import org.safehaus.triplesec.store.DefaultServerProfileStore;
+import org.safehaus.triplesec.store.ServerProfileStore;
+import org.safehaus.triplesec.store.ProfileStateFactory;
+import org.safehaus.triplesec.store.ProfileObjectFactory;
+import org.safehaus.triplesec.store.schema.SafehausSchema;
+
+
+/**
+ * Test cases for the HOTP SAM verifier class.
+ *
+ * @version $Rev$
+ */
+public class HotpSamVerifierITest extends TestCase
+{
+    DirContext userContext;
+    DefaultServerProfileStore store;
+
+    /**
+     * Creates the hotp verifier test class.
+     */
+    public HotpSamVerifierITest()
+    {
+        super();
+    }
+
+
+    protected void setUp() throws Exception
+    {
+        File workingDirectory = new File ( System.getProperty( "workingDirectory" ) ); 
+        if ( ! workingDirectory.exists() ) 
+        {
+            workingDirectory.mkdirs();
+        }
+        FileUtils.forceDelete( workingDirectory );
+        
+        MutableStartupConfiguration config = new MutableStartupConfiguration();
+        config.setWorkingDirectory( workingDirectory );
+        config.setShutdownHookEnabled( false );
+        MutablePartitionConfiguration partConfig = new MutablePartitionConfiguration();
+        partConfig.setName( "example" );
+
+        HashSet indices = new HashSet();
+        indices.add( "dc" );
+        indices.add( "ou" );
+        indices.add( "objectClass" );
+        indices.add( "krb5PrincipalName" );
+        indices.add( "uid" );
+        partConfig.setIndexedAttributes( indices );
+
+        partConfig.setSuffix( "dc=example,dc=com" );
+
+        LockableAttributesImpl attrs = new LockableAttributesImpl();
+        LockableAttributeImpl attr = new LockableAttributeImpl( "objectClass" );
+        attr.add( "top" );
+        attr.add( "domain" );
+        attrs.put( attr );
+        attrs.put( "dc", "example" );
+        partConfig.setContextEntry( attrs );
+
+        Set schemas = new HashSet();
+        schemas.add( new SystemSchema() );
+        schemas.add( new SafehausSchema() );
+        schemas.add( new ApacheSchema() );
+        schemas.add( new CoreSchema() );
+        schemas.add( new CosineSchema() );
+        schemas.add( new InetorgpersonSchema() );
+        schemas.add( new Krb5kdcSchema() );
+        config.setBootstrapSchemas( schemas );
+        config.setContextPartitionConfigurations( Collections.singleton( partConfig ) );
+
+        Hashtable env = new Hashtable();
+        env.put( Context.INITIAL_CONTEXT_FACTORY, "org.apache.directory.server.core.jndi.CoreContextFactory" );
+        env.put( Context.PROVIDER_URL, "dc=example,dc=com" );
+        env.put( Context.SECURITY_PRINCIPAL, "uid=admin,ou=system" );
+        env.put( Context.SECURITY_AUTHENTICATION, "simple" );
+        env.put( Context.SECURITY_CREDENTIALS, "secret" );
+        env.put( Configuration.JNDI_KEY, config );
+        env.put( Context.STATE_FACTORIES, ProfileStateFactory.class.getName() );
+        env.put( Context.OBJECT_FACTORIES, ProfileObjectFactory.class.getName() );
+
+        userContext = new InitialDirContext( env );
+        try
+        {
+            userContext = ( DirContext ) userContext.lookup( "ou=users" );
+        }
+        catch ( NamingException e )
+        {
+            Attributes users = new BasicAttributes( "objectClass", "top", true );
+            users.get( "objectClass" ).add( "organizationalUnit" );
+            attrs.put( "ou", "users" );
+            userContext = userContext.createSubcontext( "ou=users", attrs );
+        }
+
+        store = new DefaultServerProfileStore( userContext );
+        store.init();
+
+        List filters = Collections.singletonList( new Krb5KdcEntryFilter() );
+        LdifFileLoader loader = new LdifFileLoader( userContext, new File( "safehaus.ldif" ), filters, getClass().getClassLoader() );
+        loader.execute();
+    }
+
+
+    protected void tearDown() throws Exception
+    {
+        userContext.close();
+        ShutdownConfiguration config = new ShutdownConfiguration();
+        Hashtable env = new Hashtable();
+        env.put( Context.INITIAL_CONTEXT_FACTORY, "org.apache.directory.server.core.jndi.CoreContextFactory" );
+        env.put( Context.PROVIDER_URL, "dc=example,dc=com" );
+        env.put( Context.SECURITY_PRINCIPAL, "uid=admin,ou=system" );
+        env.put( Context.SECURITY_AUTHENTICATION, "simple" );
+        env.put( Context.SECURITY_CREDENTIALS, "secret" );
+        env.put( Configuration.JNDI_KEY, config );
+        env.put( Context.STATE_FACTORIES, ProfileStateFactory.class.getName() );
+        env.put( Context.OBJECT_FACTORIES, ProfileObjectFactory.class.getName() );
+        new InitialDirContext( env );
+        
+        userContext = null;
+        store = null;
+    }
+
+
+    /**
+     * Generates the encrypted time stamp using a KerberosKey to mimic clients
+     *
+     * @param kerberosKey the kerberos key (from hotp value)
+     * @param time the kerberos time for timestamp
+     * @return the encrypted time stamp
+     */
+    private byte[] generateSad( KerberosKey kerberosKey, KerberosTime time )
+    {
+        EncryptionType keyType = EncryptionType.getTypeByOrdinal( kerberosKey.getKeyType() );
+        EncryptionKey key = new EncryptionKey( keyType, kerberosKey.getEncoded() );
+        byte[] sad = null;
+
+        try
+        {
+            // Create the Timestamp
+            EncryptedTimeStampModifier modifier = new EncryptedTimeStampModifier();
+            modifier.setKerberosTime( time );
+            EncryptedTimeStamp timeStamp = modifier.getEncryptedTimestamp();
+
+            // Encode the Timestamp into ASN.1
+            EncryptedTimestampEncoder encoder = new EncryptedTimestampEncoder();
+            byte[] timeBytes = encoder.encode( timeStamp );
+
+            // Encrypt the Timestamp
+            EncryptionEngine engine = EncryptionEngineFactory.getEncryptionEngineFor( key );
+            EncryptedData encryptedData = engine.getEncryptedData( key, timeBytes );
+
+            // Encode the EncryptedData
+            sad = EncryptedDataEncoder.encode( encryptedData );
+        }
+        catch (IOException ioe)
+        {
+            ioe.printStackTrace();
+        }
+        catch (KerberosException ke)
+        {
+            ke.printStackTrace();
+        }
+
+        return sad;
+    }
+
+    
+    /**
+     * Tests that accounts lock out and ones that are locked from start do not
+     * succeed.
+     */
+    public void testLockedOut() throws SamException, IOException, NamingException
+    {
+        DefaultHotpSamVerifier samVerifierDefault = new DefaultHotpSamVerifier();
+        samVerifierDefault.setUserContext( userContext );
+        samVerifierDefault.setIntegrityChecker( new TimestampChecker() );
+        samVerifierDefault.startup();
+
+        assertNotNull( samVerifierDefault );
+        KerberosPrincipal lockedout = new KerberosPrincipal( "lockedout@EXAMPLE.COM" );
+
+        try
+        {
+            char[] hotp = "123456".toCharArray();
+            KerberosKey key = new KerberosKey( lockedout, hotp, "DES" );
+            byte[] sad = generateSad( key, new KerberosTime() );
+            samVerifierDefault.verify( lockedout, sad );
+            fail( "should not get here due to exception" );
+        }
+        catch ( SamException e )
+        {
+//            assertEquals( HotpErrorConstants.LOCKEDOUT_MSG, e.getMessage() );
+        }
+
+        // --------------------------------------------------------------------
+        // Ok let's now try to lock out an unlocked existing account: akarauslu
+        // --------------------------------------------------------------------
+
+        KerberosPrincipal akarasulu = new KerberosPrincipal( "akarasulu@EXAMPLE.COM" );
+        int ii = 0;
+        final int limit = ResynchParameters.DEFAULTS.getLockoutCount();
+
+        for (; ii < limit; ii++ )
+        {
+            char[] hotp = "123456".toCharArray();
+            KerberosKey key = new KerberosKey( akarasulu, hotp, "DES" );
+            byte[] sad = generateSad( key, new KerberosTime() );
+
+            try
+            {
+                samVerifierDefault.verify( akarasulu, sad );
+            }
+            catch( SamException e )
+            {
+                assertEquals( HotpErrorConstants.HOTPAUTH_FAILURE_MSG, e.getMessage() );
+            }
+        }
+
+        assertEquals( limit, ii );
+
+        // this next attempt with a bad hotp value should take us over the limit
+
+        try
+        {
+            char[] hotp = "123456".toCharArray();
+            KerberosKey key = new KerberosKey( akarasulu, hotp, "DES" );
+            byte[] sad = generateSad( key, new KerberosTime() );
+            samVerifierDefault.verify( akarasulu, sad );
+            fail( "should not get here due to lockout" );
+        }
+        catch ( SamException e )
+        {
+            assertEquals( limit, ii );
+            assertEquals( HotpErrorConstants.LOCKEDOUT_MSG, e.getMessage() );
+        }
+    }
+
+
+    public void testResynch() throws SamException, NamingException, IOException
+    {
+        DefaultHotpSamVerifier samVerifierDefault = new DefaultHotpSamVerifier();
+        samVerifierDefault.setUserContext( userContext );
+        samVerifierDefault.setIntegrityChecker( new TimestampChecker() );
+        samVerifierDefault.startup();
+        ServerProfileStore s = samVerifierDefault.getStore();
+        KerberosPrincipal principal = new KerberosPrincipal( "akarasulu@EXAMPLE.COM" );
+        ServerProfile p = s.getProfile( principal );
+        long factor = p.getFactor() + 5;
+        byte[] secret = p.getSecret();
+        assertNotNull( samVerifierDefault );
+
+        try
+        {
+            char[] hotp = Hotp.generate( secret, factor, DefaultHotpSamVerifier.HOTP_SIZE ).toCharArray();
+            KerberosKey key = new KerberosKey( principal, hotp, "DES" );
+            byte[] sad = generateSad( key, new KerberosTime() );
+            samVerifierDefault.verify( principal, sad );
+            fail( "should not get here due to resynch" );
+        }
+        catch ( SamException e )
+        {
+            assertEquals( HotpErrorConstants.RESYNCH_STARTING_MSG, e.getMessage() );
+        }
+
+        char[] hotp = Hotp.generate( secret, factor + 1, DefaultHotpSamVerifier.HOTP_SIZE ).toCharArray();
+        KerberosKey key = new KerberosKey( principal, hotp, "DES" );
+        byte[] sad = generateSad( key, new KerberosTime() );
+        assertNotNull( samVerifierDefault.verify( principal, sad ) );
+    }
+
+
+    public void testNormal() throws SamException, NamingException, IOException
+    {
+        DefaultHotpSamVerifier samVerifierDefault = new DefaultHotpSamVerifier();
+        samVerifierDefault.setUserContext( userContext );
+        samVerifierDefault.setIntegrityChecker( new TimestampChecker() );
+        samVerifierDefault.startup();
+        assertNotNull( samVerifierDefault );
+
+        ServerProfileStore s = samVerifierDefault.getStore();
+        KerberosPrincipal principal = new KerberosPrincipal( "akarasulu@EXAMPLE.COM" );
+        ServerProfile p = s.getProfile( principal );
+        long factor = p.getFactor();
+        byte[] secret = p.getSecret();
+        for ( int ii = 0; ii < 100; ii++ )
+        {
+            char[] hotp = Hotp.generate( secret, factor + ii, DefaultHotpSamVerifier.HOTP_SIZE ).toCharArray();
+            KerberosKey key = new KerberosKey( principal, hotp, "DES" );
+            byte[] sad = generateSad( key, new KerberosTime() );
+            assertNotNull( samVerifierDefault.verify( principal, sad ) );
+        }
+    }
+}

Added: directory/trunks/triplesec/verifier/src/test/resources/log4j.properties
URL: http://svn.apache.org/viewvc/directory/trunks/triplesec/verifier/src/test/resources/log4j.properties?view=auto&rev=486187
==============================================================================
--- directory/trunks/triplesec/verifier/src/test/resources/log4j.properties (added)
+++ directory/trunks/triplesec/verifier/src/test/resources/log4j.properties Tue Dec 12 07:23:31 2006
@@ -0,0 +1,6 @@
+log4j.rootCategory=ERROR, stdout
+
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=[%d{HH:mm:ss}] %p [%c] - %m%n
+

Added: directory/trunks/triplesec/verifier/src/test/resources/safehaus.ldif
URL: http://svn.apache.org/viewvc/directory/trunks/triplesec/verifier/src/test/resources/safehaus.ldif?view=auto&rev=486187
==============================================================================
--- directory/trunks/triplesec/verifier/src/test/resources/safehaus.ldif (added)
+++ directory/trunks/triplesec/verifier/src/test/resources/safehaus.ldif Tue Dec 12 07:23:31 2006
@@ -0,0 +1,203 @@
+# -------------------------------------------------------------------
+#
+#  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. 
+#  
+#
+# EXAMPLE.COM is freely and reserved for testing according to this RFC:
+#
+# http://www.rfc-editor.org/rfc/rfc2606.txt
+#
+# -------------------------------------------------------------------
+
+dn: ou=Users, dc=example, dc=com
+objectclass: top
+objectclass: organizationalunit
+ou: Users
+
+dn: uid=akarasulu, ou=Users, dc=example,dc=com
+cn: Alex Karasulu
+sn: Karasulu
+givenname: Alex
+objectclass: top
+objectclass: uidObject
+objectclass: person
+objectclass: organizationalPerson
+objectclass: extensibleObject
+objectclass: inetOrgPerson
+objectclass: krb5Principal
+objectclass: krb5KDCEntry
+objectclass: safehausProfile
+ou: Directory
+ou: Users
+l: Jacksonville
+uid: akarasulu
+krb5PrincipalName: akarasulu@EXAMPLE.COM
+krb5KeyVersionNumber: 0
+mail: akarasulu@example.com
+telephonenumber: +1 904 982 6882
+facsimiletelephonenumber: +1 904 982 6883
+roomnumber: 666
+apacheSamType: 7
+safehausUid: akarasulu
+safehausRealm: EXAMPLE.COM
+safehausLabel: example realm
+safehausFactor: 27304238
+safehausSecret:: aaaabbbbccccdddd
+safehausFailuresInEpoch: 0
+safehausResynchCount: -1
+safehausInfo: test account
+safehausTokenPin: 1234
+safehausNotifyBy: sms
+userpassword: maxwell
+
+dn: uid=lockedout, ou=Users, dc=example,dc=com
+cn: Risky
+sn: Lockedout
+givenname: Unlucky
+objectclass: top
+objectclass: uidObject
+objectclass: person
+objectclass: organizationalPerson
+objectclass: inetOrgPerson
+objectclass: krb5Principal
+objectclass: krb5KDCEntry
+objectclass: safehausProfile
+ou: Directory
+ou: Users
+l: DummyCity
+uid: lockedout
+krb5PrincipalName: lockedout@EXAMPLE.COM
+krb5KeyVersionNumber: 0
+mail: lockedout@example.com
+telephonenumber: +1 904 982 6882
+facsimiletelephonenumber: +1 904 982 6883
+roomnumber: 699
+safehausUid: lockedout
+safehausRealm: EXAMPLE.COM
+safehausLabel: example realm
+safehausFactor: 101347012
+safehausSecret:: (Q-H23BQ#SDsdkf3o&81923r
+safehausFailuresInEpoch: 20
+safehausResynchCount: -1
+safehausInfo: unlucky account
+safehausTokenPin: 1234
+safehausNotifyBy: sms
+userpassword: asdfasdf
+
+dn: uid=erodriguez, ou=Users, dc=example,dc=com
+cn: Enrique Rodriguez
+sn: Rodriguez
+givenname: Enrique
+objectclass: top
+objectclass: uidObject
+objectclass: person
+objectclass: organizationalPerson
+objectclass: inetOrgPerson
+objectclass: krb5Principal
+objectclass: krb5KDCEntry
+objectclass: safehausProfile
+ou: Directory
+ou: Users
+l: Boston
+uid: erodriguez
+krb5PrincipalName: erodriguez@EXAMPLE.COM
+krb5KeyVersionNumber: 0
+mail: erodriguez@example.com
+telephonenumber: +1 408 555 9187
+facsimiletelephonenumber: +1 408 555 8473
+roomnumber: 667
+safehausUid: erodriguez
+safehausRealm: EXAMPLE.COM
+safehausLabel: example realm
+safehausFactor: 917483720127847
+safehausSecret:: xcJqp45S80e8fahs&@rq1I98awg8)^*
+safehausFailuresInEpoch: 0
+safehausResynchCount: -1
+safehausInfo: test account
+safehausTokenPin: 1234
+safehausNotifyBy: sms
+userpassword: noices
+
+dn: uid=krbtgt, ou=Users, dc=example,dc=com
+cn: Kerberos Server
+sn: Server
+givenname: Kerberos
+objectclass: top
+objectclass: uidObject
+objectclass: person
+objectclass: organizationalPerson
+objectclass: inetOrgPerson
+objectclass: krb5Principal
+objectclass: krb5KDCEntry
+ou: Directory
+ou: Users
+l: Boston
+uid: krbtgt
+krb5PrincipalName: krbtgt/EXAMPLE.COM@EXAMPLE.COM
+krb5KeyVersionNumber: 0
+mail: erodriguez@example.com
+telephonenumber: +1 408 555 9187
+facsimiletelephonenumber: +1 408 555 8473
+roomnumber: 667
+userpassword: kahuna
+
+dn: uid=hostssh, ou=Users, dc=example,dc=com
+cn: SSH Service
+sn: Service
+givenname: SSH
+objectclass: top
+objectclass: uidObject
+objectclass: person
+objectclass: organizationalPerson
+objectclass: inetOrgPerson
+objectclass: krb5Principal
+objectclass: krb5KDCEntry
+ou: Directory
+ou: Users
+l: Boston
+uid: hostssh
+krb5PrincipalName: host/www.example.com@EXAMPLE.COM
+krb5KeyVersionNumber: 0
+mail: erodriguez@example.com
+telephonenumber: +1 408 555 9187
+facsimiletelephonenumber: +1 408 555 8473
+roomnumber: 667
+userpassword: randall
+
+dn: uid=hostssh2, ou=Users, dc=example,dc=com
+cn: SSH Service
+sn: Service
+givenname: SSH
+objectclass: top
+objectclass: person
+objectclass: organizationalPerson
+objectclass: inetOrgPerson
+objectclass: krb5Principal
+objectclass: krb5KDCEntry
+ou: Directory
+ou: Users
+l: Boston
+uid: hostssh
+krb5PrincipalName: host/kerberos.example.com@EXAMPLE.COM
+krb5KeyVersionNumber: 0
+mail: erodriguez@example.com
+telephonenumber: +1 408 555 9187
+facsimiletelephonenumber: +1 408 555 8473
+roomnumber: 667
+userpassword: randall
+

Added: directory/trunks/triplesec/webapp-activation/pom.xml
URL: http://svn.apache.org/viewvc/directory/trunks/triplesec/webapp-activation/pom.xml?view=auto&rev=486187
==============================================================================
--- directory/trunks/triplesec/webapp-activation/pom.xml (added)
+++ directory/trunks/triplesec/webapp-activation/pom.xml Tue Dec 12 07:23:31 2006
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+  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. 
+-->
+<project>
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.safehaus.triplesec</groupId>
+    <artifactId>build</artifactId>
+    <version>1.0-SNAPSHOT</version>
+  </parent>
+  <artifactId>triplesec-webapp-activation</artifactId>
+  <name>Triplesec Webapp for Activation of Accounts</name>
+  <packaging>war</packaging>  
+
+  <dependencies>
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>triplesec-utils-hauskeys</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>triplesec-otp</artifactId>
+      <version>${project.version}</version>
+      <scope>provided</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>triplesec-sms</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>triplesec-configuration</artifactId>
+      <version>${project.version}</version>
+      <scope>provided</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>javax.activation</groupId>
+      <artifactId>activation</artifactId>
+      <version>1.1</version>
+      <scope>provided</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>javax.mail</groupId>
+      <artifactId>mail</artifactId>
+      <version>1.4</version>
+      <scope>provided</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>ant</groupId>
+      <artifactId>ant</artifactId>
+      <version>1.6.5</version>
+      <scope>provided</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>commons-lang</groupId>
+      <artifactId>commons-lang</artifactId>
+      <version>2.0</version>
+      <scope>provided</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>commons-httpclient</groupId>
+      <artifactId>commons-httpclient</artifactId>
+      <version>2.0.2</version>
+      <scope>provided</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>tomcat</groupId>
+      <artifactId>servlet-api</artifactId>
+      <version>5.5.12</version>
+      <scope>provided</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>nlog4j</artifactId>
+      <version>1.2.25</version>
+      <scope>provided</scope>
+    </dependency>
+
+  </dependencies>
+  <build>
+    <finalName>triplesec-webapp-activation</finalName>
+  </build>
+</project>

Added: directory/trunks/triplesec/webapp-activation/src/main/java/org/safehaus/triplesec/activation/ActivateAccountFilter.java
URL: http://svn.apache.org/viewvc/directory/trunks/triplesec/webapp-activation/src/main/java/org/safehaus/triplesec/activation/ActivateAccountFilter.java?view=auto&rev=486187
==============================================================================
--- directory/trunks/triplesec/webapp-activation/src/main/java/org/safehaus/triplesec/activation/ActivateAccountFilter.java (added)
+++ directory/trunks/triplesec/webapp-activation/src/main/java/org/safehaus/triplesec/activation/ActivateAccountFilter.java Tue Dec 12 07:23:31 2006
@@ -0,0 +1,628 @@
+/*
+ *  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.safehaus.triplesec.activation;
+
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Hashtable;
+
+import javax.naming.Context;
+import javax.naming.NamingException;
+import javax.naming.directory.BasicAttributes;
+import javax.naming.directory.DirContext;
+import javax.naming.ldap.InitialLdapContext;
+import javax.naming.ldap.LdapContext;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.safehaus.otp.HotpAttributes;
+import org.safehaus.otp.HotpAttributesCipher;
+import org.safehaus.triplesec.configuration.ActivationConfiguration;
+import org.safehaus.triplesec.configuration.SmsConfiguration;
+import org.safehaus.triplesec.configuration.SmtpConfiguration;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * The account activation filter activates user accounts.
+ *
+ * @author <a href="mailto:akarasulu@safehaus.org">Alex Karasulu</a>
+ * @version $Rev$
+ */
+public class ActivateAccountFilter implements Filter, Runnable
+{
+	private static final int MIN_NOTIFICATION_AGE = 15000;
+    private static final String SMTP_PASSWORD_PARAM = "smtpPassword";
+    private static final String SMTP_USERNAME_PARAM = "smtpUsername";
+    private static final String SMTP_SUBJECT_PARAM = "smtpSubject";
+    private static final String SMTP_FROM_PARAM = "smtpFrom";
+    private static final String SMTP_HOST_PARAM = "smtpHost";
+    private static final String SMS_TRANSPORT_URL_PARAM = "smsTransportUrl";
+    private static final String SMS_PASSWORD_PARAM = "smsPassword";
+    private static final String SMS_USERNAME_PARAM = "smsUsername";
+    private static final String SMS_ACCOUNT_PARAM = "smsAccountName";
+
+    private static final String MIDLET_NAME_ATTRIBUTE_PARAM = "midletNameAttribute";
+    private static final String OTP_LENGTH_PARAM = "otpLength";
+    private static final String ENABLE_DECOY_MIDLET_PARAM = "enableDecoyMidlet";
+    private static final String PRESENTATION_BASE_URL_PARAM = "presentationBaseUrl";
+    
+    private static final Logger log = LoggerFactory.getLogger( ActivateAccountFilter.class );
+	private static final String DEFAULT_MIDLETNAME = "HausKeys";
+	
+	private LdapContext ctx;
+	private File baseDir;
+    private SmsConfiguration smsConfig = null;
+	private SmtpConfiguration smtpConfig = null;
+	private ActivationConfiguration actConfig = null;
+    private NotificationQueue msgQueue = new NotificationQueue();
+    private Thread notifierThread;
+    private boolean keepRunning = true;
+    
+    
+    // -----------------------------------------------------------------------
+    // Filter Initialization Methods
+    // -----------------------------------------------------------------------
+
+    
+    /**
+     * Initializes the LDAP context using security settings from web.xml.
+     * 
+     * @param config the filter's configuration
+     */
+    private void initLdapContext( FilterConfig config ) throws ServletException
+    {
+        String ldapHost = config.getInitParameter( "ldapHost" );
+        String ldapPrincipalDn = config.getInitParameter( "ldapPrincipalDn" );
+        String ldapCredentials = config.getInitParameter( "ldapCredentials" );
+        String ldapBaseDn = config.getInitParameter( "ldapBaseDn" );
+        int ldapPort = Integer.parseInt( config.getInitParameter( "ldapPort" ) );
+
+        Hashtable env = new Hashtable();
+        env.put( Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory" );
+
+        // calculate the provider url from components
+        StringBuffer buf = new StringBuffer();
+        buf.append( "ldap://" ).append( ldapHost ).append( ":" ).append( ldapPort );
+        buf.append( "/" ).append( ldapBaseDn );
+        env.put( Context.PROVIDER_URL, buf.toString() );
+        
+        env.put( Context.SECURITY_AUTHENTICATION, "simple" );
+        env.put( Context.SECURITY_CREDENTIALS, ldapCredentials );
+        env.put( Context.SECURITY_PRINCIPAL, ldapPrincipalDn );
+
+        try
+        {
+            ctx = ( LdapContext ) new InitialLdapContext( env, null ).lookup( "" );
+        }
+        catch ( NamingException e )
+        {
+            log.error( "Failed to initialize DirContext for LDAP service.", e );
+        }
+    }
+    
+    
+    /**
+     * Intializes the SMS settings from web.xml needed to send messages.
+     * 
+     * @param config the filter's configuration
+     */
+    private void initSmsConfiguration( FilterConfig config )
+    {
+        smsConfig = new SmsConfiguration();
+        smsConfig.setSmsAccountName( config.getInitParameter( SMS_ACCOUNT_PARAM ) );
+        smsConfig.setSmsUsername( config.getInitParameter( SMS_USERNAME_PARAM ) );
+        smsConfig.setSmsPassword( config.getInitParameter( SMS_PASSWORD_PARAM ) );
+        smsConfig.setSmsTransportUrl( config.getInitParameter( SMS_TRANSPORT_URL_PARAM ) );
+    }
+    
+    
+    /**
+     * Initializes the mail server settings from web.xml to send messages.
+     * 
+     * @param config the filter's configuration
+     */
+    private void initSmtpConfiguration( FilterConfig config )
+    {
+        smtpConfig = new SmtpConfiguration();
+        smtpConfig.setSmtpHost( config.getInitParameter( SMTP_HOST_PARAM ) );
+        smtpConfig.setSmtpFrom( config.getInitParameter( SMTP_FROM_PARAM ) );
+        smtpConfig.setSmtpSubject( config.getInitParameter( SMTP_SUBJECT_PARAM ) );
+        if ( config.getInitParameter( SMTP_USERNAME_PARAM ) == null )
+        {
+            smtpConfig.setSmtpAuthenticate( false );
+        }
+        else
+        {
+            smtpConfig.setSmtpAuthenticate( true );
+            smtpConfig.setSmtpUsername( config.getInitParameter( SMTP_USERNAME_PARAM ) );
+            smtpConfig.setSmtpPassword( config.getInitParameter( SMTP_PASSWORD_PARAM ) );
+        }
+    }
+    
+    
+    /**
+     * Initializes activation behavior settings from the web.xml to send messages.
+     * 
+     * @param config the filter's configuration
+     */
+    private void initActivationConfiguration( FilterConfig config )
+    {
+        actConfig = new ActivationConfiguration();
+        
+        String presentationBaseUrl = config.getInitParameter( PRESENTATION_BASE_URL_PARAM );
+        if ( presentationBaseUrl != null && ! presentationBaseUrl.equals( "undefined" ) )
+        {
+            actConfig.setActivationBaseUrl( presentationBaseUrl + "/activation" );
+        }
+        
+        if ( config.getInitParameter( ENABLE_DECOY_MIDLET_PARAM ) != null )
+        {
+            String val = config.getInitParameter( ENABLE_DECOY_MIDLET_PARAM );
+            actConfig.setEnableDecoyMidlet( val.equals( "1" ) || val.equalsIgnoreCase( "true" ) );
+        }
+        
+        if ( config.getInitParameter( OTP_LENGTH_PARAM ) != null )
+        {
+            actConfig.setOtpLength( Integer.parseInt( config.getInitParameter( OTP_LENGTH_PARAM ) ) );
+        }
+        
+        if ( config.getInitParameter( MIDLET_NAME_ATTRIBUTE_PARAM ) != null )
+        {
+            actConfig.setMidletNameAttribute( config.getInitParameter( MIDLET_NAME_ATTRIBUTE_PARAM ) );
+        }
+    }
+    
+    
+    /**
+     * Filter's main init method called by the servlet container.
+     * 
+     * @param config the filter's configuration
+     */
+	public void init( FilterConfig config ) throws ServletException 
+	{
+        initLdapContext( config );
+        initSmsConfiguration( config );
+        initSmtpConfiguration( config );
+        initActivationConfiguration( config );
+        
+        try 
+        {
+			baseDir = new File( config.getServletContext().getRealPath( "" ) ).getCanonicalFile();
+		} 
+        catch ( IOException e ) 
+        {
+        	log.error( "Could not get cannonical file for base path", e );
+        	throw new ServletException( e );
+		}
+        
+        notifierThread = new Thread( this, "notifier" );
+        notifierThread.start();
+	}
+
+    
+    // -----------------------------------------------------------------------
+    // Main Filter Processing Method and Helpers
+    // -----------------------------------------------------------------------
+
+    
+    /**
+     * The following sequence of events are handed by this filter:
+     * <ol>
+     *   <li>Client receives SMS and follows link to a URL for Jad or Jar under this filter.</li>
+     *   <li>Servlet filter constructs Jad or midlet Jar for client request.</li>
+     *   <li>
+     *      If a Jar was requested an SMS message is sent to the client with an a message 
+     *      to activate the account and an activation URL.  The activation URL is the same
+     *      as the first URL except the activation code is prefixed with an 'a' for activate.
+     *   </li>
+     *   <li>Client receives 2nd activation SMS and navigates to the embedded activation URL.</li>
+     *   <li>Filter then destroys the activation directory and activates the account.</li>
+     *   <li>A third final SMS is sent to notify the client that the account has been activated.</li>
+     * </ol>
+     * 
+     */
+	public void doFilter( ServletRequest sreq, ServletResponse sresp, FilterChain chain ) 
+        throws IOException, ServletException 
+	{
+		HttpServletRequest req = ( HttpServletRequest ) sreq;
+		HttpServletResponse resp = ( HttpServletResponse ) sresp;
+		
+		String activationKey = ActivationUtils.getActivationKey( req.getRequestURI() );
+        
+        if ( req.getRequestURI().endsWith( "activate.txt" ) || 
+             req.getRequestURI().endsWith( "activate.wml" ) ||
+             req.getRequestURI().endsWith( "activate.html" ) )
+        {
+            processActivation( activationKey, req, resp, chain );
+        }
+        else
+        {
+            processDownload( activationKey, req, resp, chain );
+        }
+    } 
+
+    
+    private void sendResponse( String msg, HttpServletRequest req, HttpServletResponse resp )
+        throws IOException, ServletException 
+    {
+        String contentType = null;
+        StringBuffer content = new StringBuffer();
+        
+        if ( req.getRequestURI().endsWith( ".txt" ) )
+        {
+            contentType = "text/plain";
+            content.append( msg );
+        }
+        else if ( req.getRequestURI().endsWith( ".html" ) )
+        {
+            contentType = "text/html";
+            content.append( "<html><body><p>" ).append( msg ).append( "</p></body></html>" );
+        }
+        else if ( req.getRequestURI().endsWith( ".wml" ) )
+        {
+            contentType = "text/vnd.wap.wml";
+            content.append( "<wml> <card id=\"main\" title=\"Safehaus\"><p align=\"left\">" );
+            content.append( msg ).append( "</p></card></wml>" );
+        }
+        else
+        {
+            String errmsg = "Unrecognized file extension in request URI: " + req.getRequestURI();
+            log.error( errmsg );
+            throw new IllegalStateException( errmsg );
+        }
+        
+        resp.setContentType( contentType );
+        resp.getWriter().write( content.toString() );
+        resp.getWriter().flush();
+        resp.getWriter().close();
+        return;
+    }
+    
+    
+    private void processActivation( String activationKey, HttpServletRequest req, HttpServletResponse resp, FilterChain chain  )
+        throws IOException, ServletException 
+    {
+        // -------------------------------------------------------------------
+        // Lookup the account associated with this activation key
+        // -------------------------------------------------------------------
+        
+        HotpAccount account = null;
+        try 
+        {
+            account = ActivationUtils.getAccountHotpAccount( ( LdapContext ) ctx.lookup( "ou=users" ), activationKey );
+        } 
+        catch ( NamingException e ) 
+        {
+            String message = "Failed to access safehaus account information for activation key: " + activationKey;
+            log.error( message, e );
+            sendResponse( message, req, resp );
+            return;
+        }
+        
+        // -------------------------------------------------------------------
+        // Delete the safehausActivationKey attribute for the user to activate the account
+        // -------------------------------------------------------------------
+
+        BasicAttributes mods = new BasicAttributes( "safehausActivationKey", null, true );
+        try
+        {
+            ctx.modifyAttributes( account.getName() + ",ou=users", DirContext.REMOVE_ATTRIBUTE, mods );
+        }
+        catch ( NamingException e )
+        {
+            String errmsg = "Failed to activate account for " + account.getName();
+            log.error( errmsg, e );
+            sendResponse( errmsg, req, resp );
+            return;
+        }
+        
+        // -------------------------------------------------------------------
+        // Delete the directory with jar that was generated for this key
+        // -------------------------------------------------------------------
+
+        File activationDirectory = new File( baseDir, activationKey );
+        ActivationUtils.deleteActivationDirectory( activationDirectory );
+        
+        // -------------------------------------------------------------------
+        // Send informational message back confirming activation using 
+        // account's notification medium: sms or email.
+        // -------------------------------------------------------------------
+
+        Notification msg = null;
+        if ( account.isDeliveryMethodSms() )
+        {
+            msg = new SmsNotification( account.getCellularNumber(), account.getCarrier(), activationKey, 
+                    "Your account for realm " + account.getRealm() + " has been activated!" );
+            try
+            {
+                NotificationUtil.send( account.getRealm(), msg, smsConfig );
+            }
+            catch ( Exception e )
+            {
+                log.error( "Failed to send sms message: " + msg, e );
+            }
+        }
+        else
+        {
+            msg = new EmailNotification( account.getEmailAddress(), activationKey, 
+                "Your account for realm " + account.getRealm() + " has been activated!" );
+            try
+            {
+                NotificationUtil.send( account.getRealm(), msg, smtpConfig );
+            }
+            catch ( Exception e )
+            {
+                log.error( "Failed to send email message: " + msg, e );
+            }
+        }
+        
+        // -------------------------------------------------------------------
+        // Return a text response back as well (should work for all clients)
+        // -------------------------------------------------------------------
+
+        sendResponse( "Your account for realm " + account.getRealm() + " has been activated!", req, resp );
+        return;
+    }
+    
+
+    private void processDownload( String activationKey, HttpServletRequest req, 
+                                  HttpServletResponse resp, FilterChain chain  ) throws IOException, ServletException 
+    {
+        if ( req.getRequestURI().indexOf( ".jar" ) == -1 )
+        {
+            String message = "No such resource associated with the activation key: " + activationKey;
+            log.info( message );
+            resp.setContentType( "text/plain" );
+            resp.getWriter().write( message );
+            resp.getWriter().close();
+            return;
+        }
+
+        // -------------------------------------------------------------------
+        // Lookup the account associated with this activation key
+        // -------------------------------------------------------------------
+        
+        HotpAccount account = null;
+        try 
+        {
+            account = ActivationUtils.getAccountHotpAccount( ( LdapContext ) ctx.lookup( "ou=users" ), activationKey );
+        } 
+        catch ( NamingException e ) 
+        {
+            String message = "Failed to access safehaus account information for activation key: " + activationKey;
+            log.error( message, e );
+            resp.setContentType( "text/plain" );
+            resp.getWriter().write( message );
+            resp.getWriter().close();
+            return;
+        }
+        
+        // -------------------------------------------------------------------
+        // If the activation directory exists that means the jar is there 
+        // already and this is not the 1st request so far for the jar file 
+        // so we want to expedite the delivery of the ActivationMessage for 
+        // this activation.
+        // -------------------------------------------------------------------
+        
+        File activationDirectory = new File( baseDir, activationKey );
+        if ( activationDirectory.exists() )
+        {
+            chain.doFilter( req, resp );
+
+            // Dequeue the message if it has not been delivered already and send
+            // instead of waiting for the message to be delivered after the delay   
+            Notification msg = null;
+            synchronized ( msgQueue )
+            {
+                msg = msgQueue.dequeue( activationKey );
+            }
+            if ( msg != null )
+            {
+                try
+                {
+                    NotificationUtil.send( account.getRealm(), msg, smsConfig );
+                }
+                catch ( Exception e )
+                {
+                    log.error( "Failed to deliver message: " + msg, e );
+                }
+            }
+            
+            return;
+        }
+        
+        // -------------------------------------------------------------------
+        // First jar request builds the midlet
+        // -------------------------------------------------------------------
+
+        ActivationUtils.createActivationDirectory( activationDirectory );
+		String hotpInfo = null;
+		String midletName = DEFAULT_MIDLETNAME;
+        if ( ! actConfig.isEnableDecoyMidlet() && account == null )
+        {
+            String message = "No such safehaus account with activation key: " + activationKey;
+            log.info( message );
+            resp.setContentType( "text/plain" );
+            resp.getWriter().write( message );
+            resp.getWriter().close();
+            return;
+        }
+
+        if ( account == null )
+		{
+			hotpInfo = ActivationUtils.getBogusHotpInfo();
+		}
+		else
+		{
+            HotpAttributes hotpAttrs = new HotpAttributes( actConfig.getOtpLength(), 
+                account.getFactor(), account.getSecret() );
+            hotpInfo = HotpAttributesCipher.encrypt( account.getPin(), hotpAttrs );
+		}
+		
+        midletName = account.getMidletName();
+		File appSrc = new File( baseDir, "HausKeys.jar" );
+		File appDest = new File( activationDirectory, "HausKeys.jar" );
+		try 
+		{
+			ActivationUtils.buildMidlet( midletName, hotpInfo, appSrc, appDest );
+		} 
+		catch ( Exception e ) 
+		{
+			String message = "Failed to create midlet jar for activation key " + activationKey;
+			log.error( message, e );
+			resp.setContentType( "text/plain" );
+			resp.getWriter().write( message );
+			resp.getWriter().close();
+			ActivationUtils.deleteActivationDirectory( activationDirectory );
+			return;
+		} 
+
+		chain.doFilter( req, resp );
+
+        Notification msg = null;
+        StringBuffer content = new StringBuffer();
+        if ( actConfig.getActivationBaseUrl() == null || actConfig.getActivationBaseUrl().equals( "undefined" ) )
+        {
+            content.append( "To activate your new account navigate to the following URL: http://" );
+            content.append( req.getServerName() ).append( ":" ).append( req.getServerPort() );
+            content.append( "/activation/" ).append( activationKey ).append( "/activate" );
+        }
+        else 
+        {
+            content.append( "To activate your new account navigate to the following URL: " );
+            content.append( actConfig.getActivationBaseUrl() );
+            content.append( "/" ).append( activationKey ).append( "/activate" );
+        }
+        
+        if ( account.isDeliveryMethodSms() )
+        {
+            content.append( ".wml" );
+            msg = new SmsNotification( account.getCellularNumber(), account.getCarrier(), 
+                activationKey, content.toString() );
+        }
+        else
+        {
+            content.append( ".html" );
+            msg = new EmailNotification( account.getEmailAddress(), activationKey, content.toString() );
+        }
+        
+        synchronized ( msgQueue )
+        {
+            // enqueue the activation request message for the account
+            msgQueue.enqueue( msg );
+            msgQueue.notifyAll();
+        }
+	}
+	
+	
+    public void destroy()
+    {
+        keepRunning = false;
+        
+        while ( notifierThread.isAlive() )
+        {
+            synchronized ( msgQueue )
+            {
+                msgQueue.notifyAll();
+            }
+            
+            try
+            {
+                notifierThread.join( 250 );
+            }
+            catch ( InterruptedException e )
+            {
+                log.error( "encountered failure while waiting for notifier thread to die", e );
+            }
+        }
+    }
+    
+
+    public void run()
+    {
+        while ( keepRunning )
+        {
+            synchronized ( msgQueue )
+            {
+                if ( msgQueue.available( MIN_NOTIFICATION_AGE ) )
+                {
+                    Notification[] msgs = msgQueue.dequeue( MIN_NOTIFICATION_AGE );
+                    for ( int ii = 0; ii < msgs.length; ii++ )
+                    {
+                        if ( msgs[ii] instanceof SmsNotification )
+                        {
+                            try
+                            {
+                                NotificationUtil.send( "unknown", msgs[ii], smsConfig );
+                            }
+                            catch ( Exception e )
+                            {
+                                log.error( "Failed to send sms notification: " + msgs[ii], e );
+                            }
+                        }
+                        else
+                        {
+                            try
+                            {
+                                NotificationUtil.send( "unknown", msgs[ii], smtpConfig );
+                            }
+                            catch ( Exception e )
+                            {
+                                log.error( "Failed to send email notification: " + msgs[ii], e );
+                            }
+                        }
+                    }
+                }
+
+                long waitMillis = msgQueue.getWaitMillis();
+                try
+                {
+                    if ( waitMillis == Long.MAX_VALUE )
+                    {
+                        msgQueue.wait();
+                    }
+                    else if ( waitMillis == 0 )
+                    {
+                        continue;
+                    }
+                    else
+                    {
+                        msgQueue.wait( waitMillis );
+                    }
+                }
+                catch ( InterruptedException e )
+                {
+                    log.error( "got interrupted exception while attempting to wait for " 
+                        + waitMillis + " milliseconds", e );
+                }
+            }
+        }
+    }
+}

Added: directory/trunks/triplesec/webapp-activation/src/main/java/org/safehaus/triplesec/activation/ActivationUtils.java
URL: http://svn.apache.org/viewvc/directory/trunks/triplesec/webapp-activation/src/main/java/org/safehaus/triplesec/activation/ActivationUtils.java?view=auto&rev=486187
==============================================================================
--- directory/trunks/triplesec/webapp-activation/src/main/java/org/safehaus/triplesec/activation/ActivationUtils.java (added)
+++ directory/trunks/triplesec/webapp-activation/src/main/java/org/safehaus/triplesec/activation/ActivationUtils.java Tue Dec 12 07:23:31 2006
@@ -0,0 +1,182 @@
+/*
+ *  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.safehaus.triplesec.activation;
+
+
+import java.io.File;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.SearchResult;
+
+import org.apache.commons.lang.RandomStringUtils;
+import org.apache.commons.lang.math.RandomUtils;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.taskdefs.Delete;
+import org.apache.tools.ant.taskdefs.ManifestException;
+import org.apache.tools.ant.taskdefs.Mkdir;
+
+import org.safehaus.otp.HotpAttributes;
+import org.safehaus.otp.HotpAttributesCipher;
+import org.safehaus.triplesec.utils.hauskeys.HauskeysMidletBuilder;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * Utility functions for activating an account.
+ * 
+ * @author <a href="mailto:akarasulu@safehaus.org">Alex Karasulu</a>
+ * @version $Rev$
+ */
+public class ActivationUtils 
+{
+	/** logger for this class */
+	private static final Logger log = LoggerFactory.getLogger( ActivationUtils.class );
+	/** index of the act key when spliting the request URI */
+	private static final int ACTKEY_INDEX = 2;
+	
+	
+	/**
+	 * Extracts the activation key from a request's URI string.
+	 * 
+	 * @param requestURI the request's URI string
+	 * @return the safehaus activation key
+	 * @throws IllegalArgumentException if the request URI is null or does not contain "/"
+	 */
+	public static String getActivationKey( String requestURI )
+	{
+		if ( requestURI == null || requestURI.indexOf( "/" ) == -1  || !requestURI.startsWith( "/" ) ) 
+		{
+			throw new IllegalArgumentException( "Not a valid URI string: " + requestURI );
+		}
+		
+		String[] comps = requestURI.split( "/" );
+		return comps[ACTKEY_INDEX];
+	}
+	
+	
+	public static File createActivationDirectory( File activationDirectory )
+	{
+		Project project = new Project();
+		
+		// if old directory exists blow it away
+		if ( activationDirectory.exists() )
+		{
+			Delete delete = new Delete();
+			delete.setProject( project );
+			delete.setDir( activationDirectory );
+			delete.execute();
+		}
+		
+		Mkdir mkdir = new Mkdir();
+		mkdir.setProject( project );
+		mkdir.setDir( activationDirectory );
+	    mkdir.execute();
+	    return activationDirectory;
+	}
+	
+	
+	public static void deleteActivationDirectory( File activationDirectory )
+	{
+		if ( activationDirectory == null )
+		{
+			throw new IllegalArgumentException( "activationDirectory cannot be null" );
+		}
+		
+		Project project = new Project();
+		Delete delete = new Delete();
+		delete.setProject( project );
+		delete.setDir( activationDirectory );
+		delete.execute();
+	}
+	
+	
+    public static HotpAccount getAccountHotpAccount( DirContext ctx, String activationKey ) throws NamingException
+	{
+		NamingEnumeration list = null;
+		
+        try
+        {
+            list = ctx.search( "", new BasicAttributes( "safehausActivationKey", activationKey, true ) );
+            if ( list.hasMore() )
+            {
+            	SearchResult result = ( SearchResult ) list.next();
+            	Attributes entry = result.getAttributes();
+                HotpAccountModifier modifier = new HotpAccountModifier();
+                modifier.setCarrier( ( String ) entry.get( "safehausMobileCarrier" ).get() );
+                modifier.setCellularNumber( ( String ) entry.get( "mobile" ).get() );
+                modifier.setDeliveryMethodSms( ( String ) entry.get( "safehausNotifyBy" ).get() );
+                modifier.setEmailAddress( ( String ) entry.get( "mail" ).get() );
+                modifier.setFactor( Long.parseLong( ( String ) entry.get( "safehausFactor" ).get() ) );
+                modifier.setPin( ( String ) entry.get( "safehausTokenPin" ).get() );
+                modifier.setSecret( entry.get( "safehausSecret" ).get() );
+                modifier.setMidletName( ( String ) entry.get( "safehausMidletName" ).get() );
+                modifier.setName( result.getName() );
+                modifier.setRealm( ( String ) entry.get( "safehausRealm" ).get() );
+            	return modifier.create();
+            }
+        }
+        finally
+        {
+            if ( list != null ) { try { list.close(); } catch ( Exception e ){ log.error( "can't close enum" ); } };
+        }
+        
+		return null;
+	}
+    
+
+    public static String getBogusHotpInfo()
+    {
+        // max length 16, min length 8 bytes
+        int length = 8 + RandomUtils.nextInt() % 8;
+        byte[] secret = RandomStringUtils.random( length ).getBytes();
+        HotpAttributes hotpAttrs = new HotpAttributes( 6, RandomUtils.nextLong(), secret );
+        try 
+        {
+			return HotpAttributesCipher.encrypt( RandomStringUtils.randomAlphanumeric(4), hotpAttrs );
+		} 
+        catch ( UnsupportedEncodingException e ) 
+        {
+        	log.error( "could not make hotp info", e );
+        	return null;
+		}
+    }
+    
+    
+    public static void buildMidlet( String midletName, String hotpInfo, File appSrc, File appDest ) 
+        throws IOException, ManifestException
+    {
+        File tmp = new File( System.getProperty( "java.io.tmpdir" ) );
+        HauskeysMidletBuilder builder = new HauskeysMidletBuilder();
+        builder.setTmpDirectory( tmp );
+        builder.setHauskeysSrcFile( appSrc );
+        builder.setHauskeysDstFile( appDest );
+        builder.setHotpInfo( hotpInfo );
+        builder.setMidletName( midletName );
+        builder.build();
+    }
+}

Added: directory/trunks/triplesec/webapp-activation/src/main/java/org/safehaus/triplesec/activation/EmailNotification.java
URL: http://svn.apache.org/viewvc/directory/trunks/triplesec/webapp-activation/src/main/java/org/safehaus/triplesec/activation/EmailNotification.java?view=auto&rev=486187
==============================================================================
--- directory/trunks/triplesec/webapp-activation/src/main/java/org/safehaus/triplesec/activation/EmailNotification.java (added)
+++ directory/trunks/triplesec/webapp-activation/src/main/java/org/safehaus/triplesec/activation/EmailNotification.java Tue Dec 12 07:23:31 2006
@@ -0,0 +1,45 @@
+/*
+ *  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.safehaus.triplesec.activation;
+
+
+/**
+ * Email based notification message.
+ * 
+ * @author <a href="mailto:akarasulu@safehaus.org">Alex Karasulu</a>
+ * @version $Rev$
+ */
+public class EmailNotification extends Notification
+{
+    private final String emailAddress;
+
+    
+    protected EmailNotification( String emailAddress, String activationKey, String message )
+    {
+        super( activationKey, message );
+        this.emailAddress = emailAddress;
+    }
+    
+    
+    public String getEmailAddress()
+    {
+        return emailAddress;
+    }
+}

Added: directory/trunks/triplesec/webapp-activation/src/main/java/org/safehaus/triplesec/activation/HotpAccount.java
URL: http://svn.apache.org/viewvc/directory/trunks/triplesec/webapp-activation/src/main/java/org/safehaus/triplesec/activation/HotpAccount.java?view=auto&rev=486187
==============================================================================
--- directory/trunks/triplesec/webapp-activation/src/main/java/org/safehaus/triplesec/activation/HotpAccount.java (added)
+++ directory/trunks/triplesec/webapp-activation/src/main/java/org/safehaus/triplesec/activation/HotpAccount.java Tue Dec 12 07:23:31 2006
@@ -0,0 +1,121 @@
+/*
+ *  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.safehaus.triplesec.activation;
+
+
+import org.safehaus.sms.Carrier;
+
+/**
+ * Bean representing HOTP centric account information.
+ * 
+ * @author <a href="mailto:akarasulu@safehaus.org">Alex Karasulu</a>
+ * @version $Rev$
+ */
+public class HotpAccount
+{
+    private final Carrier carrier;
+    private final String cellularNumber;
+    private final String emailAddress;
+    private final String midletName;
+    private final boolean deliveryMethodSms;
+    private final String realm;
+    private final String name;
+    private final byte[] secret;
+    private final String pin;
+    private final long factor;
+    
+    
+    public HotpAccount( Carrier carrier, String cellularNumber, String emailAddress, String midletName,
+                        boolean deliveryMethodSms, String realm, String name, byte[] secret, String pin, long factor )
+    {
+        this.carrier = carrier;
+        this.cellularNumber = cellularNumber;
+        this.emailAddress = emailAddress;
+        this.midletName = midletName;
+        this.deliveryMethodSms = deliveryMethodSms;
+        this.realm = realm;
+        this.name = name;
+        this.secret = secret;
+        this.pin = pin;
+        this.factor = factor;
+    }
+
+
+    public Carrier getCarrier()
+    {
+        return carrier;
+    }
+
+
+    public String getCellularNumber()
+    {
+        return cellularNumber;
+    }
+
+
+    public String getEmailAddress()
+    {
+        return emailAddress;
+    }
+
+
+    public String getMidletName()
+    {
+        return midletName;
+    }
+
+
+    public boolean isDeliveryMethodSms()
+    {
+        return deliveryMethodSms;
+    }
+
+
+    public String getRealm()
+    {
+        return realm;
+    }
+
+
+    public String getName()
+    {
+        return name;
+    }
+
+
+    public byte[] getSecret()
+    {
+        byte[] copy = new byte[secret.length];
+        System.arraycopy( secret, 0, copy, 0, secret.length );
+        return copy;
+    }
+
+
+    public String getPin()
+    {
+        return pin;
+    }
+
+
+    public long getFactor()
+    {
+        return factor;
+    }
+}

Added: directory/trunks/triplesec/webapp-activation/src/main/java/org/safehaus/triplesec/activation/HotpAccountModifier.java
URL: http://svn.apache.org/viewvc/directory/trunks/triplesec/webapp-activation/src/main/java/org/safehaus/triplesec/activation/HotpAccountModifier.java?view=auto&rev=486187
==============================================================================
--- directory/trunks/triplesec/webapp-activation/src/main/java/org/safehaus/triplesec/activation/HotpAccountModifier.java (added)
+++ directory/trunks/triplesec/webapp-activation/src/main/java/org/safehaus/triplesec/activation/HotpAccountModifier.java Tue Dec 12 07:23:31 2006
@@ -0,0 +1,238 @@
+/*
+ *  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.safehaus.triplesec.activation;
+
+
+import java.io.UnsupportedEncodingException;
+
+import org.safehaus.sms.Carrier;
+
+
+/**
+ * A modifier for a HotpAccount.
+ * 
+ * @author <a href="mailto:akarasulu@safehaus.org">Alex Karasulu</a>
+ * @version $Rev$
+ */
+public class HotpAccountModifier
+{
+    private Carrier carrier;
+    private String cellularNumber;
+    private String emailAddress;
+    private String midletName;
+    private boolean deliveryMethodSms;
+    private String realm;
+    private String name;
+    private byte[] secret;
+    private String pin;
+    private long factor;
+    
+    
+    public void setCarrier( String carrierName )
+    {
+        if ( carrierName.equalsIgnoreCase( Carrier.ATT.getName() ) )
+        {
+            carrier = Carrier.ATT;
+        }
+        else if ( carrierName.equalsIgnoreCase( Carrier.CINGULAR.getName() ) )
+        {
+            carrier = Carrier.CINGULAR;
+        }
+        else if ( carrierName.equalsIgnoreCase( Carrier.NEXTEL.getName() ) )
+        {
+            carrier = Carrier.NEXTEL;
+        }
+        else if ( carrierName.equalsIgnoreCase( Carrier.SPRINT.getName() ) )
+        {
+            carrier = Carrier.SPRINT;
+        }
+        else if ( carrierName.equalsIgnoreCase( Carrier.T_MOBILE.getName() ) )
+        {
+            carrier = Carrier.T_MOBILE;
+        }
+        else if ( carrierName.equalsIgnoreCase( Carrier.VERIZON.getName() ) )
+        {
+            carrier = Carrier.VERIZON;
+        }
+        else
+        {
+            throw new IllegalArgumentException( "Unrecognized carrier name: " + carrierName );
+        }
+    }
+    
+    
+    public Carrier getCarrier()
+    {
+        return carrier;
+    }
+
+
+    public void setCellularNumber( String cellularNumber )
+    {
+        this.cellularNumber = cellularNumber;
+    }
+
+
+    public String getCellularNumber()
+    {
+        return cellularNumber;
+    }
+
+
+    public void setEmailAddress( String emailAddress )
+    {
+        this.emailAddress = emailAddress;
+    }
+
+
+    public String getEmailAddress()
+    {
+        return emailAddress;
+    }
+
+
+    public void setMidletName( String midletName )
+    {
+        this.midletName = midletName;
+    }
+
+
+    public String getMidletName()
+    {
+        return midletName;
+    }
+
+
+    public void setDeliveryMethodSms( boolean deliveryMethodSms )
+    {
+        this.deliveryMethodSms = deliveryMethodSms;
+    }
+
+
+    public void setDeliveryMethodSms( String deliveryMethodSmsName )
+    {
+        if ( deliveryMethodSmsName.equalsIgnoreCase( "sms" ) )
+        {
+            this.deliveryMethodSms = true;
+        }
+        else
+        {
+            this.deliveryMethodSms = false;
+        }
+    }
+
+
+    public boolean isDeliveryMethodSms()
+    {
+        return deliveryMethodSms;
+    }
+
+
+    public void setRealm( String realm )
+    {
+        this.realm = realm;
+    }
+
+
+    public String getRealm()
+    {
+        return realm;
+    }
+
+
+    public void setName( String name )
+    {
+        this.name = name;
+    }
+
+
+    public String getName()
+    {
+        return name;
+    }
+
+
+    public void setSecret( Object secret )
+    {
+        if ( secret instanceof byte[] )
+        {
+            this.secret = ( byte[] ) secret;
+        }
+        else if ( secret instanceof String )
+        {
+            try
+            {
+                this.secret = ( ( String ) secret ).getBytes( "UTF-8" );
+            }
+            catch ( UnsupportedEncodingException e )
+            {
+                e.printStackTrace();
+            }
+        }
+        else
+        {
+            try
+            {
+                this.secret = String.valueOf( secret ).getBytes( "UTF-8" );
+            }
+            catch ( UnsupportedEncodingException e )
+            {
+                e.printStackTrace();
+            }
+        }
+    }
+
+
+    public byte[] getSecret()
+    {
+        return secret;
+    }
+
+
+    public void setPin( String pin )
+    {
+        this.pin = pin;
+    }
+
+
+    public String getPin()
+    {
+        return pin;
+    }
+
+
+    public void setFactor( long factor )
+    {
+        this.factor = factor;
+    }
+
+
+    public long getFactor()
+    {
+        return factor;
+    }
+
+
+    public HotpAccount create()
+    {
+        return new HotpAccount( carrier, cellularNumber, emailAddress, midletName, 
+            deliveryMethodSms, realm, name, secret, pin, factor );
+    }
+}

Added: directory/trunks/triplesec/webapp-activation/src/main/java/org/safehaus/triplesec/activation/Notification.java
URL: http://svn.apache.org/viewvc/directory/trunks/triplesec/webapp-activation/src/main/java/org/safehaus/triplesec/activation/Notification.java?view=auto&rev=486187
==============================================================================
--- directory/trunks/triplesec/webapp-activation/src/main/java/org/safehaus/triplesec/activation/Notification.java (added)
+++ directory/trunks/triplesec/webapp-activation/src/main/java/org/safehaus/triplesec/activation/Notification.java Tue Dec 12 07:23:31 2006
@@ -0,0 +1,83 @@
+/*
+ *  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.safehaus.triplesec.activation;
+
+
+import java.util.Date;
+
+
+/**
+ * A message to be delivered to the account owner requesting the activation of
+ * their account.  ActivationMessages are delivered to the owner after some 
+ * delay, hence the need to encapsulate the message and enqueue it for future
+ * delivery.  An ActivationMessage generally contains the URL used to activate 
+ * the account.
+ * 
+ * @author <a href="mailto:akarasulu@safehaus.org">Alex Karasulu</a>
+ * @version $Rev$
+ */
+public abstract class Notification
+{
+    private final long timestamp;
+    private final String message;
+    private final String activationKey;
+    
+    
+    protected Notification( String activationKey, String message )
+    {
+        this.timestamp = System.currentTimeMillis();
+        this.message = message;
+        this.activationKey = activationKey;
+    }
+    
+    
+    public long getTimestamp()
+    {
+        return timestamp;
+    }
+    
+    
+    public String getMessage()
+    {
+        return message;
+    }
+    
+    
+    public String getActivationKey()
+    {
+        return activationKey;
+    }
+    
+    
+    public int hashCode()
+    {
+        return activationKey.hashCode();
+    }
+    
+    
+    public String toString()
+    {
+        StringBuffer buf = new StringBuffer();
+        buf.append( "\t    timestamp = " ).append( new Date( timestamp ) ).append( "\n" );
+        buf.append( "\t      message = " ).append( message ).append( "\n" );
+        buf.append( "\tactivationKey = " ).append( activationKey ).append( "\n" );
+        return buf.toString();
+    }
+}

Added: directory/trunks/triplesec/webapp-activation/src/main/java/org/safehaus/triplesec/activation/NotificationQueue.java
URL: http://svn.apache.org/viewvc/directory/trunks/triplesec/webapp-activation/src/main/java/org/safehaus/triplesec/activation/NotificationQueue.java?view=auto&rev=486187
==============================================================================
--- directory/trunks/triplesec/webapp-activation/src/main/java/org/safehaus/triplesec/activation/NotificationQueue.java (added)
+++ directory/trunks/triplesec/webapp-activation/src/main/java/org/safehaus/triplesec/activation/NotificationQueue.java Tue Dec 12 07:23:31 2006
@@ -0,0 +1,143 @@
+/*
+ *  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.safehaus.triplesec.activation;
+
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * An activation message queue which allows the dequeue of messages based on age
+ * or based on an activationKey.  Not synchronized.
+ * 
+ * @author <a href="mailto:akarasulu@safehaus.org">Alex Karasulu</a>
+ * @version $Rev$
+ */
+public class NotificationQueue
+{
+    LinkedList msgList = new LinkedList();
+    Map msgMap = new HashMap();
+    
+    
+    public void enqueue( Notification message )
+    {
+        msgList.addFirst( message );
+        msgMap.put( message.getActivationKey(), message );
+    }
+    
+    
+    /**
+     * Dequeues an array of ActivationMessages from the message queue based on age.
+     * 
+     * @param ageMillis remove all messages that have been in the queue for this 
+     * time or more in milliseconds
+     * @return an array of messages of this age with the oldest first
+     */
+    public Notification[] dequeue( long ageMillis )
+    {
+        List ready = null;
+        
+        if ( msgList.size() == 0 )
+        {
+            return new Notification[0];
+        }
+        
+        ready = new ArrayList();
+        while( msgList.size() > 0 )
+        {
+            Notification msg = ( Notification ) msgList.getLast();
+            if ( System.currentTimeMillis() - msg.getTimestamp() >= ageMillis )
+            {
+                msgList.removeLast();
+                msgMap.remove( msg.getActivationKey() );
+                ready.add( msg );
+            }
+        }
+
+        Notification[] array = new Notification[ready.size()];
+        return ( Notification[] ) ready.toArray( array );
+    }
+    
+    
+    /**
+     * Dequeues a specific message regardless of it's age from this message queue.
+     * 
+     * @param activationKey the activation key of the message to remove
+     * @return the activation message with the key or null if none exists 
+     */
+    public Notification dequeue( String activationKey )
+    {
+        Notification msg = ( Notification ) msgMap.remove( activationKey );
+        if ( msg == null )
+        {
+            return null;
+        }
+        msgList.remove( msg );
+        return msg;
+    }
+    
+    
+    public boolean isEmpty()
+    {
+        return msgList.isEmpty();
+    }
+    
+    
+    public boolean available( long ageMillis )
+    {
+        if ( msgList.isEmpty() )
+        {
+            return false;
+        }
+        
+        Notification msg = ( Notification ) msgList.getLast();
+        if ( msg == null )
+        {
+            return false;
+        }
+        return System.currentTimeMillis() - msg.getTimestamp() >= ageMillis;
+    }
+
+
+    public long getWaitMillis()
+    {
+        if ( msgList.isEmpty() )
+        {
+            return Long.MAX_VALUE;
+        }
+        
+        Notification msg = ( Notification ) msgList.getLast();
+        if ( msg == null )
+        {
+            return Long.MAX_VALUE;
+        }
+        
+        long waitMillis = 60000 - ( System.currentTimeMillis() - msg.getTimestamp() );
+        if ( waitMillis < 0 )
+        {
+            return 0;
+        }
+        return waitMillis;
+    }
+}

Added: directory/trunks/triplesec/webapp-activation/src/main/java/org/safehaus/triplesec/activation/NotificationUtil.java
URL: http://svn.apache.org/viewvc/directory/trunks/triplesec/webapp-activation/src/main/java/org/safehaus/triplesec/activation/NotificationUtil.java?view=auto&rev=486187
==============================================================================
--- directory/trunks/triplesec/webapp-activation/src/main/java/org/safehaus/triplesec/activation/NotificationUtil.java (added)
+++ directory/trunks/triplesec/webapp-activation/src/main/java/org/safehaus/triplesec/activation/NotificationUtil.java Tue Dec 12 07:23:31 2006
@@ -0,0 +1,147 @@
+/*
+ *  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.safehaus.triplesec.activation;
+
+
+import java.io.IOException;
+import java.util.Date;
+import java.util.Properties;
+
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.HttpMethod;
+import org.apache.commons.httpclient.NameValuePair;
+import org.apache.commons.httpclient.methods.PostMethod;
+import org.safehaus.triplesec.configuration.SmsConfiguration;
+import org.safehaus.triplesec.configuration.SmtpConfiguration;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.sun.mail.smtp.SMTPTransport;
+
+
+/**
+ * Utility methods for sending notifications via sms and email.
+ * 
+ * @author <a href="mailto:akarasulu@safehaus.org">Alex Karasulu</a>
+ * @version $Rev$
+ */
+public class NotificationUtil
+{
+    // Request parameters for NMSI SMS POST
+    private static final String MSG_REQPARAM = "msg";
+    private static final String MOBILE_REQPARAM = "CellNumber";
+    private static final String ACCOUNTNAME_REQPARAM = "Campaign";
+    private static final String PASSWORD_REQPARAM = "PWD";
+    private static final String USERNAME_REQPARAM = "UID";
+    private static final String CARRIERCODE_REQPARAM = "Carrier";
+    
+    /** logger for this class */
+    private static final Logger log = LoggerFactory.getLogger( ActivationUtils.class );
+
+
+    static void send( String realm, Notification msg, Object config ) throws Exception
+    {
+        if ( msg instanceof SmsNotification )
+        {
+            sendSms( realm, ( SmsNotification ) msg, ( SmsConfiguration ) config );
+        }
+        else
+        {
+            sendEmail( realm, ( EmailNotification ) msg, ( SmtpConfiguration ) config );
+        }
+    }
+    
+    
+    static void sendSms( String realm, SmsNotification msg, SmsConfiguration smsConfig ) throws IOException
+    {
+        HttpClient client = new HttpClient();
+        HttpMethod method = new PostMethod( smsConfig.getSmsTransportUrl() );
+        int carrierCode = msg.getCarrier().getValue();
+        String mobile = msg.getCellularNumber();
+        
+        if ( mobile.startsWith( "+" ) )
+        {
+            mobile = mobile.substring( 1 );
+        }
+        
+        if ( mobile.startsWith( "1" ) )
+        {
+            mobile = mobile.substring( 1 );
+            if ( mobile.length() < 10 )
+            {
+                throw new RuntimeException( "mobile phone number was not 10 digits in length" );
+            }
+        }
+        
+        NameValuePair[] params = new NameValuePair[] {
+            new NameValuePair( CARRIERCODE_REQPARAM, String.valueOf( carrierCode ) ),
+            new NameValuePair( USERNAME_REQPARAM, smsConfig.getSmsUsername() ),
+            new NameValuePair( PASSWORD_REQPARAM, smsConfig.getSmsPassword() ),
+            new NameValuePair( ACCOUNTNAME_REQPARAM, smsConfig.getSmsAccountName() ),
+            new NameValuePair( MOBILE_REQPARAM, mobile ),
+            new NameValuePair( MSG_REQPARAM, msg.getMessage() )
+        };
+        method.setQueryString( params );
+        client.executeMethod( method );
+    }
+
+
+    static void sendEmail( String realm, EmailNotification msg, SmtpConfiguration smtpConfig ) 
+        throws MessagingException
+    {
+        Properties props = new Properties();
+        props.setProperty( "mail.smtp.host", smtpConfig.getSmtpHost() );
+        Session session = Session.getInstance( props, null );
+
+        MimeMessage mimeMsg = new MimeMessage( session );
+        if ( smtpConfig.getSmtpFrom() != null )
+        {
+            mimeMsg.setFrom( new InternetAddress( smtpConfig.getSmtpFrom() ) );
+        }
+        else
+        {
+            mimeMsg.setFrom();
+        }
+
+        mimeMsg.setRecipients( Message.RecipientType.TO, InternetAddress.parse( msg.getEmailAddress(), false ) );
+        mimeMsg.setSubject( smtpConfig.getSmtpSubject() );
+        mimeMsg.setText( msg.getMessage() );
+        mimeMsg.setHeader( "X-Mailer", "triplesec-acivation" );
+        mimeMsg.setSentDate( new Date() );
+        SMTPTransport transport = ( SMTPTransport ) session.getTransport( "smtp" );
+        if ( smtpConfig.isSmtpAuthenticate() )
+        {
+            transport.connect( smtpConfig.getSmtpHost(), smtpConfig.getSmtpUsername(), smtpConfig.getSmtpPassword() );
+        }
+        else
+        {
+            transport.connect();
+        }
+        transport.sendMessage( mimeMsg, mimeMsg.getAllRecipients() );
+        log.info( "mail server response: " + transport.getLastServerResponse() );
+    }
+}

Added: directory/trunks/triplesec/webapp-activation/src/main/java/org/safehaus/triplesec/activation/ServletUtils.java
URL: http://svn.apache.org/viewvc/directory/trunks/triplesec/webapp-activation/src/main/java/org/safehaus/triplesec/activation/ServletUtils.java?view=auto&rev=486187
==============================================================================
--- directory/trunks/triplesec/webapp-activation/src/main/java/org/safehaus/triplesec/activation/ServletUtils.java (added)
+++ directory/trunks/triplesec/webapp-activation/src/main/java/org/safehaus/triplesec/activation/ServletUtils.java Tue Dec 12 07:23:31 2006
@@ -0,0 +1,106 @@
+/*
+ *  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.safehaus.triplesec.activation;
+
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Enumeration;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+
+/**
+ * Useful servlet utilities.
+ * 
+ * @author <a href="mailto:akarasulu@safehaus.org">Alex Karasulu</a>
+ * @version $Rev$
+ */
+public class ServletUtils 
+{
+	public static void printRequest( HttpServletRequest req, PrintWriter out )
+	{
+		out.println( "-[characterEncoding: " + req.getCharacterEncoding() + "]-" );
+		out.println( "-[contentLength: " + req.getContentLength() + "]-" );
+		out.println( "-[contentType: " + req.getContentType() + "]-" );
+		out.println( "-[contentPath: " + req.getContextPath() + "]-" );
+		out.println( "-[method: " + req.getMethod() + "]-" );
+		out.println( "-[pathInfo: " + req.getPathInfo() + "]-" );
+		out.println( "-[pathTranslated: " + req.getPathTranslated() + "]-" );
+		out.println( "-[protocol: " + req.getProtocol() + "]-" );
+		out.println( "-[queryString: " + req.getQueryString() + "]-" );
+		out.println( "-[remoteAddr" + req.getRemoteAddr() + "]-" );
+		out.println( "-[remoteHost: " + req.getRemoteHost() + "]-" );
+		out.println( "-[remotePort: " + req.getRemotePort() + "]-" );
+		out.println( "-[remoteUser: " + req.getRemoteUser() + "]-" );
+		out.println( "-[requestedSessionId: " + req.getRequestedSessionId() + "]-" );
+		out.println( "-[requestURI: " + req.getRequestURI() + "]-" );
+		out.println( "-[requestURL: " + req.getRequestURL() + "]-" );
+		out.println( "-[scheme: " + req.getScheme() + "]-" );
+		out.println( "-[serverName: " + req.getServerName() + "]-" );
+		out.println( "-[serverPort: " + req.getServerPort() + "]-" );
+		out.println( "-[servletPath: " + req.getServletPath() + "]-" );
+		out.println( "-[locale: " + req.getLocale() + "]-" );
+
+		Enumeration list = req.getAttributeNames();
+		out.println( "--> requestAttributes <-- " );
+		while ( list.hasMoreElements() )
+		{
+			String name = (String) list.nextElement();
+			out.println( "\t-[" + name + ": " + req.getAttribute( name ) + "]-" );
+		}
+		
+		list = req.getParameterNames();
+		out.println( "--> parameters <-- " );
+		while ( list.hasMoreElements() )
+		{
+			String name = (String) list.nextElement();
+			out.println( "\t-[" + name + ": " + req.getParameter( name ) + "]-" );
+		}
+		
+		list = req.getHeaderNames();
+		out.println( "--> headers <-- " );
+		while ( list.hasMoreElements() )
+		{
+			String name = (String) list.nextElement();
+			out.println( "\t-[" + name + ": " + req.getHeader( name ) + "]-" );
+		}
+	}
+	
+    
+    public static String getNotNull( HttpServletRequest req, HttpServletResponse resp, String param ) throws ServletException, IOException
+    {
+        String value = req.getParameter( param );
+
+        if ( value == null )
+        {
+            String msg = "error: value for parameter " + param + " was not found";
+            resp.getWriter().print( msg );
+            resp.getWriter().close();
+            throw new ServletException( msg );
+        }
+        else
+        {
+            return value;
+        }
+    }
+}