You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by vt...@apache.org on 2004/02/16 02:52:42 UTC

svn commit: rev 6675 - in incubator/directory/janus/trunk/authentication/impl: . src src/java src/java/org src/java/org/apache src/java/org/apache/janus src/java/org/apache/janus/authentication src/java/org/apache/janus/authentication/realm src/test src/test/org src/test/org/apache src/test/org/apache/janus src/test/org/apache/janus/authentication src/test/org/apache/janus/authentication/realm

Author: vtence
Date: Sun Feb 15 17:52:41 2004
New Revision: 6675

Added:
   incubator/directory/janus/trunk/authentication/impl/project.xml
      - copied, changed from rev 6667, incubator/directory/janus/trunk/authentication/api/project.xml
   incubator/directory/janus/trunk/authentication/impl/src/
   incubator/directory/janus/trunk/authentication/impl/src/java/
   incubator/directory/janus/trunk/authentication/impl/src/java/org/
   incubator/directory/janus/trunk/authentication/impl/src/java/org/apache/
   incubator/directory/janus/trunk/authentication/impl/src/java/org/apache/janus/
   incubator/directory/janus/trunk/authentication/impl/src/java/org/apache/janus/DefaultSubject.java
   incubator/directory/janus/trunk/authentication/impl/src/java/org/apache/janus/authentication/
   incubator/directory/janus/trunk/authentication/impl/src/java/org/apache/janus/authentication/AbstractCredentialCollection.java
   incubator/directory/janus/trunk/authentication/impl/src/java/org/apache/janus/authentication/DefaultAuthenticator.java
   incubator/directory/janus/trunk/authentication/impl/src/java/org/apache/janus/authentication/DefaultCredentialCollection.java
   incubator/directory/janus/trunk/authentication/impl/src/java/org/apache/janus/authentication/realm/
   incubator/directory/janus/trunk/authentication/impl/src/java/org/apache/janus/authentication/realm/AbstractPrincipal.java
   incubator/directory/janus/trunk/authentication/impl/src/java/org/apache/janus/authentication/realm/DefaultRealm.java
   incubator/directory/janus/trunk/authentication/impl/src/java/org/apache/janus/authentication/realm/GroupPrincipal.java
   incubator/directory/janus/trunk/authentication/impl/src/java/org/apache/janus/authentication/realm/UsernamePrincipal.java
   incubator/directory/janus/trunk/authentication/impl/src/test/
   incubator/directory/janus/trunk/authentication/impl/src/test/org/
   incubator/directory/janus/trunk/authentication/impl/src/test/org/apache/
   incubator/directory/janus/trunk/authentication/impl/src/test/org/apache/janus/
   incubator/directory/janus/trunk/authentication/impl/src/test/org/apache/janus/CatPrincipal.java
   incubator/directory/janus/trunk/authentication/impl/src/test/org/apache/janus/DefaultSubjectTest.java
   incubator/directory/janus/trunk/authentication/impl/src/test/org/apache/janus/DogPrincipal.java
   incubator/directory/janus/trunk/authentication/impl/src/test/org/apache/janus/authentication/
   incubator/directory/janus/trunk/authentication/impl/src/test/org/apache/janus/authentication/DefaultAuthenticatorTest.java
   incubator/directory/janus/trunk/authentication/impl/src/test/org/apache/janus/authentication/DefaultCredentialCollectionTest.java
   incubator/directory/janus/trunk/authentication/impl/src/test/org/apache/janus/authentication/FruitPrincipal.java
   incubator/directory/janus/trunk/authentication/impl/src/test/org/apache/janus/authentication/GroupSupportRealm.java
   incubator/directory/janus/trunk/authentication/impl/src/test/org/apache/janus/authentication/realm/
   incubator/directory/janus/trunk/authentication/impl/src/test/org/apache/janus/authentication/realm/DefaultRealmTest.java
Log:
o Added authentication impl

Copied: incubator/directory/janus/trunk/authentication/impl/project.xml (from rev 6667, incubator/directory/janus/trunk/authentication/api/project.xml)
==============================================================================
--- incubator/directory/janus/trunk/authentication/api/project.xml	(original)
+++ incubator/directory/janus/trunk/authentication/impl/project.xml	Sun Feb 15 17:52:41 2004
@@ -2,14 +2,21 @@
 <project>
     <extend>${basedir}/../../project.xml</extend>
 
-    <name>Janus Authentication API</name>
-    <id>janus-authentication-api</id>
+    <name>Janus Authentication API Implementation</name>
+    <id>janus-authentication-impl</id>
     <package>org.apache.janus.authentication</package>
 
-    <shortDescription>Janus Authentication API</shortDescription>
+    <shortDescription>Janus Authentication API Implementation</shortDescription>
 
     <description>
-    API for the Janus Security Framework
+    Implementation of the Janus Security Framework Authentication API
     </description>
 
+    <dependencies>
+        <dependency>
+            <groupId>${pom.groupId}</groupId>
+            <artifactId>janus-authentication-api</artifactId>
+            <version>${pom.currentVersion}</version>
+        </dependency>
+    </dependencies>
 </project>

