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 [23/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/store/pom.xml
URL: http://svn.apache.org/viewvc/directory/trunks/triplesec/store/pom.xml?view=auto&rev=486187
==============================================================================
--- directory/trunks/triplesec/store/pom.xml (added)
+++ directory/trunks/triplesec/store/pom.xml Tue Dec 12 07:23:31 2006
@@ -0,0 +1,173 @@
+<?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-store</artifactId>
+  <name>Triplesec Store</name>
+  <packaging>jar</packaging>  
+  <dependencies>
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>triplesec-testdata</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>triplesec-profile</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>triplesec-jaas</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>nlog4j</artifactId>
+      <version>1.2.25</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.directory.server</groupId>
+      <artifactId>apacheds-kerberos-shared</artifactId>
+      <version>1.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.directory.server</groupId>
+      <artifactId>apacheds-core</artifactId>
+      <version>1.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.directory.shared</groupId>
+      <artifactId>shared-ldap</artifactId>
+      <version>0.9.5.3-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.directory.server</groupId>
+      <artifactId>apacheds-core-unit</artifactId>
+      <version>1.0-SNAPSHOT</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.directory.server</groupId>
+        <artifactId>apacheds-core-plugin</artifactId>
+        <version>1.0-SNAPSHOT</version>
+        <configuration>
+          <schemaSourcesDir>src/main/schema</schemaSourcesDir>
+          <schemas>
+            <schema>
+              <name>safehaus</name>
+              <pkg>org.safehaus.triplesec.store.schema</pkg>
+              <dependencies>
+                <dependency>system</dependency>
+                <dependency>core</dependency>
+                <dependency>cosine</dependency>
+              </dependencies>
+            </schema>
+          </schemas>
+        </configuration>
+        <executions>
+          <execution>
+            <goals>
+              <goal>generate</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+
+  <profiles>
+    <profile>
+      <id>no-integration-tests</id>
+      <activation>
+        <activeByDefault>true</activeByDefault>
+      </activation>
+      <build>
+        <plugins>
+           <plugin>
+              <artifactId>maven-surefire-plugin</artifactId>
+              <configuration>
+                <excludes>
+                  <exclude>**/*ITest.java</exclude>
+                  <exclude>**/*IntegrationTest.java</exclude>
+                </excludes>
+              </configuration>
+            </plugin>
+            <plugin>
+              <artifactId>maven-antrun-plugin</artifactId>
+              <executions>
+                <execution>
+                  <phase>validate</phase>
+                  <configuration>
+                    <tasks>
+                      <echo>
+=================================================================
+                          W A R N I N G
+                          -------------
+
+Integration tests have been disabled.  To enable integration
+tests run maven with the -Dintegration switch.
+=================================================================
+                      </echo>
+                    </tasks>
+                  </configuration>
+                  <goals>
+                    <goal>run</goal>
+                  </goals>
+                </execution>
+              </executions>
+            </plugin>
+         </plugins>
+       </build>
+    </profile>
+    <profile>
+      <id>integration</id>
+      <activation>
+        <property><name>integration</name></property>
+      </activation>
+      <build>
+        <plugins>
+          <plugin>
+            <artifactId>maven-surefire-plugin</artifactId>
+            <configuration>
+              <systemProperties>
+                <property>
+                  <name>workingDirectory</name>
+                  <value>${basedir}/target/server-work</value>
+                </property>
+              </systemProperties>
+            </configuration>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+  </profiles>
+
+</project>

Added: directory/trunks/triplesec/store/src/main/java/org/safehaus/triplesec/store/DefaultServerProfileStore.java
URL: http://svn.apache.org/viewvc/directory/trunks/triplesec/store/src/main/java/org/safehaus/triplesec/store/DefaultServerProfileStore.java?view=auto&rev=486187
==============================================================================
--- directory/trunks/triplesec/store/src/main/java/org/safehaus/triplesec/store/DefaultServerProfileStore.java (added)
+++ directory/trunks/triplesec/store/src/main/java/org/safehaus/triplesec/store/DefaultServerProfileStore.java Tue Dec 12 07:23:31 2006
@@ -0,0 +1,320 @@
+/*
+ *  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.store;
+
+
+import java.util.ArrayList;
+
+import javax.naming.directory.*;
+import javax.naming.NamingException;
+import javax.naming.NamingEnumeration;
+import javax.naming.OperationNotSupportedException;
+import javax.security.auth.kerberos.KerberosPrincipal;
+
+import org.apache.directory.shared.ldap.NotImplementedException;
+import org.apache.directory.shared.ldap.name.LdapDN;
+import org.apache.directory.shared.ldap.message.LockableAttributesImpl;
+import org.apache.directory.shared.ldap.message.LockableAttributeImpl;
+
+import org.safehaus.profile.ServerProfile;
+
+
+/**
+ * The default Safehaus store implementation which is backed by the ApacheDS.
+ *
+ * @author <a href="mailto:akarasulu@safehaus.org">Alex Karasulu</a>
+ * @version $Rev$
+ */
+public class DefaultServerProfileStore implements ServerProfileStore
+{
+    /** Empty array of modification items so we do not create on every time */
+    private static final ModificationItem[] EMPTY = new ModificationItem[0];
+
+    /** temporary property to lookup alternate monitor */
+    public static final String MONITOR_PROP = "org.safehaus.store.monitor";
+
+    // ------------------------------------------------------------------------
+    // Members
+    // ------------------------------------------------------------------------
+
+    /** the context under which users are created */
+    private DirContext userContext;
+
+    /** the StoreMonitor used to report notable events to */
+    private StoreMonitor monitor = new StoreMonitorAdapter();
+
+    // ------------------------------------------------------------------------
+    // C O N S T R U C T O R S
+    // ------------------------------------------------------------------------
+
+    /**
+     * Creates the embedded ApacheDS principal store.
+     *
+     * @param userContext the context under which users are created.
+     */
+    public DefaultServerProfileStore( DirContext userContext ) throws NamingException
+    {
+        this.userContext = userContext;
+    }
+
+
+    // ------------------------------------------------------------------------
+    // ServerProfileStore methods
+    // ------------------------------------------------------------------------
+
+
+    public void init() throws NamingException
+    {
+    }
+
+
+    private ProfileObjectFactory objectFactory = new ProfileObjectFactory();
+    
+    public ServerProfile getProfile( KerberosPrincipal principal ) throws NamingException
+    {
+        Attributes attributes = new LockableAttributesImpl();
+        attributes.put( PRINCIPAL_ATTR, principal.getName() );
+        SearchResult result = null;
+        Attributes attrs = null;
+
+        NamingEnumeration list = userContext.search( "", attributes );
+        if ( list.hasMore() )
+        {
+            result = ( SearchResult ) list.next();
+            attrs = result.getAttributes();
+        }
+        list.close();
+        
+        if ( attrs == null || result == null )
+        {
+            monitor.storeFailure( this, principal, "principal not in store" );
+            return null;
+        }
+        
+        Object obj = objectFactory.getObjectInstance( null, null, null, null, attrs );
+        if ( obj instanceof ServerProfile )
+        {
+            monitor.profileAccessed( this, ( ServerProfile ) obj );
+            return ( ServerProfile ) obj;
+        }
+
+        monitor.storeFailure( this, principal, "failed to recognize principal type" );
+        return null;
+    }
+
+
+    private SearchResult getProfileEntry( KerberosPrincipal principal ) throws NamingException
+    {
+        Attributes attributes = new LockableAttributesImpl();
+        attributes.put( PRINCIPAL_ATTR, principal.getName() );
+        SearchResult result = null;
+        NamingEnumeration list = userContext.search( "", attributes );
+        if ( list.hasMore() )
+        {
+            result = ( SearchResult ) list.next();
+        }
+        list.close();
+        return result;
+    }
+
+
+    public boolean hasProfile( KerberosPrincipal principal ) throws NamingException
+    {
+        Attributes attributes = new LockableAttributesImpl();
+        attributes.put( PRINCIPAL_ATTR, principal.getName() );
+        SearchResult result = null;
+        Attributes attrs = null;
+        NamingEnumeration list = userContext.search( "", attributes );
+
+        if ( list.hasMore() )
+        {
+            result = ( SearchResult ) list.next();
+            attrs = result.getAttributes();
+        }
+        list.close();
+
+        if ( attrs == null || result == null )
+        {
+            return false;
+        }
+
+        return true;
+    }
+
+
+    public void add( ServerProfile profile ) throws NamingException
+    {
+        userContext.bind( "uid=" + profile.getUserId(), profile );
+        monitor.profileAdded( this, profile );
+    }
+
+
+    public void delete( KerberosPrincipal principal ) throws NamingException
+    {
+        String msg = "delete in org.safehaus.triplesec.store.DefaultServerProfileStore not implemented!";
+        throw new NotImplementedException( msg );
+    }
+
+
+    public void update( KerberosPrincipal oldPrincipal, ServerProfile updated ) throws NamingException
+    {
+        String oldId = oldPrincipal.getName();
+        oldId = oldId.split( "@" )[0];
+        String oldRealm = oldPrincipal.getName();
+        oldRealm = oldRealm.split( "@" )[1];
+
+        if ( ! oldId.equals( updated.getUserId() ) || ! oldRealm.equals( updated.getRealm() ) )
+        {
+            String msg = "Attempt to move or rename existing profile not yet supported!";
+            OperationNotSupportedException onse = new OperationNotSupportedException( msg );
+            monitor.storeFailure( this, onse );
+            throw onse;
+        }
+
+        ArrayList list = new ArrayList();  // list of modification items
+        SearchResult result = getProfileEntry( oldPrincipal );
+        Attributes original = result.getAttributes();
+
+        if ( updated.getFactor() != getLong( "safehausFactor", original ) )
+        {
+            Attribute attr = new LockableAttributeImpl( "safehausFactor" );
+            attr.add( Long.toString( updated.getFactor() ) );
+            list.add( new ModificationItem( DirContext.REPLACE_ATTRIBUTE, attr ) );
+        }
+
+        if ( updated.getResynchCount() != getInt( "safehausResynchCount", original ) )
+        {
+            Attribute attr = new LockableAttributeImpl( "safehausResynchCount" );
+            attr.add( Integer.toString( updated.getResynchCount() ) );
+            list.add( new ModificationItem( DirContext.REPLACE_ATTRIBUTE, attr ) );
+        }
+
+        if ( updated.getFailuresInEpoch() != getInt( "safehausFailuresInEpoch", original ) )
+        {
+            Attribute attr = new LockableAttributeImpl( "safehausFailuresInEpoch" );
+            attr.add( Integer.toString( updated.getFailuresInEpoch() ) );
+            list.add( new ModificationItem( DirContext.REPLACE_ATTRIBUTE, attr ) );
+        }
+
+        if ( ! updated.getUserId().equals( getString( "safehausUid", original ) ) )
+        {
+            Attribute attr = new LockableAttributeImpl( "safehausUid" );
+            attr.add( updated.getUserId() );
+            list.add( new ModificationItem( DirContext.REPLACE_ATTRIBUTE, attr ) );
+        }
+
+        if ( ! updated.getRealm().equals( getString( "safehausRealm", original ) ) )
+        {
+            Attribute attr = new LockableAttributeImpl( "safehausRealm" );
+            attr.add( updated.getRealm() );
+            list.add( new ModificationItem( DirContext.REPLACE_ATTRIBUTE, attr ) );
+        }
+
+        if ( ! updated.getLabel().equals( getString( "safehausLabel", original ) ) )
+        {
+            Attribute attr = new LockableAttributeImpl( "safehausLabel" );
+            attr.add( updated.getLabel() );
+            list.add( new ModificationItem( DirContext.REPLACE_ATTRIBUTE, attr ) );
+        }
+
+        if ( ! updated.getSecret().equals( getString( "safehausSecret", original ) ) )
+        {
+            Attribute attr = new LockableAttributeImpl( "safehausSecret" );
+            attr.add( updated.getSecret() );
+            list.add( new ModificationItem( DirContext.REPLACE_ATTRIBUTE, attr ) );
+        }
+
+        if ( updated.getInfo() != null && ! updated.getInfo().equals( getString( "safehausInfo", original ) ) )
+        {
+            Attribute attr = new LockableAttributeImpl( "safehausInfo" );
+            attr.add( updated.getInfo() );
+            list.add( new ModificationItem( DirContext.REPLACE_ATTRIBUTE, attr ) );
+        }
+
+        if ( result.getObject() instanceof DirContext )
+        {
+            DirContext entryCtx = ( DirContext ) result.getObject();
+            entryCtx.modifyAttributes( "", ( ModificationItem[] ) list.toArray( EMPTY ) );
+            return;
+        }
+
+        LdapDN base = new LdapDN( userContext.getNameInNamespace() );
+        LdapDN dn = new LdapDN( result.getName() );
+        LdapDN rdn = ( LdapDN ) dn.getSuffix( base.size() );
+        ModificationItem[] mods = ( ModificationItem[] ) list.toArray( EMPTY );
+        userContext.modifyAttributes( rdn, mods );
+        monitor.profileUpdated( this, updated, mods );
+    }
+
+
+    public void setMonitor( StoreMonitor monitor )
+    {
+        this.monitor = monitor;
+    }
+
+
+    // ------------------------------------------------------------------------
+    // private utility methods
+    // ------------------------------------------------------------------------
+
+
+    private long getLong( String id, Attributes attrs ) throws NamingException
+    {
+        Attribute attr = attrs.get( id );
+        if ( attr == null || attr.size() == 0 )
+        {
+            throw new NamingException( "Attribute not found or does not have a value!" );
+        }
+        return Long.parseLong( ( String ) attr.get() );
+    }
+
+
+    private int getInt( String id, Attributes attrs ) throws NamingException
+    {
+        Attribute attr = attrs.get( id );
+        if ( attr == null || attr.size() == 0 )
+        {
+            throw new NamingException( "Attribute not found or does not have a value!" );
+        }
+        return Integer.parseInt( ( String ) attr.get() );
+    }
+
+
+    private String getString( String id, Attributes attrs ) throws NamingException
+    {
+        Attribute attr = attrs.get( id );
+
+        if ( attr == null || attr.size() == 0 )
+        {
+            throw new NamingException( "Attribute not found or does not have a value!" );
+        }
+
+        if ( attr.get() instanceof String )
+        {
+            return ( String ) attr.get();
+        }
+        else if ( attr.get() instanceof byte[] )
+        {
+            return new String( ( byte[] ) attr.get() );
+        }
+
+        return attr.get().toString();
+    }
+}

Added: directory/trunks/triplesec/store/src/main/java/org/safehaus/triplesec/store/ProfileObjectFactory.java
URL: http://svn.apache.org/viewvc/directory/trunks/triplesec/store/src/main/java/org/safehaus/triplesec/store/ProfileObjectFactory.java?view=auto&rev=486187
==============================================================================
--- directory/trunks/triplesec/store/src/main/java/org/safehaus/triplesec/store/ProfileObjectFactory.java (added)
+++ directory/trunks/triplesec/store/src/main/java/org/safehaus/triplesec/store/ProfileObjectFactory.java Tue Dec 12 07:23:31 2006
@@ -0,0 +1,116 @@
+/*
+ *  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.store;
+
+
+import java.util.Hashtable;
+
+import javax.naming.Name;
+import javax.naming.Context;
+import javax.naming.NamingException;
+import javax.naming.directory.Attributes;
+import javax.naming.spi.DirObjectFactory;
+
+import org.apache.directory.server.kerberos.shared.store.KerberosAttribute;
+import org.apache.directory.shared.ldap.util.StringTools;
+import org.safehaus.profile.BaseServerProfileModifier;
+
+
+/**
+ * An ObjectFactory that resusitates objects from directory attributes.
+ *
+ * @author <a href="mailto:akarasulu@safehaus.org">Alex Karasulu</a>
+ * @version $Rev$
+ */
+public class ProfileObjectFactory implements DirObjectFactory
+{
+    private static boolean parseBoolean( String bool )
+    {
+        if ( bool.toLowerCase().equals( "true" ) )
+        {
+            return true;
+        }
+        
+        return false;
+    }
+    
+    
+    public Object getObjectInstance( Object obj, Name name, Context nameCtx, Hashtable environment, Attributes attrs ) throws NamingException
+    {
+        if ( attrs == null || attrs.get( "objectClass" ) == null || ! attrs.get( "objectClass" ).contains( "safehausProfile" ) )
+        {
+            return null;
+        }
+
+        BaseServerProfileModifier modifier = new BaseServerProfileModifier();
+        modifier.setUserId( ( String ) attrs.get( "safehausUid" ).get() );
+        modifier.setRealm( ( String ) attrs.get( "safehausRealm" ).get() );
+        modifier.setLabel( ( String ) attrs.get( "safehausLabel" ).get() );
+        modifier.setTokenPin( ( String ) attrs.get( "safehausTokenPin" ).get() );
+        modifier.setFactor( Long.parseLong( ( String ) attrs.get( "safehausFactor" ).get() ) );
+        
+        if ( attrs.get( KerberosAttribute.ACCOUNT_DISABLED ) != null )
+        {
+            modifier.setDisabled( parseBoolean( ( ( String ) 
+                attrs.get( KerberosAttribute.ACCOUNT_DISABLED ).get() ).toLowerCase() ) );
+        }
+
+        Object secret = attrs.get( "safehausSecret" ).get();
+        if ( secret instanceof String )
+        {
+            modifier.setSecret( StringTools.getBytesUtf8( ( String ) secret ) );
+        }
+        else
+        {
+            modifier.setSecret( ( byte[] ) secret );
+        }
+
+        Object password = attrs.get( "userPassword" ).get();
+        if ( password instanceof String )
+        {
+            modifier.setPassword( StringTools.getBytesUtf8( ( String ) password ) );
+        }
+        else
+        {
+            modifier.setPassword( ( byte[] ) password );
+        }
+        
+        modifier.setFailuresInEpoch( Integer.parseInt( ( String ) attrs.get( "safehausFailuresInEpoch" ).get() ) );
+        modifier.setResynchCount( Integer.parseInt( ( String ) attrs.get( "safehausResynchCount" ).get() ) );
+
+        if ( attrs.get( "safehausInfo" ) != null )
+        {
+            modifier.setInfo( ( String ) attrs.get( "safehausInfo" ).get() );
+        }
+
+        if ( attrs.get( "safehausActivationKey" ) != null )
+        {
+            modifier.setActivationKey( ( String ) attrs.get( "safehausActivationKey" ).get() );
+        }
+
+        return modifier.getServerProfile();
+    }
+
+
+    public Object getObjectInstance( Object obj, Name name, Context nameCtx, Hashtable environment ) throws Exception
+    {
+        throw new UnsupportedOperationException( "Attributes required to resusitate an OTP account!" );
+    }
+}

Added: directory/trunks/triplesec/store/src/main/java/org/safehaus/triplesec/store/ProfileStateFactory.java
URL: http://svn.apache.org/viewvc/directory/trunks/triplesec/store/src/main/java/org/safehaus/triplesec/store/ProfileStateFactory.java?view=auto&rev=486187
==============================================================================
--- directory/trunks/triplesec/store/src/main/java/org/safehaus/triplesec/store/ProfileStateFactory.java (added)
+++ directory/trunks/triplesec/store/src/main/java/org/safehaus/triplesec/store/ProfileStateFactory.java Tue Dec 12 07:23:31 2006
@@ -0,0 +1,196 @@
+/*
+ *  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.store;
+
+
+import java.util.Hashtable;
+
+import javax.naming.Name;
+import javax.naming.Context;
+import javax.naming.NamingException;
+import javax.naming.NamingEnumeration;
+import javax.naming.spi.DirStateFactory;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttribute;
+import javax.naming.directory.BasicAttributes;
+import javax.naming.directory.Attribute;
+import javax.security.auth.kerberos.KerberosPrincipal;
+import javax.security.auth.kerberos.KerberosKey;
+
+import org.safehaus.profile.ServerProfile;
+import org.apache.directory.server.kerberos.shared.store.KerberosAttribute;
+import org.apache.directory.shared.ldap.message.LockableAttributeImpl;
+
+
+/**
+ * A StateFactory for a server profile.
+ *
+ * @author <a href="mailto:akarasulu@safehaus.org">Alex Karasulu</a>
+ * @version $Rev$
+ */
+public class ProfileStateFactory implements DirStateFactory
+{
+    /** the krb5kdc schema key for a krb5KDCEntry */
+    private static final String KEY_ATTR = "krb5Key";
+
+    /** the krb5kdc schema key encryption type for a krb5KDCEntry */
+    private static final String TYPE_ATTR = "krb5EncryptionType";
+
+    /** the krb5kdc schema principal name for a krb5KDCEntry */
+    private static final String PRINCIPAL_ATTR = "krb5PrincipalName";
+
+    /** the krb5kdc schema key version identifier for a krb5KDCEntry */
+    private static final String VERSION_ATTR   = "krb5KeyVersionNumber";
+
+
+    public Result getStateToBind( Object obj, Name name, Context nameCtx, Hashtable environment, Attributes inAttrs ) throws NamingException
+    {
+        ServerProfile p = ( ServerProfile ) obj;
+
+        Attributes outAttrs = new BasicAttributes( true );
+
+        if ( inAttrs != null )
+        {
+            NamingEnumeration list = inAttrs.getIDs();
+
+            while ( list.hasMore() )
+            {
+                String id = ( String ) list.next();
+
+                outAttrs.put( ( Attribute ) inAttrs.get( id ).clone() );
+            }
+        }
+
+        // process the objectClass attribute
+
+        Attribute oc = outAttrs.get( "objectClass" );
+
+        if ( oc == null )
+        {
+            oc = new LockableAttributeImpl( "objectClass" );
+
+            outAttrs.put( oc );
+        }
+
+        if ( ! oc.contains( "top" ) )
+        {
+            oc.add( "top" );
+        }
+
+        if ( ! oc.contains( "uidObject" ) )
+        {
+            oc.add( "uidObject" );
+
+            outAttrs.put( "uid", p.getUserId() );
+        }
+
+        if ( ! oc.contains( "extensibleObject" ) )
+        {
+            oc.add( "extensibleObject" );
+
+            outAttrs.put( "apacheSamType", "7" );
+        }
+
+        if ( ! oc.contains( "person" ) )
+        {
+            oc.add( "person" );
+
+            // @todo look into adding sn and cn to ServerProfiles
+            // shoot I think we're going to need to have these properties to
+            // enforce person usage - or we can blow chunks ????
+
+            outAttrs.put( "sn", p.getUserId() );
+
+            outAttrs.put( "cn", p.getUserId() );
+        }
+
+        if ( ! oc.contains( "organizationalPerson" ) )
+        {
+            oc.add( "organizationalPerson" );
+        }
+
+        if ( ! oc.contains( "inetOrgPerson" ) )
+        {
+            oc.add( "inetOrgPerson" );
+        }
+
+        if ( ! oc.contains( "krb5KDCEntry" ) )
+        {
+            oc.add( "krb5KDCEntry" );
+            String pw = p.getUserId();
+
+            if ( p.getPassword() == null )
+            {
+                outAttrs.put( "userpassword", p.getUserId() );
+            }
+
+            StringBuffer buf = new StringBuffer();
+            buf.append( p.getUserId() );
+            buf.append( "@" );
+            buf.append( p.getRealm() );
+            KerberosPrincipal principal = new KerberosPrincipal( buf.toString() );
+
+            KerberosKey key = new KerberosKey( principal, pw.toCharArray(), "DES" );
+            outAttrs.put( PRINCIPAL_ATTR, principal.getName() );
+            byte[] encodedKey = key.getEncoded();
+            outAttrs.put( KEY_ATTR, encodedKey );
+            outAttrs.put( VERSION_ATTR, Integer.toString( key.getVersionNumber() ) );
+            outAttrs.put( TYPE_ATTR, Integer.toString( key.getKeyType() ) );
+        }
+
+        if ( ! oc.contains( "safehausProfile" ) )
+        {
+            oc.add( "safehausProfile" );
+        }
+
+        // process the Profile specific attributes
+
+        outAttrs.put( new LockableAttributeImpl( "safehausUid", p.getUserId() ) );
+        outAttrs.put( new LockableAttributeImpl( "safehausRealm", p.getRealm() ) );
+        outAttrs.put( new LockableAttributeImpl( "safehausFactor", String.valueOf( p.getFactor() ) ) );
+        outAttrs.put( new LockableAttributeImpl( "safehausSecret", p.getSecret() ) );
+        outAttrs.put( new LockableAttributeImpl( "safehausLabel", p.getLabel() ) );
+        outAttrs.put( new LockableAttributeImpl( "safehausTokenPin", p.getTokenPin() ) );
+        outAttrs.put( new LockableAttributeImpl( "safehausNotifyBy", p.getNotifyBy() ) );
+        outAttrs.put( new LockableAttributeImpl( KerberosAttribute.ACCOUNT_DISABLED, String.valueOf( p.isDisabled() ).toUpperCase() ) );
+        outAttrs.put( new LockableAttributeImpl( "userPassword", p.getPassword() ) );
+
+        if ( p.getActivationKey() != null )
+        {
+        	outAttrs.put( new LockableAttributeImpl( "safehausActivationKey", p.getActivationKey() ) );
+        }
+        
+        outAttrs.put( new LockableAttributeImpl( "safehausResynchCount", Integer.toString( p.getResynchCount() ) ) );
+        outAttrs.put( new LockableAttributeImpl( "safehausFailuresInEpoch", Integer.toString( p.getFailuresInEpoch() ) ) );
+        if ( p.getInfo() != null )
+        {
+            outAttrs.put( new BasicAttribute( "safehausInfo", p.getInfo() ) );
+        }
+
+        Result r = new Result( obj, outAttrs );
+        return r;
+    }
+
+
+    public Object getStateToBind( Object obj, Name name, Context nameCtx, Hashtable environment ) throws NamingException
+    {
+        throw new UnsupportedOperationException( "Structural objectClass needed for safehausProfile within additional attributes!" );
+    }
+}

Added: directory/trunks/triplesec/store/src/main/java/org/safehaus/triplesec/store/ServerProfileStore.java
URL: http://svn.apache.org/viewvc/directory/trunks/triplesec/store/src/main/java/org/safehaus/triplesec/store/ServerProfileStore.java?view=auto&rev=486187
==============================================================================
--- directory/trunks/triplesec/store/src/main/java/org/safehaus/triplesec/store/ServerProfileStore.java (added)
+++ directory/trunks/triplesec/store/src/main/java/org/safehaus/triplesec/store/ServerProfileStore.java Tue Dec 12 07:23:31 2006
@@ -0,0 +1,96 @@
+/*
+ *  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.store;
+
+
+import javax.naming.NamingException;
+import javax.security.auth.kerberos.KerberosPrincipal;
+
+import org.safehaus.profile.ServerProfile;
+
+
+/**
+ * A server store's interface.
+ *
+ * @author <a href="mailto:akarasulu@safehaus.org">Alex Karasulu</a>
+ * @version $Rev$
+ */
+public interface ServerProfileStore
+{
+    /** the krb5kdc schema principal name for a krb5KDCEntry */
+    String PRINCIPAL_ATTR = "krb5PrincipalName";
+
+    /**
+     * The store might need to be initialized before use.  We recommend calling
+     * this method before making calls to other methods.
+     */
+    public void init() throws NamingException;
+
+    /**
+     * Gets a server's Hotp account profile.
+     *
+     * @param principal the KerberosPrincipal associated with the profile
+     * @return the Hotp ServerProfile
+     * @throws javax.naming.NamingException if the Profile cannot be accessed
+     */
+    ServerProfile getProfile( KerberosPrincipal principal ) throws NamingException;
+
+    /**
+     * Checks to see if a Profile exists within a realm in the Profile store.
+     *
+     * @param principal the KerberosPrincipal associated with the profile
+     * @return true if the Profile exists within the store, false if it does not
+     * @throws javax.naming.NamingException if the store is not available
+     */
+    public boolean hasProfile( KerberosPrincipal principal  ) throws NamingException;
+
+    /**
+     * Adds a hotp account Profile to the store.
+     *
+     * @param profile the account ServerProfile to add
+     * @throws javax.naming.NamingException if the Profile cannot be added
+     */
+    public void add( ServerProfile profile ) throws NamingException;
+
+    /**
+     * Deletes an item by name within the list and on the store.
+     *
+     * @param principal the KerberosPrincipal associated with the profile
+     * @throws javax.naming.NamingException if the ServerProfile cannot be deleted
+     */
+    public void delete( KerberosPrincipal principal ) throws NamingException;
+
+    /**
+     * Updates the store by removing the old copy of the profile and creating
+     * the new one.  So really this is a simple delete and add operation.
+     *
+     * @param oldPrincipal the original KerberosPrincipal for the profile
+     * @param updated the altered profile
+     */
+    public void update( KerberosPrincipal oldPrincipal, ServerProfile updated ) throws NamingException;
+
+    /**
+     * Sets the monitor whose callbacks are invoked to notify of events in the
+     * store.
+     *
+     * @param monitor the monitor to set for the store
+     */
+    public void setMonitor( StoreMonitor monitor );
+}

Added: directory/trunks/triplesec/store/src/main/java/org/safehaus/triplesec/store/StoreMonitor.java
URL: http://svn.apache.org/viewvc/directory/trunks/triplesec/store/src/main/java/org/safehaus/triplesec/store/StoreMonitor.java?view=auto&rev=486187
==============================================================================
--- directory/trunks/triplesec/store/src/main/java/org/safehaus/triplesec/store/StoreMonitor.java (added)
+++ directory/trunks/triplesec/store/src/main/java/org/safehaus/triplesec/store/StoreMonitor.java Tue Dec 12 07:23:31 2006
@@ -0,0 +1,65 @@
+/*
+ *  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.store;
+
+
+import javax.security.auth.kerberos.KerberosPrincipal;
+import javax.naming.directory.ModificationItem;
+import javax.naming.directory.Attributes;
+
+import org.safehaus.profile.ServerProfile;
+import org.safehaus.triplesec.store.ServerProfileStore;
+
+
+/**
+ * A monitor for a ServerProfile store which receives callbacks to represent
+ * notable events within a ServerProfileStore.
+ *
+ * @author <a href="mailto:directory-dev@incubator.apache.org">Apache Directory Project</a>
+ * @version $Rev$
+ */
+public interface StoreMonitor
+{
+    void profileAdded( ServerProfileStore store, ServerProfile profile );
+
+    void profileDeleted( ServerProfileStore store, ServerProfile profile );
+
+    void profileAccessed( ServerProfileStore store, ServerProfile profile );
+
+    void profileUpdated( ServerProfileStore store, ServerProfile profile, ModificationItem[] mods );
+
+    void profileImported( ServerProfileStore store, String dn, Attributes attributes );
+
+    void profileNotImported( ServerProfileStore store, String dn, Attributes attributes );
+
+    void info( ServerProfileStore store, String s );
+
+    void storeInitialized( ServerProfileStore store );
+
+    void storeFailure( ServerProfileStore store );
+
+    void storeFailure( ServerProfileStore store, Throwable t );
+
+    void storeFailure( ServerProfileStore store, KerberosPrincipal principal );
+
+    void storeFailure( ServerProfileStore store, KerberosPrincipal principal, Throwable t );
+
+    void storeFailure( ServerProfileStore store, KerberosPrincipal principal, String s );
+}

Added: directory/trunks/triplesec/store/src/main/java/org/safehaus/triplesec/store/StoreMonitorAdapter.java
URL: http://svn.apache.org/viewvc/directory/trunks/triplesec/store/src/main/java/org/safehaus/triplesec/store/StoreMonitorAdapter.java?view=auto&rev=486187
==============================================================================
--- directory/trunks/triplesec/store/src/main/java/org/safehaus/triplesec/store/StoreMonitorAdapter.java (added)
+++ directory/trunks/triplesec/store/src/main/java/org/safehaus/triplesec/store/StoreMonitorAdapter.java Tue Dec 12 07:23:31 2006
@@ -0,0 +1,115 @@
+/*
+ *  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.store;
+
+
+import javax.security.auth.kerberos.KerberosPrincipal;
+import javax.naming.directory.ModificationItem;
+import javax.naming.directory.Attributes;
+
+import org.safehaus.profile.ServerProfile;
+import org.safehaus.triplesec.store.ServerProfileStore;
+import org.safehaus.triplesec.store.StoreMonitor;
+
+
+/**
+ * An adapter for the StoreMonitor interface.
+ *
+ * @author <a href="mailto:directory-dev@incubator.apache.org">Apache Directory Project</a>
+ * @version $Rev$
+ */
+public class StoreMonitorAdapter implements StoreMonitor
+{
+    public void profileAdded( ServerProfileStore store, ServerProfile profile )
+    {
+    }
+
+
+    public void profileDeleted( ServerProfileStore store, ServerProfile profile )
+    {
+    }
+
+
+    public void profileAccessed( ServerProfileStore store, ServerProfile profile )
+    {
+    }
+
+
+    public void profileUpdated( ServerProfileStore store, ServerProfile profile, ModificationItem[] mods )
+    {
+    }
+
+
+    public void storeInitialized( ServerProfileStore store )
+    {
+    }
+
+
+    public void storeFailure( ServerProfileStore store )
+    {
+    }
+
+
+    public void storeFailure( ServerProfileStore store, Throwable t )
+    {
+        if ( t == null )
+        {
+            return;
+        }
+
+        t.printStackTrace( System.err );
+    }
+
+
+    public void storeFailure( ServerProfileStore store, KerberosPrincipal principal )
+    {
+    }
+
+
+    public void storeFailure( ServerProfileStore store, KerberosPrincipal principal, Throwable t )
+    {
+        if ( t == null )
+        {
+            return;
+        }
+
+        t.printStackTrace( System.err );
+    }
+
+
+    public void profileImported( ServerProfileStore store, String dn, Attributes attributes )
+    {
+    }
+
+
+    public void profileNotImported( ServerProfileStore store, String dn, Attributes attributes )
+    {
+    }
+
+
+    public void info( ServerProfileStore store, String s )
+    {
+    }
+
+
+    public void storeFailure( ServerProfileStore store, KerberosPrincipal principal, String s )
+    {
+    }
+}

Added: directory/trunks/triplesec/store/src/main/java/org/safehaus/triplesec/store/interceptor/ApplicationAciManager.java
URL: http://svn.apache.org/viewvc/directory/trunks/triplesec/store/src/main/java/org/safehaus/triplesec/store/interceptor/ApplicationAciManager.java?view=auto&rev=486187
==============================================================================
--- directory/trunks/triplesec/store/src/main/java/org/safehaus/triplesec/store/interceptor/ApplicationAciManager.java (added)
+++ directory/trunks/triplesec/store/src/main/java/org/safehaus/triplesec/store/interceptor/ApplicationAciManager.java Tue Dec 12 07:23:31 2006
@@ -0,0 +1,492 @@
+/*
+ *  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.store.interceptor;
+
+
+import org.apache.directory.shared.ldap.message.LockableAttributeImpl;
+import org.apache.directory.shared.ldap.message.LockableAttributesImpl;
+import org.apache.directory.shared.ldap.name.LdapDN;
+import org.apache.directory.shared.ldap.schema.AttributeType;
+import org.apache.directory.shared.ldap.util.AttributeUtils;
+import org.apache.directory.shared.ldap.util.NamespaceTools;
+import org.apache.directory.shared.ldap.exception.LdapNameAlreadyBoundException;
+import org.apache.directory.server.core.invocation.InvocationStack;
+import org.apache.directory.server.core.partition.PartitionNexusProxy;
+import org.apache.directory.server.core.schema.AttributeTypeRegistry;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+
+import java.util.*;
+
+
+/**
+ * Automatically manages ACI creation, deletion and updates when applications are
+ * created, deleted, renamed and moved.
+ *
+ * @author Alex Karasulu
+ * @version $Rev: 64 $, $Date: 2005-11-03 01:43:49 -0500 (Thu, 03 Nov 2005) $
+ */
+public class ApplicationAciManager
+{
+    private static final Logger log = LoggerFactory.getLogger( ApplicationAciManager.class );
+    private static final String APP_ACITAG_SUFFIX = "Aci";
+    private static final String APP_ACITAG_SUFFIX_LOWER = APP_ACITAG_SUFFIX.toLowerCase();
+    private static final String APPADMIN_ACITAG_SUFFIX = "AdminAci";
+    private static final String APPADMIN_GROUP_SUFFIX = "AdminGroup";
+    private static final String APPADMIN_GROUP_SUFFIX_LOWWER = APPADMIN_GROUP_SUFFIX.toLowerCase();
+    private static final String[] RETURN_ADMINROLE = new String[] { "administrativeRole" };
+
+    private static final Collection ADD_BYPASS;
+    private static final Collection DEL_BYPASS;
+    static final Collection LOOKUP_BYPASS;
+
+    static
+    {
+        Collection c = new HashSet();
+        c.add( "normalizationService" );
+        c.add( "authenticationService" );
+        c.add( "defaultAuthorizationService" );
+        c.add( "schemaService" );
+        c.add( "policyProtectionService" );
+        c.add( "collectiveAttributeService" );
+        ADD_BYPASS = Collections.unmodifiableCollection( c );
+        DEL_BYPASS = Collections.unmodifiableCollection( c );
+
+        c = new HashSet();
+        c.addAll( PartitionNexusProxy.LOOKUP_BYPASS );
+        c.add( "policyProtectionService" );
+        LOOKUP_BYPASS = Collections.unmodifiableCollection( c );
+    }
+
+    /** LUT of normalized DNs for existing ou=groups entries of suffixes */
+    private final Set groupsLut = new HashSet();
+    /** LUT of normalized suffix DNs which are already ASCAs */
+    private final Set acsaLut = new HashSet();
+
+    private final AttributeTypeRegistry registry;
+    private final AttributeType administrativeRoleType;
+    
+    
+    public ApplicationAciManager( AttributeTypeRegistry registry ) throws NamingException
+    {
+        this.registry = registry;
+        administrativeRoleType = registry.lookup( "administrativeRole" );
+    }
+    
+
+    /**
+     * Creates an ACIItem in a new subentry and adds it to the topmost AAA for application's to fully
+     * access their subtree.  This method should be invoked immediately after the application entry
+     * is created.
+     *
+     * @param upDn the user provided DN string for the entry being added
+     * @param appDn the normalized DN for the entry being added
+     */
+    public void appAdded( LdapDN appDn ) throws NamingException
+    {
+        // get the current invocation object's proxy to access it's nexus proxy
+        PartitionNexusProxy proxy = InvocationStack.getInstance().peek().getProxy();
+        addApplicationAdminGroup( proxy, appDn );
+        addApplicationSubentry( proxy, appDn );
+    }
+
+
+    /**
+     * Deletes the access control subentry added to the top most AAA for application access.  This
+     * method should be invoked immediately after the application entry is removed.
+     */
+    public void appRemoved( LdapDN appDn ) throws NamingException
+    {
+        // get the current invocation object's proxy to access it's nexus proxy
+        PartitionNexusProxy proxy = InvocationStack.getInstance().peek().getProxy();
+        removeApplicationAdminGroup( proxy, appDn );
+        removeApplicationSubentry( proxy, appDn );
+    }
+
+
+    /**
+     * Adjusts subtree specifications and ACI when applications are renamed or moved in the DIT.  This
+     * operation is a little bit tricky.  Any name change on an application will remove it from the
+     * scope of the AC subentry's subtreeSpecification.  It will also invalidate the ACIItem designed
+     * to grant the application at it's old DN.  The subentry subsystem will automatically remove the
+     * operational attributes for relating the entry at the old DN to this AC subentry.  At this point
+     * it's just best to remove the old AC subentry with it's ACIItem and create the new one.  So this
+     * boil's down to an add and a delete.
+     *
+     * When the number of entries under the application entry are large this will mean a big change.
+     * All entries will have the old subentries operational attributes removed.  The new operational
+     * entries will then be added.  The cost of this change is expensive.  However not that it is a
+     * management operation that happens relatively infrequently.
+     */
+//    public void appNameChange( Name oldDn, String newUpDn, Name newDn ) throws NamingException
+//    {
+//        // get the current invocation object's proxy to access it's nexus proxy
+//        DirectoryPartitionNexusProxy proxy = InvocationStack.getInstance().peek().getProxy();
+//
+//        // we don't need to mess around with deleting and adding the admin group (don't want to loose info either)
+//        removeApplicationSubentry( proxy, oldDn );
+//        addApplicationSubentry( proxy, newUpDn, newDn );
+//    }
+
+
+    private void removeApplicationAdminGroup( PartitionNexusProxy proxy, LdapDN appDn ) throws NamingException
+    {
+        // bypass all interceptors and ask for the partition suffix for this application's entry
+        // use the suffix to build the normalized DN for the administrator group for the application
+        LdapDN suffix = proxy.getSuffix( appDn, PartitionNexusProxy.BYPASS_ALL_COLLECTION );
+        String appName = NamespaceTools.getRdnValue( appDn.get( appDn.size() - 1 ) );
+        LdapDN groupDn = ( LdapDN ) suffix.clone();
+        groupDn.add( "ou=groups" );
+        StringBuffer buf = new StringBuffer();
+        buf.setLength( 0 );
+        buf.append( "cn=" );
+        buf.append( appName.toLowerCase() );
+        buf.append( APPADMIN_GROUP_SUFFIX_LOWWER );
+        groupDn.add( buf.toString() );
+
+        // blow away the group entry
+        groupDn.normalize( registry.getNormalizerMapping() );
+        proxy.delete( groupDn, DEL_BYPASS );
+    }
+
+
+    /**
+     * Adds an administrator group specifically for the application.  The group name is inferred
+     * from the name of the application.
+     *
+     * @param proxy the nexus proxy to perform an add operation if need be
+     * @param appDn the normalized name for the application
+     * @throws NamingException if add operations fail
+     */
+    private void addApplicationAdminGroup( PartitionNexusProxy proxy, LdapDN appDn ) throws NamingException
+    {
+        // bypass all interceptors and ask for the partition suffix for this application's entry
+        // the suffix entry will be used as the administrative point for a ACSA starting at it
+        LdapDN suffix = proxy.getSuffix( appDn, PartitionNexusProxy.BYPASS_ALL_COLLECTION );
+        String appUpName = NamespaceTools.getRdnValue( appDn.getRdn().getUpName() );
+
+        // calculate the names of the group container and create ou=groups if we have to
+        LdapDN groupDn = ( LdapDN ) suffix.clone();
+        groupDn.add( "ou=groups" );
+        groupDn.normalize( registry.getNormalizerMapping() );
+        createGroupsContainer( proxy, groupDn );
+
+        // continue building the name for the new group entry off of ou=groups
+        StringBuffer buf = new StringBuffer();
+        Attribute cnAttr = new LockableAttributeImpl( "cn" );
+        buf.append( appUpName );
+        buf.append( APPADMIN_GROUP_SUFFIX );
+        cnAttr.add( buf.toString() );
+        buf.insert( 0, "cn=" );
+        groupDn.add( buf.toString() );
+        groupDn.normalize( registry.getNormalizerMapping() );
+
+        // create the admin group entry
+        Attributes group = new LockableAttributesImpl();
+        group.put( "objectClass", "top" );
+        group.get( "objectClass" ).add( "groupOfUniqueNames" );
+        group.put( cnAttr );
+        // not need since admin can do anything but we need one member at least
+        group.put( "uniqueMember", "uid=admin,ou=system" );
+        proxy.add( groupDn, group, ADD_BYPASS );
+    }
+
+
+    /**
+     * Creates the group container ou=groups if it does not exist.
+     *
+     * @param proxy the nexus proxy to perform an add operation if need be
+     * @param groupDn the normalized name for ou=groups under a suffix
+     * @throws NamingException if add operations fail
+     */
+    private void createGroupsContainer( PartitionNexusProxy proxy, LdapDN groupDn ) throws NamingException
+    {
+        if ( groupsLut.contains( groupDn.getNormName() ) )
+        {
+            return;
+        }
+
+        Attributes groups = new LockableAttributesImpl();
+        groups.put( "objectClass", "top" );
+        groups.get( "objectClass" ).add( "organizationalUnit" );
+        groups.put( "ou", "Groups" );
+
+        try
+        {
+            proxy.add( groupDn, groups, ADD_BYPASS );
+        }
+        catch ( LdapNameAlreadyBoundException e )
+        {
+            if ( log.isInfoEnabled() )
+            {
+                log.info( "Could not add " + groupDn + " since it exists ... adding it to exists LUT.");
+            }
+        }
+        groupsLut.add( groupDn.getNormName() );
+    }
+
+
+
+    void removeApplicationSubentry( PartitionNexusProxy proxy, LdapDN appDn ) throws NamingException
+    {
+        // bypass all interceptors and ask for the partition suffix for this application's entry
+        // then calculate the normalized dn of the subentry to delete for this application
+        LdapDN suffix = proxy.getSuffix( appDn, PartitionNexusProxy.BYPASS_ALL_COLLECTION );
+        String appName = NamespaceTools.getRdnValue( appDn.get( appDn.size() - 1 ) );
+        StringBuffer buf = new StringBuffer();
+        buf.append( "cn=" );
+        buf.append( appName.toLowerCase() );
+        buf.append( APP_ACITAG_SUFFIX_LOWER );
+        LdapDN subentryDn = ( LdapDN ) suffix.clone();
+        subentryDn.add( buf.toString() );
+
+        // delete the access control subentry
+        subentryDn.normalize( registry.getNormalizerMapping() );
+        proxy.delete( subentryDn, DEL_BYPASS );
+    }
+
+
+    /**
+     * Adds an accessControl subentry to the suffix of the partition containing the entry.  If the suffix
+     * is not the Administrative Point for the ACSA then it is promoted to one.
+     *
+     * @param proxy the nexus proxy to perform an add operation if need be
+     * @param appDn the normalized name for the application entry being added
+     * @throws NamingException if add operations fail
+     */
+    void addApplicationSubentry( PartitionNexusProxy proxy, LdapDN appDn ) throws NamingException
+    {
+        // bypass all interceptors and ask for the partition suffix for this application's entry
+        // the suffix entry will be used as the administrative point for a ACSA starting at it
+        LdapDN suffix = proxy.getSuffix( appDn, PartitionNexusProxy.BYPASS_ALL_COLLECTION );
+        String appUpName = NamespaceTools.getRdnValue( appDn.getRdn().getUpName() );
+        String appName = NamespaceTools.getRdnValue( appDn.get( appDn.size() - 1 ) );
+        createAccessControlArea( proxy, suffix );
+
+        // calculate the application admin group name for the application
+        LdapDN groupDn = ( LdapDN ) suffix.clone();
+        groupDn.add( "ou=Groups" );
+        StringBuffer groupRdn = new StringBuffer();
+        groupRdn.append( "cn=" );
+        groupRdn.append( appUpName );
+        groupRdn.append( APPADMIN_GROUP_SUFFIX );
+        groupDn.add( groupRdn.toString() );
+        groupDn.normalize( registry.getNormalizerMapping() );
+
+        // calculate the name for the new subentry to create
+        StringBuffer buf = new StringBuffer();
+        buf.append( appName );
+        buf.append( APP_ACITAG_SUFFIX );
+        String aciTag = buf.toString();
+
+        // calculate subentry attributes with both app user ACI and 
+        // app admin group ACI in same subentry
+        Attributes subentry = new LockableAttributesImpl();
+        subentry.put( "objectClass", "top" );
+        subentry.get( "objectClass" ).add( "subentry" );
+        subentry.get( "objectClass" ).add( "accessControlSubentry" );
+        subentry.put( "cn", aciTag );
+        subentry.put( "subtreeSpecification", createSubtreeSpecification( suffix, appDn ) );
+        subentry.put( "prescriptiveACI", createApplicationACIItem( aciTag, appDn ) );
+        
+        // now add another prescriptiveACI to the same subentry to allow 
+        // read/write access by the admin group
+        buf.setLength( 0 );
+        buf.append( appName );
+        buf.append( APPADMIN_ACITAG_SUFFIX );
+        subentry.get( "prescriptiveACI" ).add( createApplicationAdminACIItem( buf.toString(), groupDn ) );
+
+        // create the subentry
+        buf.setLength( 0 );
+        buf.append( "cn=" );
+        buf.append( appUpName );
+        buf.append( APP_ACITAG_SUFFIX );
+        LdapDN subentryDn = ( LdapDN ) suffix.clone();
+        subentryDn.add( buf.toString() );
+        subentryDn.normalize( registry.getNormalizerMapping() );
+        proxy.add( subentryDn, subentry, ADD_BYPASS );
+    }
+
+
+    /**
+     * Checks cache to see if the entry at apDn is an access control specific area (ACSA), if
+     * not the entry is accessed to check if it is an administrative point for an ACSA.  If
+     * the entry is an ACSA AP, then the cache is updated.  If the entry is NOT an ACSA AP then
+     * the entry at apDn is promoted to an ACSA.
+     *
+     * @param apDn
+     * @throws NamingException
+     */
+    private void createAccessControlArea( PartitionNexusProxy proxy, LdapDN apDn ) throws NamingException
+    {
+        if ( acsaLut.contains( apDn.getNormName() ) )
+        {
+            return;
+        }
+
+        Attributes acsa = proxy.lookup( apDn, RETURN_ADMINROLE, LOOKUP_BYPASS );
+        Attribute administrativeRole = AttributeUtils.getAttribute( acsa, administrativeRoleType );
+        if ( administrativeRole != null )
+        {
+            for ( int ii = 0; ii < administrativeRole.size(); ii++ )
+            {
+                String role = ( String ) administrativeRole.get( ii );
+                if ( role.equalsIgnoreCase( "accessControlSpecificArea" ) )
+                {
+                    acsaLut.add( apDn.toString() );
+                    return;
+                }
+            }
+        }
+
+        Attributes mods = new LockableAttributesImpl();
+        mods.put( "administrativeRole", "accessControlSpecificArea" );
+        proxy.modify( apDn, DirContext.ADD_ATTRIBUTE, mods );
+        acsaLut.add( apDn.getNormName() );
+    }
+
+
+    /**
+     * Creates the subtreeSpecification for the accessControlSubentry for the application.
+     *
+     * @param adminPointDn the normalized DN of the AP (Administrative Point) for the entry
+     * @param appDn the normalized DN of the application entry
+     * @return the subtreeSpecification of a subtree which starts at the application entry and
+     * descends to all leaf entries
+     * @throws NamingException if the AP is (wrong) not an ancestor of the application entry
+     */
+    private String createSubtreeSpecification( LdapDN adminPointDn, LdapDN appDn ) throws NamingException
+    {
+        LdapDN baseRdn = ( LdapDN ) NamespaceTools.getRelativeName( adminPointDn, appDn );
+        StringBuffer buf = new StringBuffer();
+        buf.append( "{ base \"" );
+        buf.append( baseRdn.getNormName() );
+        buf.append( "\" }" );
+        return buf.toString();
+    }
+
+
+    /**
+     * Creates an ACIItem for the application user to have <b>READ-ONLY</b> access to policy
+     * information for itself (the application).  The application user will be granted the
+     * following permissions on all entries and their attributes under the application's subtree:
+     *
+     * <ul>
+     *   <li>Read</li>
+     *   <li>ReturnDN</li>
+     *   <li>Browse</li>
+     *   <li>DiscloseOnError</li>
+     *   <li>Compare</li>
+     * </ul>
+     *
+     * Here's what the ACIItem looks like for application appname=abc,ou=applications,dc=example,dc=com:
+     * <pre>
+     *  {
+     *    identificationTag "abcAci"
+     *    precedence 14,
+     *    authenticationLevel simple,
+     *    itemOrUserFirst userFirst: {
+     *    userClasses { name { "appName=abc,ou=applications,dc=example,dc=com" } },
+     *    userPermissions
+     *    { {
+     *      protectedItems {entry, allUserAttributeTypesAndValues},
+     *      grantsAndDenials { grantRead, grantReturnDN, grantBrowse, grantDiscloseOnError, grantCompare } }
+     *    } }
+     * }
+     * </pre>
+     *
+     * @param aciTag the identificationTag for the ACIItem produced
+     * @param appDn the normalized DN for the application
+     * @return the ACIItem for the application user
+     */
+    private String createApplicationACIItem( String aciTag, LdapDN appDn )
+    {
+        StringBuffer buf = new StringBuffer();
+        buf.append( "{ identificationTag \"" );
+        buf.append( aciTag );
+        buf.append( "\", precedence 14, authenticationLevel simple, itemOrUserFirst userFirst: { " );
+        buf.append( "userClasses { name { \"" );
+        buf.append( appDn.getNormName() );
+        buf.append( "\" } }, userPermissions { { protectedItems {entry, allUserAttributeTypesAndValues}, ");
+        buf.append("grantsAndDenials { grantRead, grantReturnDN, grantBrowse, grantDiscloseOnError, grantCompare } } } } }" );
+        return buf.toString();
+    }
+
+
+    /**
+     * Creates an ACIItem for an adminstrative group with full access to alter policy information
+     * for an application.  The group will be granted the following permissions on all entries and
+     * their attributes under the application subtree:
+     *
+     * <ul>
+     *   <li>Read</li>
+     *   <li>ReturnDN</li>
+     *   <li>Browse</li>
+     *   <li>DiscloseOnError</li>
+     *   <li>Compare</li>
+     *   <li>Add</li>
+     *   <li>Rename</li>
+     *   <li>Remove</li>
+     *   <li>Modify</li>
+     *   <li>Import</li>
+     *   <li>Export</li>
+     * </ul>
+     *
+     * Here's what the ACIItem looks like for application appName=abc,ou=applications,dc=example,dc=com and the
+     * admin group cn=abcAdminGroup,ou=groups,dc=example,dc=com:
+     * <pre>
+     *  {
+     *    identificationTag "abcAdminAci"
+     *    precedence 14,
+     *    authenticationLevel simple,
+     *    itemOrUserFirst userFirst: {
+     *    userClasses { userGroup { "cn=abcApplicationAdminGroup,ou=groups,dc=example,dc=com" } },
+     *    userPermissions
+     *    { {
+     *      protectedItems {entry, allUserAttributeTypesAndValues},
+     *      grantsAndDenials { grantRead, grantReturnDN, grantBrowse, grantDiscloseOnError, grantCompare,
+     *                         grantAdd, grantRename, grantRemove, grantModify, grantImport, grantExport } }
+     *    } }
+     * }
+     * </pre>
+     *
+     * @param aciTag the identificationTag for the ACIItem produced
+     * @param adminGroupDn the normalized DN for the application's admin group
+     * @return the ACIItem for the administrative user's group for an application
+     */
+    private String createApplicationAdminACIItem( String aciTag, LdapDN adminGroupDn )
+    {
+        StringBuffer buf = new StringBuffer();
+        buf.append( "{ identificationTag \"" );
+        buf.append( aciTag );
+        buf.append( "\", precedence 14, authenticationLevel simple, itemOrUserFirst userFirst: { " );
+        buf.append( "userClasses { userGroup { \"" );
+        buf.append( adminGroupDn.getNormName() );
+        buf.append( "\" } }, userPermissions { { protectedItems {entry, allUserAttributeTypesAndValues}, ");
+        buf.append( "grantsAndDenials { grantRead, grantReturnDN, grantBrowse, grantDiscloseOnError, grantCompare, ");
+        buf.append( "grantAdd, grantRename, grantRemove, grantModify, grantImport, grantExport } } } } }" );
+        return buf.toString();
+    }
+}