Added: incubator/directory/janus/trunk/authentication/impl/src/java/org/apache/janus/DefaultSubject.java
==============================================================================
--- (empty file)
+++ incubator/directory/janus/trunk/authentication/impl/src/java/org/apache/janus/DefaultSubject.java	Sun Feb 15 17:52:41 2004
@@ -0,0 +1,105 @@
+/*
+ *   Copyright 2004 The Apache Software Foundation
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+package org.apache.janus;
+
+import java.security.Principal;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:directory-dev@incubator.apache.org">Apache Directory Project</a>
+ */
+public class DefaultSubject implements Subject
+{
+    private final Set m_principals;
+    private boolean m_readOnly;
+
+    public DefaultSubject()
+    {
+        m_principals = new HashSet();
+    }
+
+    public boolean addPrincipal( Principal principal )
+    {
+        assertWritable();
+        return m_principals.add( principal );
+    }
+
+    public boolean addAllPrincipals( Collection principals )
+    {
+        boolean changed = false;
+        for ( Iterator it = principals.iterator(); it.hasNext(); )
+        {
+            final Principal p = (Principal) it.next();
+            changed |= addPrincipal( p );
+        }
+
+        return changed;
+    }
+
+    public boolean removePrincipal( Principal principal )
+    {
+        assertWritable();
+        return m_principals.remove( principal );
+    }
+
+    public boolean removeAllPrincipals( Collection principals )
+    {
+        boolean changed = false;
+        for ( Iterator it = principals.iterator(); it.hasNext(); )
+        {
+            final Principal p = (Principal) it.next();
+            changed |= removePrincipal( p );
+        }
+
+        return changed;
+    }
+
+    public Set getPrincipals()
+    {
+        return Collections.unmodifiableSet( m_principals );
+    }
+
+    public Set getPrincipals( Class c )
+    {
+        final Set subSet = new HashSet();
+
+        for ( Iterator it = m_principals.iterator(); it.hasNext(); )
+        {
+            final Principal principal = (Principal) it.next();
+            if ( c.isInstance( principal ) ) subSet.add( principal );
+        }
+
+        return subSet;
+    }
+
+    public void makeReadOnly()
+    {
+        m_readOnly = true;
+    }
+
+    private void assertWritable()
+    {
+        if ( m_readOnly )
+        {
+            throw new IllegalStateException( "In read-only state" );
+        }
+    }
+}

Added: incubator/directory/janus/trunk/authentication/impl/src/java/org/apache/janus/authentication/AbstractCredentialCollection.java
==============================================================================
--- (empty file)
+++ incubator/directory/janus/trunk/authentication/impl/src/java/org/apache/janus/authentication/AbstractCredentialCollection.java	Sun Feb 15 17:52:41 2004
@@ -0,0 +1,100 @@
+/*
+ *   Copyright 2004 The Apache Software Foundation
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+package org.apache.janus.authentication;
+
+import java.io.Serializable;
+import java.util.Iterator;
+
+/**
+ * Base class for <code>CredentialCollection</code>s implementations.
+ *
+ * @author <a href="mailto:directory-dev@incubator.apache.org">Apache Directory Project</a>
+ */
+public abstract class AbstractCredentialCollection
+        implements CredentialCollection, Serializable
+{
+    /**
+     * The authentication method to use with this credential
+     * collection.
+     */
+    private String m_authMethod;
+
+    /**
+     * Sets the authentication method being used - or credential types.
+     */
+    protected void setAuthenticationMethod( String authMethod )
+    {
+        m_authMethod = authMethod;
+    }
+
+    public String getAuthenticationMethod()
+    {
+        return m_authMethod;
+    }
+
+    /**
+     * Returns a string describing this CredentialCollection object,
+     * providing information about all the credentials it contains.
+     */
+    public String toString()
+    {
+        final StringBuffer sb = new StringBuffer();
+        sb.append( "authentication-method=" ).append( m_authMethod );
+        sb.append( ", elements=" );
+
+        if ( elements().isEmpty() ) return sb.append( "{}" ).toString();
+
+        final Iterator i = elements().iterator();
+        sb.append( "{" );
+        while ( i.hasNext() )
+        {
+            sb.append( i.next().toString() );
+            sb.append( ", " );
+        }
+        // Remove trailing ", "
+        // This is safe since the elements collection is not empty
+        sb.setLength( sb.length() - 2 );
+        sb.append( "}" );
+
+        return sb.toString();
+    }
+
+    public boolean equals( Object o )
+    {
+        if ( this == o ) return true;
+        if ( !(o instanceof CredentialCollection) ) return false;
+
+        final CredentialCollection credentialCollection = (CredentialCollection) o;
+
+        if ( !m_authMethod.equals(
+                credentialCollection.getAuthenticationMethod() ) )
+        {
+            return false;
+        }
+        if ( !elements().equals( credentialCollection.elements() ) ) return false;
+
+        return true;
+    }
+
+    public int hashCode()
+    {
+        int hashCode = m_authMethod.hashCode();
+        hashCode = hashCode * 29 + elements().hashCode();
+
+        return hashCode;
+    }
+}
\ No newline at end of file

Added: incubator/directory/janus/trunk/authentication/impl/src/java/org/apache/janus/authentication/DefaultAuthenticator.java
==============================================================================
--- (empty file)
+++ incubator/directory/janus/trunk/authentication/impl/src/java/org/apache/janus/authentication/DefaultAuthenticator.java	Sun Feb 15 17:52:41 2004
@@ -0,0 +1,59 @@
+/*
+ *   Copyright 2004 The Apache Software Foundation
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+package org.apache.janus.authentication;
+
+import org.apache.janus.DefaultSubject;
+import org.apache.janus.Subject;
+import org.apache.janus.authentication.realm.GroupSupport;
+import org.apache.janus.authentication.realm.Realm;
+
+import java.security.Principal;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:directory-dev@incubator.apache.org">Apache Directory Project</a>
+ */
+public class DefaultAuthenticator implements Authenticator
+{
+    private final Realm m_realm;
+
+    public DefaultAuthenticator( Realm realm )
+    {
+        m_realm = realm;
+    }
+
+    public Subject authenticate( CredentialCollection credentials )
+            throws AuthenticationException
+    {
+        Principal p = m_realm.validateCredentials( credentials );
+        if ( p == null )
+        {
+            throw new AuthenticationException( "Credentials rejected" );
+        }
+
+        DefaultSubject subject = new DefaultSubject();
+        subject.addPrincipal( p );
+
+        if ( m_realm instanceof GroupSupport )
+        {
+            Set groups = ((GroupSupport) m_realm).getGroupsForPrincipal( p );
+            subject.addAllPrincipals( groups );
+        }
+
+        return subject;
+    }
+}

Added: incubator/directory/janus/trunk/authentication/impl/src/java/org/apache/janus/authentication/DefaultCredentialCollection.java
==============================================================================
--- (empty file)
+++ incubator/directory/janus/trunk/authentication/impl/src/java/org/apache/janus/authentication/DefaultCredentialCollection.java	Sun Feb 15 17:52:41 2004
@@ -0,0 +1,87 @@
+/*
+ *   Copyright 2004 The Apache Software Foundation
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+package org.apache.janus.authentication;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:directory-dev@incubator.apache.org">Apache Directory Project</a>
+ */
+public class DefaultCredentialCollection extends AbstractCredentialCollection
+{
+    private final Set m_credentials;
+    private boolean m_readOnly;
+
+    public DefaultCredentialCollection( String authenticationMethod )
+    {
+        m_credentials = new HashSet();
+        setAuthenticationMethod( authenticationMethod );
+    }
+
+    public boolean add( Credential cred )
+    {
+        assertWritable();
+        return m_credentials.add( cred );
+    }
+
+    public boolean remove( Credential cred )
+    {
+        assertWritable();
+        return m_credentials.remove( cred );
+    }
+
+    public boolean isEmpty()
+    {
+        return m_credentials.isEmpty();
+    }
+
+    public CredentialCollection get( String type )
+    {
+        final DefaultCredentialCollection creds =
+                new DefaultCredentialCollection( getAuthenticationMethod() );
+
+        for ( Iterator it = m_credentials.iterator(); it.hasNext(); )
+        {
+            final Credential credential = (Credential) it.next();
+            if ( credential.getType().equals( type ) ) creds.add( credential );
+        }
+
+        return creds;
+    }
+
+    public Collection elements()
+    {
+        return Collections.unmodifiableCollection( m_credentials );
+    }
+
+    public void makeReadOnly()
+    {
+        m_readOnly = true;
+    }
+
+    private void assertWritable()
+    {
+        if ( m_readOnly )
+        {
+            throw new IllegalStateException( "In read-only state" );
+        }
+    }
+}

Added: incubator/directory/janus/trunk/authentication/impl/src/java/org/apache/janus/authentication/realm/AbstractPrincipal.java
==============================================================================
--- (empty file)
+++ incubator/directory/janus/trunk/authentication/impl/src/java/org/apache/janus/authentication/realm/AbstractPrincipal.java	Sun Feb 15 17:52:41 2004
@@ -0,0 +1,68 @@
+/*
+ *   Copyright 2004 The Apache Software Foundation
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+package org.apache.janus.authentication.realm;
+
+import java.security.Principal;
+
+/**
+ * A principal name is unique within the set
+ * of principals of the same type.
+ *
+ * @author <a href="mailto:directory-dev@incubator.apache.org">Apache Directory Project</a>
+ */
+public abstract class AbstractPrincipal implements Principal
+{
+    private final String m_name;
+
+    public AbstractPrincipal( String name )
+    {
+        if ( name == null ) throw new NullPointerException( "name" );
+        if ( name.equals( "" ) )
+        {
+            throw new IllegalArgumentException( "Empty name" );
+        }
+        m_name = name;
+    }
+
+    public String getName()
+    {
+        return m_name;
+    }
+
+    public boolean equals( Object o )
+    {
+        if ( this == o ) return true;
+        if ( !(o instanceof AbstractPrincipal) ) return false;
+
+        final AbstractPrincipal abstractPrincipal = (AbstractPrincipal) o;
+
+        if ( !m_name.equals( abstractPrincipal.m_name ) ) return false;
+
+        return true;
+    }
+
+    public int hashCode()
+    {
+        return m_name.hashCode();
+    }
+
+    public String toString()
+    {
+        return "name=" + m_name;
+    }
+}
+

Added: incubator/directory/janus/trunk/authentication/impl/src/java/org/apache/janus/authentication/realm/DefaultRealm.java
==============================================================================
--- (empty file)
+++ incubator/directory/janus/trunk/authentication/impl/src/java/org/apache/janus/authentication/realm/DefaultRealm.java	Sun Feb 15 17:52:41 2004
@@ -0,0 +1,241 @@
+/*
+ *   Copyright 2004 The Apache Software Foundation
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+package org.apache.janus.authentication.realm;
+
+import org.apache.janus.authentication.Credential;
+import org.apache.janus.authentication.CredentialCollection;
+
+import java.security.Principal;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Support username-authentication only and assumes a single password per username.
+ *
+ * @author <a href="mailto:directory-dev@incubator.apache.org">Apache Directory Project</a>
+ */
+public class DefaultRealm implements MutableRealm, GroupSupport
+{
+    private final Map m_credentials;
+    private final Map m_principals;
+
+    public DefaultRealm()
+    {
+        m_credentials = new HashMap();
+        m_principals = new HashMap();
+    }
+
+    public Set getPrincipals( Class c )
+    {
+        if ( c.equals( UsernamePrincipal.class ) )
+        {
+            return Collections.unmodifiableSet( m_credentials.keySet() );
+        }
+
+        if ( c.equals( GroupPrincipal.class ) )
+        {
+            return Collections.unmodifiableSet( m_principals.keySet() );
+        }
+
+        return null;
+    }
+
+    public Principal addPrincipal( String principalName )
+            throws PrincipalAlreadyExistsException
+    {
+        Principal user = new UsernamePrincipal( principalName );
+
+        if ( m_credentials.containsKey( user ) )
+        {
+            throw new PrincipalAlreadyExistsException(
+                    "Principal already a member", user );
+        }
+
+        m_credentials.put( user, new HashSet() );
+        return user;
+    }
+
+    public Principal addGroup( String groupName )
+            throws GroupAlreadyExistsException
+    {
+        Principal group = new GroupPrincipal( groupName );
+
+        if ( m_principals.containsKey( group ) )
+        {
+            throw new GroupAlreadyExistsException( "Group already a member",
+                    group );
+        }
+
+        m_principals.put( group, new HashSet() );
+        return group;
+    }
+
+    public boolean addCredentialToPrincipal( Principal p, Credential c )
+    {
+        Set creds = (Set) m_credentials.get( p );
+        if ( creds == null )
+        {
+            throw new IllegalArgumentException( "Imposter: " + p );
+        }
+        return creds.add( c );
+    }
+
+    public Principal validateCredentials( CredentialCollection credentials )
+    {
+        String authMethod = credentials.getAuthenticationMethod();
+        if ( !"username-password".equals( authMethod ) )
+        {
+            throw new UnsupportedOperationException(
+                    "Authentication method: " + authMethod );
+        }
+        Credential username = getUsername( credentials );
+        if ( username == null ) return null;
+        Credential password = getPassword( credentials );
+        if ( password == null ) return null;
+
+        Principal identity = new UsernamePrincipal(
+                (String) username.getValue() );
+        Set creds = (Set) m_credentials.get( identity );
+        if ( creds == null || !creds.contains( password ) ) return null;
+
+        return identity;
+    }
+
+    public Set getGroupsForPrincipal( Principal principal )
+    {
+        Collection groups = m_principals.keySet();
+
+        Set memberOf = new HashSet();
+        for ( Iterator it = groups.iterator(); it.hasNext(); )
+        {
+            final Principal group = (Principal) it.next();
+            if ( isMember( group, principal ) ) memberOf.add( group );
+        }
+
+        return memberOf;
+    }
+
+    private Credential getPassword( CredentialCollection creds )
+    {
+        CredentialCollection passwords = creds.get( "password" );
+        if ( passwords.isEmpty() ) return null;
+        if ( passwords.elements().size() > 1 )
+        {
+            throw new IllegalArgumentException(
+                    "Only one password is possible" );
+        }
+        return (Credential) passwords.elements().iterator().next();
+    }
+
+    private Credential getUsername( CredentialCollection creds )
+    {
+        CredentialCollection usernames = creds.get( "username" );
+        if ( usernames.isEmpty() ) return null;
+        if ( usernames.elements().size() > 1 )
+        {
+            throw new IllegalArgumentException(
+                    "Only one username is possible" );
+        }
+        return (Credential) usernames.elements().iterator().next();
+    }
+
+    public Set getPrincipalsForGroup( Principal group )
+    {
+        Set principals = (Set) m_principals.get( group );
+        if ( principals == null ) principals = Collections.EMPTY_SET;
+
+        return principals;
+    }
+
+    public boolean addPrincipalToGroup( Principal group, Principal principal )
+    {
+        assertNotSame( group, principal );
+        assertIsGroup( group );
+        assertIsInRealm( principal );
+        assertNotAMember( principal, group );
+
+        Set principals = (Set) m_principals.get( group );
+        return principals.add( principal );
+    }
+
+    private void assertNotSame( Principal group, Principal principal )
+    {
+        if ( group.equals( principal ) )
+        {
+            throw new IllegalArgumentException(
+                    "Can't add group to itself:" + group.getName() );
+        }
+    }
+
+    private void assertNotAMember( Principal principal, Principal group )
+    {
+        if ( isGroup( principal ) && isMember( principal, group ) )
+        {
+            throw new IllegalArgumentException( "Cyclic membership detected" );
+        }
+    }
+
+    private void assertIsInRealm( Principal principal )
+    {
+        if ( !isPrincipal( principal ) && !isGroup( principal ) )
+        {
+            throw new IllegalArgumentException(
+                    "Not a valid principal or group: " + principal.getName() );
+        }
+    }
+
+    private void assertIsGroup( Principal group )
+    {
+        if ( !isGroup( group ) )
+        {
+            throw new IllegalArgumentException(
+                    "Not a valid group: " + group.getName() );
+        }
+    }
+
+    private boolean isMember( Principal group, Principal principal )
+    {
+        Set members = (Set) m_principals.get( group );
+        if ( members.contains( principal ) ) return true;
+
+        for ( Iterator it = members.iterator(); it.hasNext(); )
+        {
+            final Principal member = (Principal) it.next();
+            if ( isGroup( member ) )
+            {
+                if ( isMember( member, principal ) ) return true;
+            }
+        }
+
+        return false;
+    }
+
+    private boolean isGroup( Principal p )
+    {
+        return m_principals.keySet().contains( p );
+    }
+
+    private boolean isPrincipal( Principal p )
+    {
+        return m_credentials.keySet().contains( p );
+    }
+}

Added: incubator/directory/janus/trunk/authentication/impl/src/java/org/apache/janus/authentication/realm/GroupPrincipal.java
==============================================================================
--- (empty file)
+++ incubator/directory/janus/trunk/authentication/impl/src/java/org/apache/janus/authentication/realm/GroupPrincipal.java	Sun Feb 15 17:52:41 2004
@@ -0,0 +1,41 @@
+/*
+ *   Copyright 2004 The Apache Software Foundation
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+package org.apache.janus.authentication.realm;
+
+/**
+ * @author <a href="mailto:directory-dev@incubator.apache.org">Apache Directory Project</a>
+ */
+public class GroupPrincipal extends AbstractPrincipal
+{
+    public GroupPrincipal( String name )
+    {
+        super( name );
+    }
+
+    public boolean equals( Object o )
+    {
+        if ( this == o ) return true;
+        if ( !(o instanceof GroupPrincipal) ) return false;
+
+        return super.equals( o );
+    }
+
+    public String toString()
+    {
+        return "[GroupPrincipal: " + super.toString() + "]";
+    }
+}

Added: incubator/directory/janus/trunk/authentication/impl/src/java/org/apache/janus/authentication/realm/UsernamePrincipal.java
==============================================================================
--- (empty file)
+++ incubator/directory/janus/trunk/authentication/impl/src/java/org/apache/janus/authentication/realm/UsernamePrincipal.java	Sun Feb 15 17:52:41 2004
@@ -0,0 +1,43 @@
+/*
+ *   Copyright 2004 The Apache Software Foundation
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+package org.apache.janus.authentication.realm;
+
+/**
+ * A principal representing a login username.
+ *
+ * @author <a href="mailto:directory-dev@incubator.apache.org">Apache Directory Project</a>
+ */
+public class UsernamePrincipal extends AbstractPrincipal
+{
+    public UsernamePrincipal( String username )
+    {
+        super( username );
+    }
+
+    public boolean equals( Object o )
+    {
+        if ( this == o ) return true;
+        if ( !(o instanceof UsernamePrincipal) ) return false;
+
+        return super.equals( o );
+    }
+
+    public String toString()
+    {
+        return "[UsernamePrincipal: " + super.toString() + "]";
+    }
+}

Added: incubator/directory/janus/trunk/authentication/impl/src/test/org/apache/janus/CatPrincipal.java
==============================================================================
--- (empty file)
+++ incubator/directory/janus/trunk/authentication/impl/src/test/org/apache/janus/CatPrincipal.java	Sun Feb 15 17:52:41 2004
@@ -0,0 +1,30 @@
+/*
+ *   Copyright 2004 The Apache Software Foundation
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+package org.apache.janus;
+
+import org.apache.janus.authentication.realm.AbstractPrincipal;
+
+/**
+ * @author <a href="mailto:directory-dev@incubator.apache.org">Apache Directory Project</a>
+ */
+public class CatPrincipal extends AbstractPrincipal
+{
+    public CatPrincipal( String name )
+    {
+        super( name );
+    }
+}
\ No newline at end of file

Added: incubator/directory/janus/trunk/authentication/impl/src/test/org/apache/janus/DefaultSubjectTest.java
==============================================================================
--- (empty file)
+++ incubator/directory/janus/trunk/authentication/impl/src/test/org/apache/janus/DefaultSubjectTest.java	Sun Feb 15 17:52:41 2004
@@ -0,0 +1,143 @@
+/*
+ *   Copyright 2004 The Apache Software Foundation
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+package org.apache.janus;
+
+import junit.framework.TestCase;
+
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+
+/**
+ * @author <a href="mailto:directory-dev@incubator.apache.org">Apache Directory Project</a>
+ */
+public class DefaultSubjectTest extends TestCase
+{
+    public static void main( String[] args )
+    {
+        junit.textui.TestRunner.run( DefaultSubjectTest.class );
+    }
+
+    public void testContainsNoPrincipalByDefault()
+    {
+        DefaultSubject subject = new DefaultSubject();
+        assertTrue( "Subject should be constructed empty",
+                subject.getPrincipals().isEmpty() );
+    }
+
+    public void testMaintainsASetOfPrincipals()
+    {
+        Principal labrador = new DogPrincipal( "labrador" );
+        Principal rott = new DogPrincipal( "rott" );
+        Principal doberman = new DogPrincipal( "doberman" );
+        Principal dane = new DogPrincipal( "dane" );
+        DefaultSubject subject = new DefaultSubject();
+        subject.addPrincipal( labrador );
+        subject.addPrincipal( rott );
+        Collection remaining = new ArrayList();
+        remaining.add( doberman );
+        remaining.add( dane );
+        subject.addAllPrincipals( remaining );
+
+        Collection expected = new HashSet();
+        expected.add( labrador );
+        expected.add( rott );
+        expected.add( doberman );
+        expected.add( dane );
+        assertEquals( "Wrong principal set after addition", expected,
+                subject.getPrincipals() );
+
+        // Test removal as well
+        subject.removePrincipal( rott );
+        subject.removeAllPrincipals( remaining );
+
+        expected = new HashSet();
+        expected.add( labrador );
+        assertEquals( "Wrong principal set after removal", expected,
+                subject.getPrincipals() );
+    }
+
+    public void testPreventsModificationOfUnderlyingSet()
+    {
+        DefaultSubject subject = new DefaultSubject();
+        try
+        {
+            subject.getPrincipals().add( new Object() );
+            fail( "Underlying collection is modifiable" );
+        }
+        catch ( UnsupportedOperationException expected )
+        {
+        }
+    }
+
+    public void testIgnoresAdditionOfDuplicatePrincipals()
+    {
+        Principal alleyCat = new CatPrincipal( "alleyCat" );
+        DefaultSubject subject = new DefaultSubject();
+        assertTrue( "First time add failed", subject.addPrincipal( alleyCat ) );
+        assertFalse( "Add twice worked", subject.addPrincipal( alleyCat ) );
+    }
+
+    public void testIgnoresRemovalOfUnknownPrincipals()
+    {
+        Principal alleyCat = new CatPrincipal( "alleyCat" );
+        DefaultSubject subject = new DefaultSubject();
+        subject.addPrincipal( alleyCat );
+        assertTrue( "First time remove failed",
+                subject.removePrincipal( alleyCat ) );
+        assertFalse( "Remove twice worked",
+                subject.removePrincipal( alleyCat ) );
+    }
+
+    public void testReturnsPrincipalSubsetBasedOnClass()
+    {
+        Principal labrador = new DogPrincipal( "labrador" );
+        Principal alleyCat = new CatPrincipal( "alleyCat" );
+        DefaultSubject subject = new DefaultSubject();
+        subject.addPrincipal( labrador );
+        subject.addPrincipal( alleyCat );
+
+        Collection expected = new HashSet();
+        expected.add( labrador );
+        assertEquals( "Wrong principal subset", expected,
+                subject.getPrincipals( DogPrincipal.class ) );
+    }
+
+    public void testCanBeFrozen()
+    {
+        DefaultSubject subject = new DefaultSubject();
+        subject.makeReadOnly();
+        try
+        {
+            subject.addPrincipal( new CatPrincipal( "alleyCat" ) );
+            fail( "Subject still read-write" );
+        }
+        catch ( IllegalStateException expected )
+        {
+        }
+
+        try
+        {
+            subject.removePrincipal( new CatPrincipal( "alleyCat" ) );
+            fail( "Subject still read-write" );
+        }
+        catch ( IllegalStateException expected )
+        {
+        }
+    }
+}

Added: incubator/directory/janus/trunk/authentication/impl/src/test/org/apache/janus/DogPrincipal.java
==============================================================================
--- (empty file)
+++ incubator/directory/janus/trunk/authentication/impl/src/test/org/apache/janus/DogPrincipal.java	Sun Feb 15 17:52:41 2004
@@ -0,0 +1,30 @@
+/*
+ *   Copyright 2004 The Apache Software Foundation
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+package org.apache.janus;
+
+import org.apache.janus.authentication.realm.AbstractPrincipal;
+
+/**
+ * @author <a href="mailto:directory-dev@incubator.apache.org">Apache Directory Project</a>
+ */
+public class DogPrincipal extends AbstractPrincipal
+{
+    public DogPrincipal( String name )
+    {
+        super( name );
+    }
+}
\ No newline at end of file

Added: incubator/directory/janus/trunk/authentication/impl/src/test/org/apache/janus/authentication/DefaultAuthenticatorTest.java
==============================================================================
--- (empty file)
+++ incubator/directory/janus/trunk/authentication/impl/src/test/org/apache/janus/authentication/DefaultAuthenticatorTest.java	Sun Feb 15 17:52:41 2004
@@ -0,0 +1,93 @@
+/*
+ *   Copyright 2004 The Apache Software Foundation
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+package org.apache.janus.authentication;
+
+import com.mockobjects.dynamic.C;
+import com.mockobjects.dynamic.Mock;
+import junit.framework.TestCase;
+import org.apache.janus.Subject;
+import org.apache.janus.authentication.realm.GroupPrincipal;
+import org.apache.janus.authentication.realm.Realm;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:directory-dev@incubator.apache.org">Apache Directory Project</a>
+ */
+public class DefaultAuthenticatorTest extends TestCase
+{
+    private DefaultAuthenticator m_authenticator;
+    private Mock m_mockRealm;
+
+    public static void main( String[] args )
+    {
+        junit.textui.TestRunner.run( DefaultAuthenticatorTest.class );
+    }
+
+    protected void setUp() throws Exception
+    {
+        m_mockRealm
+                = new Mock( GroupSupportRealm.class );
+        m_authenticator
+                = new DefaultAuthenticator( (Realm) m_mockRealm.proxy() );
+    }
+
+    public void testAuthentication()
+    {
+        DefaultCredentialCollection credentials = new DefaultCredentialCollection(
+                "taste" );
+        m_mockRealm.matchAndReturn( "validateCredentials", C.same( credentials ),
+                new FruitPrincipal( "banana" ) );
+        Set groups = new HashSet();
+        groups.add( new GroupPrincipal( "fruit" ) );
+        groups.add( new GroupPrincipal( "yellow" ) );
+        m_mockRealm.matchAndReturn( "getGroupsForPrincipal",
+                new FruitPrincipal( "banana" ), groups );
+
+        Subject subject = null;
+        try
+        {
+            subject = m_authenticator.authenticate( credentials );
+        }
+        catch ( AuthenticationException e )
+        {
+            fail( "Login failed" );
+        }
+        assertTrue( "Principal was not added to subject",
+                subject.getPrincipals().contains(
+                        new FruitPrincipal( "banana" ) ) );
+        assertTrue( "Groups were not added to subject",
+                subject.getPrincipals().containsAll( groups ) );
+    }
+
+    public void testAuthenticationFailure()
+    {
+        m_mockRealm.matchAndReturn( "validateCredentials", C.ANY_ARGS, null );
+
+        try
+        {
+            m_authenticator.authenticate(
+                    new DefaultCredentialCollection( "dummy" ) );
+            fail( "Login has not failed" );
+        }
+        catch ( AuthenticationException expected )
+        {
+            assertTrue( true );
+        }
+    }
+}

Added: incubator/directory/janus/trunk/authentication/impl/src/test/org/apache/janus/authentication/DefaultCredentialCollectionTest.java
==============================================================================
--- (empty file)
+++ incubator/directory/janus/trunk/authentication/impl/src/test/org/apache/janus/authentication/DefaultCredentialCollectionTest.java	Sun Feb 15 17:52:41 2004
@@ -0,0 +1,130 @@
+/*
+ *   Copyright 2004 The Apache Software Foundation
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+package org.apache.janus.authentication;
+
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * @author <a href="mailto:directory-dev@incubator.apache.org">Apache Directory Project</a>
+ */
+public class DefaultCredentialCollectionTest extends TestCase
+{
+    public static void main( String[] args )
+    {
+        junit.textui.TestRunner.run( DefaultCredentialCollectionTest.class );
+    }
+
+    public void testReturnsSubsetOfCredentialsBasedOnType()
+    {
+        DefaultCredentialCollection creds = new DefaultCredentialCollection(
+                "auth-method" );
+        creds.add( new Credential( "username", "joe" ) );
+        creds.add( new Credential( "password", "turtle" ) );
+        creds.add( new Credential( "password", "sea" ) );
+
+        Collection expected = new ArrayList();
+        expected.add( new Credential( "password", "turtle" ) );
+        expected.add( new Credential( "password", "sea" ) );
+        CredentialCollection subCollection = creds.get( "password" );
+        Collection actual = new ArrayList( subCollection.elements() );
+        assertEquals( "Wrong number of credentials in subset", expected.size(),
+                actual.size() );
+
+        actual.removeAll( expected );
+        assertTrue( "Too many credentials in subset", actual.isEmpty() );
+
+        expected.removeAll( subCollection.elements() );
+        assertTrue( "Too few credentials in subset", expected.isEmpty() );
+    }
+
+    public void testSubCollectionKeepsSameAuthenticationMethod()
+    {
+        DefaultCredentialCollection creds = new DefaultCredentialCollection(
+                "auth-method" );
+        Credential username = new Credential( "username", "joe" );
+        Credential password = new Credential( "password", "turtle" );
+        creds.add( username );
+        creds.add( password );
+
+        CredentialCollection subCollection = creds.get( "password" );
+        assertEquals( "Different authentication methods", creds.getAuthenticationMethod(),
+                subCollection.getAuthenticationMethod() );
+    }
+
+    public void testCanBeFrozen()
+    {
+        DefaultCredentialCollection creds = new DefaultCredentialCollection(
+                "auth-method" );
+        creds.makeReadOnly();
+
+        Credential username = new Credential( "username", "joe" );
+        try
+        {
+            creds.add( username );
+            fail( "Credential collection still read-write" );
+        }
+        catch ( IllegalStateException expected )
+        {
+        }
+
+        try
+        {
+            creds.remove( username );
+            fail( "Credential collection still read-write" );
+        }
+        catch ( IllegalStateException expected )
+        {
+        }
+    }
+
+    public void testPreventsChangeOfUnderlyingCollection()
+    {
+        DefaultCredentialCollection creds = new DefaultCredentialCollection(
+                "auth-method" );
+        try
+        {
+            creds.elements().add( new Credential( "username", "joe" ) );
+            fail( "Can change underlying collection" );
+        }
+        catch ( UnsupportedOperationException expected )
+        {
+        }
+        assertTrue( "Not longer empty", creds.isEmpty() );
+    }
+
+    public void testDuplicatesAreIgnoredOnAdd()
+    {
+        DefaultCredentialCollection creds = new DefaultCredentialCollection(
+                "auth-method" );
+        Credential username = new Credential( "username", "joe" );
+        assertTrue( "First add failed", creds.add( username ) );
+        assertFalse( "Second add worked", creds.add( username ) );
+    }
+
+    public void testUnknownsAreIgnoredOnRemove()
+    {
+        DefaultCredentialCollection creds = new DefaultCredentialCollection(
+                "auth-method" );
+        Credential username = new Credential( "username", "joe" );
+        creds.add( username );
+        assertTrue( "First remove failed", creds.remove( username ) );
+        assertFalse( "Second remove worked", creds.remove( username ) );
+    }
+}

Added: incubator/directory/janus/trunk/authentication/impl/src/test/org/apache/janus/authentication/FruitPrincipal.java
==============================================================================
--- (empty file)
+++ incubator/directory/janus/trunk/authentication/impl/src/test/org/apache/janus/authentication/FruitPrincipal.java	Sun Feb 15 17:52:41 2004
@@ -0,0 +1,30 @@
+/*
+ *   Copyright 2004 The Apache Software Foundation
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+package org.apache.janus.authentication;
+
+import org.apache.janus.authentication.realm.AbstractPrincipal;
+
+/**
+ * @author <a href="mailto:directory-dev@incubator.apache.org">Apache Directory Project</a>
+ */
+public class FruitPrincipal extends AbstractPrincipal
+{
+    public FruitPrincipal( String name )
+    {
+        super( name );
+    }
+}

Added: incubator/directory/janus/trunk/authentication/impl/src/test/org/apache/janus/authentication/GroupSupportRealm.java
==============================================================================
--- (empty file)
+++ incubator/directory/janus/trunk/authentication/impl/src/test/org/apache/janus/authentication/GroupSupportRealm.java	Sun Feb 15 17:52:41 2004
@@ -0,0 +1,28 @@
+/*
+ *   Copyright 2004 The Apache Software Foundation
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+package org.apache.janus.authentication;
+
+import org.apache.janus.authentication.realm.Realm;
+import org.apache.janus.authentication.realm.GroupSupport;
+
+/**
+ * @author <a href="mailto:directory-dev@incubator.apache.org">Apache Directory Project</a>
+ */
+
+public interface GroupSupportRealm extends Realm, GroupSupport
+{
+}

Added: incubator/directory/janus/trunk/authentication/impl/src/test/org/apache/janus/authentication/realm/DefaultRealmTest.java
==============================================================================
--- (empty file)
+++ incubator/directory/janus/trunk/authentication/impl/src/test/org/apache/janus/authentication/realm/DefaultRealmTest.java	Sun Feb 15 17:52:41 2004
@@ -0,0 +1,267 @@
+/*
+ *   Copyright 2004 The Apache Software Foundation
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+package org.apache.janus.authentication.realm;
+
+import junit.framework.TestCase;
+import org.apache.janus.authentication.Credential;
+import org.apache.janus.authentication.DefaultCredentialCollection;
+
+import java.security.Principal;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:directory-dev@incubator.apache.org">Apache Directory Project</a>
+ */
+public class DefaultRealmTest extends TestCase
+{
+    private DefaultRealm m_realm;
+
+    public static void main( String[] args )
+    {
+        junit.textui.TestRunner.run( DefaultRealmTest.class );
+    }
+
+    protected void setUp() throws Exception
+    {
+        m_realm = new DefaultRealm();
+    }
+
+    public void testAddsUsernamePrincipals() throws Exception
+    {
+        assertEquals( "Wrong principal was added to the realm",
+                new UsernamePrincipal( "joe" ), m_realm.addPrincipal( "joe" ) );
+        assertTrue( "Principal not found in realm",
+                m_realm.getPrincipals( UsernamePrincipal.class ).contains(
+                        new UsernamePrincipal( "joe" ) ) );
+    }
+
+    public void testDuplicatePrincipalsAreNotAllowed() throws Exception
+    {
+        m_realm.addPrincipal( "joe" );
+        try
+        {
+            m_realm.addPrincipal( "joe" );
+            fail( "Duplicate principal added" );
+        }
+        catch ( PrincipalAlreadyExistsException expected )
+        {
+        }
+    }
+
+    public void testAdditionOfCredential() throws Exception
+    {
+        m_realm.addPrincipal( "john" );
+        m_realm.addCredentialToPrincipal( new UsernamePrincipal( "john" ),
+                new Credential( "password", "banana" ) );
+        assertFalse( "Credential was not added the first time",
+                m_realm.addCredentialToPrincipal(
+                        new UsernamePrincipal( "john" ),
+                        new Credential( "password", "banana" ) ) );
+    }
+
+    public void testAdditionOfCredentialsToImposterIsRefused()
+    {
+        try
+        {
+            m_realm.addCredentialToPrincipal(
+                    new UsernamePrincipal( "freddy" ),
+                    new Credential( "password", "banana" ) );
+            fail( "Addition of credential to imposter possible" );
+        }
+        catch ( IllegalArgumentException expected )
+        {
+        }
+    }
+
+    public void testAddsGroupPrincipals() throws GroupAlreadyExistsException
+    {
+        assertEquals( "Wrong group was added to the realm",
+                new GroupPrincipal( "The Does" ),
+                m_realm.addGroup( "The Does" ) );
+        assertTrue( "Group not found in realm",
+                m_realm.getPrincipals( GroupPrincipal.class ).contains(
+                        new GroupPrincipal( "The Does" ) ) );
+    }
+
+    public void testDuplicateGroupsAreNotAllowed() throws Exception
+    {
+        m_realm.addGroup( "The Does" );
+        try
+        {
+            m_realm.addGroup( "The Does" );
+            fail( "Duplicate group added" );
+        }
+        catch ( GroupAlreadyExistsException expected )
+        {
+        }
+    }
+
+    public void testAdditionOfPrincipalToGroup() throws Exception
+    {
+        Principal group = m_realm.addGroup( "The Does" );
+        Principal principal = m_realm.addPrincipal( "John Doe" );
+        assertTrue( "Principal not added to group",
+                m_realm.addPrincipalToGroup( group, principal ) );
+        assertTrue( "Principal not a member of group",
+                m_realm.getPrincipalsForGroup( group ).contains( principal ) );
+    }
+
+    public void testAdditionOfSubGroupToGroup() throws Exception
+    {
+        Principal group = m_realm.addGroup( "The Does" );
+        Principal subGroup = m_realm.addGroup( "The Children" );
+        m_realm.addPrincipalToGroup( group, subGroup );
+        assertTrue( "Principal not a member of group",
+                m_realm.getPrincipalsForGroup( group ).contains( subGroup ) );
+    }
+
+    public void testCyclicMembershipIsProhibited() throws Exception
+    {
+        Principal group = m_realm.addGroup( "jazzmen" );
+        Principal subGroup = m_realm.addGroup( "saxophonists" );
+        m_realm.addPrincipalToGroup( group, subGroup );
+        try
+        {
+            m_realm.addPrincipalToGroup( subGroup, group );
+            fail( "Cyclic dependencies in group members" );
+        }
+        catch ( IllegalArgumentException expected )
+        {
+        }
+    }
+
+    public void testCredentialsValidation() throws Exception
+    {
+        Principal joe = m_realm.addPrincipal( "joe" );
+        m_realm.addCredentialToPrincipal( joe,
+                new Credential( "password", "supersize-me!" ) );
+
+        DefaultCredentialCollection valid = new DefaultCredentialCollection(
+                "username-password" );
+        valid.add( new Credential( "username", "joe" ) );
+        valid.add( new Credential( "password", "supersize-me!" ) );
+
+        assertEquals( "Authentication failed with correct password",
+                new UsernamePrincipal( "joe" ),
+                m_realm.validateCredentials( valid ) );
+
+        DefaultCredentialCollection invalid = new DefaultCredentialCollection(
+                "username-password" );
+        invalid.add( new Credential( "username", "joe" ) );
+        invalid.add( new Credential( "password", "noclue" ) );
+
+        assertEquals( "Authentication succeeded with invalid password", null,
+                m_realm.validateCredentials( invalid ) );
+
+        DefaultCredentialCollection unknown = new DefaultCredentialCollection(
+                "username-password" );
+        unknown.add( new Credential( "username", "garfield" ) );
+        unknown.add( new Credential( "password", "fish" ) );
+
+        assertEquals( "Authentication succeeded for unknown user", null,
+                m_realm.validateCredentials( unknown ) );
+    }
+
+    public void testCredentialCollectionShouldContainExactlyOneUsername()
+    {
+        DefaultCredentialCollection missingUsername = new DefaultCredentialCollection(
+                "username-password" );
+        missingUsername.add( new Credential( "password", "noclue" ) );
+        assertEquals( "Invalid principal returned", null,
+                m_realm.validateCredentials( missingUsername ) );
+
+        DefaultCredentialCollection creds = new DefaultCredentialCollection(
+                "username-password" );
+        creds.add( new Credential( "username", "john" ) );
+        creds.add( new Credential( "username", "joe" ) );
+        creds.add( new Credential( "password", "pasta" ) );
+        try
+        {
+            m_realm.validateCredentials( creds );
+            fail( "More than one username was accepted" );
+        }
+        catch ( IllegalArgumentException expected )
+        {
+        }
+    }
+
+    public void testCredentialCollectionShouldContainExactlyOnePassword()
+    {
+        DefaultCredentialCollection missingPassword = new DefaultCredentialCollection(
+                "username-password" );
+        missingPassword.add( new Credential( "username", "joe" ) );
+        assertEquals( "Invalid principal returned", null,
+                m_realm.validateCredentials( missingPassword ) );
+
+        DefaultCredentialCollection creds = new DefaultCredentialCollection(
+                "username-password" );
+        creds.add( new Credential( "username", "john" ) );
+        creds.add( new Credential( "password", "pasta" ) );
+        creds.add( new Credential( "password", "pizza" ) );
+        try
+        {
+            m_realm.validateCredentials( creds );
+            fail( "More than one password was accepted" );
+        }
+        catch ( IllegalArgumentException expected )
+        {
+        }
+    }
+
+    public void testUsernamePasswordIsTheSoleAuthenticationMethodSupported()
+    {
+        DefaultCredentialCollection creds = new DefaultCredentialCollection(
+                "X509Cert" );
+        try
+        {
+            m_realm.validateCredentials( creds );
+            fail( "Unsupported authentication method considered valid" );
+        }
+        catch ( UnsupportedOperationException expected )
+        {
+        }
+    }
+
+    public void testGroupMembershipIsRecursive() throws Exception
+    {
+        Principal theDoes = m_realm.addGroup( "The Does" );
+        Principal theJazzMen = m_realm.addGroup( "The JazzMen" );
+        Principal theSaxophonists = m_realm.addGroup( "The Saxophonists" );
+        m_realm.addPrincipalToGroup( theJazzMen, theSaxophonists );
+        m_realm.addGroup( "The Others" );
+
+        Principal johnDoe = m_realm.addPrincipal( "John Doe" );
+        m_realm.addPrincipalToGroup( theDoes, johnDoe );
+        m_realm.addPrincipalToGroup( theSaxophonists, johnDoe );
+
+        Collection actual = m_realm.getGroupsForPrincipal( johnDoe );
+        Set expected = new HashSet();
+        expected.add( theDoes );
+        expected.add( theJazzMen );
+        expected.add( theSaxophonists );
+        assertEquals( "Wrong membership", expected, actual );
+    }
+
+    public void testGroupIsNotSelfContained() throws Exception
+    {
+        Principal theDoes = m_realm.addGroup( "The Does" );
+        assertTrue( "Group self-contained",
+                m_realm.getGroupsForPrincipal( theDoes ).isEmpty() );
+    }
+}