aboutsummaryrefslogtreecommitdiffstats
path: root/redback-users
diff options
context:
space:
mode:
authorOlivier Lamy <olamy@apache.org>2012-04-06 09:58:14 +0000
committerOlivier Lamy <olamy@apache.org>2012-04-06 09:58:14 +0000
commit5b06b6673ee8eaed4b46ad8e847e98fe4c90319d (patch)
treee184a7512cd005f5baaf82a94e13fd67646cc2ea /redback-users
parentbe9e1800fdcb3c37c566220c1b2b79650d375000 (diff)
downloadarchiva-5b06b6673ee8eaed4b46ad8e847e98fe4c90319d.tar.gz
archiva-5b06b6673ee8eaed4b46ad8e847e98fe4c90319d.zip
import of redback core sources
http://svn.codehaus.org/redback/redback/trunk/ r1724 git-svn-id: https://svn.apache.org/repos/asf/archiva/redback/redback-core/trunk@1310268 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'redback-users')
-rw-r--r--redback-users/pom.xml33
-rw-r--r--redback-users/redback-authentication-users/pom.xml63
-rw-r--r--redback-users/redback-authentication-users/src/main/java/org/codehaus/plexus/redback/authentication/users/UserManagerAuthenticator.java194
-rw-r--r--redback-users/redback-authentication-users/src/main/resources/META-INF/spring-context.xml34
-rw-r--r--redback-users/redback-authentication-users/src/test/java/org/codehaus/plexus/redback/authentication/users/UserManagerAuthenticatorTest.java211
-rw-r--r--redback-users/redback-authentication-users/src/test/resources/spring-context.xml37
-rw-r--r--redback-users/redback-users-api/pom.xml33
-rw-r--r--redback-users/redback-users-api/src/main/java/org/codehaus/plexus/redback/users/AbstractUserManager.java139
-rw-r--r--redback-users/redback-users-api/src/main/java/org/codehaus/plexus/redback/users/AbstractUserQuery.java119
-rw-r--r--redback-users/redback-users-api/src/main/java/org/codehaus/plexus/redback/users/Messages.java91
-rw-r--r--redback-users/redback-users-api/src/main/java/org/codehaus/plexus/redback/users/PermanentUserException.java49
-rw-r--r--redback-users/redback-users-api/src/main/java/org/codehaus/plexus/redback/users/User.java298
-rw-r--r--redback-users/redback-users-api/src/main/java/org/codehaus/plexus/redback/users/UserManager.java198
-rw-r--r--redback-users/redback-users-api/src/main/java/org/codehaus/plexus/redback/users/UserManagerException.java48
-rw-r--r--redback-users/redback-users-api/src/main/java/org/codehaus/plexus/redback/users/UserManagerListener.java15
-rw-r--r--redback-users/redback-users-api/src/main/java/org/codehaus/plexus/redback/users/UserNotFoundException.java24
-rw-r--r--redback-users/redback-users-api/src/main/java/org/codehaus/plexus/redback/users/UserQuery.java134
-rw-r--r--redback-users/redback-users-api/src/main/resources/org/codehaus/plexus/redback/users/messages.properties10
-rw-r--r--redback-users/redback-users-providers/pom.xml40
-rw-r--r--redback-users/redback-users-providers/redback-users-cached/pom.xml77
-rw-r--r--redback-users/redback-users-providers/redback-users-cached/src/main/java/org/codehaus/plexus/redback/users/cached/CachedUserManager.java318
-rw-r--r--redback-users/redback-users-providers/redback-users-cached/src/main/resources/META-INF/spring-context.xml45
-rw-r--r--redback-users/redback-users-providers/redback-users-cached/src/test/java/org/codehaus/plexus/redback/users/cached/CachedUserManagerTest.java67
-rw-r--r--redback-users/redback-users-providers/redback-users-cached/src/test/resources/org/codehaus/plexus/redback/config-defaults.properties127
-rw-r--r--redback-users/redback-users-providers/redback-users-cached/src/test/resources/spring-context.xml47
-rw-r--r--redback-users/redback-users-providers/redback-users-configurable/pom.xml46
-rw-r--r--redback-users/redback-users-providers/redback-users-configurable/src/main/java/org/codehaus/plexus/redback/users/configurable/ConfigurableUserManager.java179
-rw-r--r--redback-users/redback-users-providers/redback-users-configurable/src/main/resources/META-INF/spring-context.xml33
-rw-r--r--redback-users/redback-users-providers/redback-users-jdo/pom.xml143
-rw-r--r--redback-users/redback-users-providers/redback-users-jdo/src/main/java/org/codehaus/plexus/redback/users/jdo/JdoUserManager.java487
-rw-r--r--redback-users/redback-users-providers/redback-users-jdo/src/main/java/org/codehaus/plexus/redback/users/jdo/JdoUserQuery.java133
-rw-r--r--redback-users/redback-users-providers/redback-users-jdo/src/main/java/org/codehaus/plexus/redback/users/jdo/Messages.java91
-rw-r--r--redback-users/redback-users-providers/redback-users-jdo/src/main/mdo/user.xml143
-rw-r--r--redback-users/redback-users-providers/redback-users-jdo/src/main/resources/META-INF/spring-context.xml33
-rw-r--r--redback-users/redback-users-providers/redback-users-jdo/src/main/resources/org/codehaus/plexus/redback/users/jdo/messages.properties2
-rw-r--r--redback-users/redback-users-providers/redback-users-jdo/src/test/java/org/codehaus/plexus/redback/users/jdo/JdoUserManagerTest.java120
-rw-r--r--redback-users/redback-users-providers/redback-users-jdo/src/test/resources/org/codehaus/plexus/redback/config-defaults.properties127
-rw-r--r--redback-users/redback-users-providers/redback-users-jdo/src/test/resources/spring-context.xml55
-rw-r--r--redback-users/redback-users-providers/redback-users-jdo/src/test/resources/test.properties1
-rw-r--r--redback-users/redback-users-providers/redback-users-ldap/pom.xml95
-rw-r--r--redback-users/redback-users-providers/redback-users-ldap/src/main/java/org/codehaus/plexus/redback/users/ldap/LdapUserManager.java498
-rw-r--r--redback-users/redback-users-providers/redback-users-ldap/src/main/java/org/codehaus/plexus/redback/users/ldap/LdapUserQuery.java60
-rw-r--r--redback-users/redback-users-providers/redback-users-ldap/src/main/java/org/codehaus/plexus/redback/users/ldap/ctl/DefaultLdapController.java298
-rw-r--r--redback-users/redback-users-providers/redback-users-ldap/src/main/java/org/codehaus/plexus/redback/users/ldap/ctl/LdapController.java54
-rw-r--r--redback-users/redback-users-providers/redback-users-ldap/src/main/java/org/codehaus/plexus/redback/users/ldap/ctl/LdapControllerException.java33
-rw-r--r--redback-users/redback-users-providers/redback-users-ldap/src/main/java/org/codehaus/plexus/redback/users/ldap/service/DefaultLdapCacheService.java118
-rw-r--r--redback-users/redback-users-providers/redback-users-ldap/src/main/java/org/codehaus/plexus/redback/users/ldap/service/LdapCacheService.java90
-rw-r--r--redback-users/redback-users-providers/redback-users-ldap/src/main/resources/META-INF/spring-context.xml61
-rw-r--r--redback-users/redback-users-providers/redback-users-ldap/src/test/java/org/codehaus/plexus/redback/users/ldap/LdapUserManagerTest.java337
-rw-r--r--redback-users/redback-users-providers/redback-users-ldap/src/test/java/org/codehaus/plexus/redback/users/ldap/service/LdapCacheServiceTest.java108
-rw-r--r--redback-users/redback-users-providers/redback-users-ldap/src/test/resources/org/codehaus/plexus/redback/users/ldap/users.ldif21
-rw-r--r--redback-users/redback-users-providers/redback-users-ldap/src/test/resources/spring-context.xml59
-rw-r--r--redback-users/redback-users-providers/redback-users-memory/pom.xml55
-rw-r--r--redback-users/redback-users-providers/redback-users-memory/src/main/java/org/codehaus/plexus/redback/users/memory/MemoryUserManager.java332
-rw-r--r--redback-users/redback-users-providers/redback-users-memory/src/main/java/org/codehaus/plexus/redback/users/memory/SimpleUser.java220
-rw-r--r--redback-users/redback-users-providers/redback-users-memory/src/main/java/org/codehaus/plexus/redback/users/memory/SimpleUserQuery.java91
-rw-r--r--redback-users/redback-users-providers/redback-users-memory/src/main/java/org/codehaus/plexus/redback/users/memory/util/UserSorter.java69
-rw-r--r--redback-users/redback-users-providers/redback-users-memory/src/main/resources/META-INF/spring-context.xml33
-rw-r--r--redback-users/redback-users-providers/redback-users-memory/src/test/java/org/codehaus/plexus/redback/users/MemoryUserManagerTest.java45
-rw-r--r--redback-users/redback-users-tests/pom.xml50
-rw-r--r--redback-users/redback-users-tests/src/main/java/org/codehaus/plexus/redback/users/provider/test/AbstractUserManagerTestCase.java609
-rw-r--r--redback-users/redback-users-tests/src/main/java/org/codehaus/plexus/redback/users/provider/test/UserManagerEventTracker.java72
62 files changed, 7452 insertions, 0 deletions
diff --git a/redback-users/pom.xml b/redback-users/pom.xml
new file mode 100644
index 000000000..f8d7889e6
--- /dev/null
+++ b/redback-users/pom.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2006 The Codehaus.
+ ~
+ ~ 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.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.codehaus.redback</groupId>
+ <artifactId>redback</artifactId>
+ <version>1.5-SNAPSHOT</version>
+ </parent>
+ <artifactId>redback-users</artifactId>
+ <name>Redback :: User Management</name>
+ <packaging>pom</packaging>
+ <modules>
+ <module>redback-users-api</module>
+ <module>redback-users-providers</module>
+ <module>redback-users-tests</module>
+ <module>redback-authentication-users</module>
+ </modules>
+</project>
diff --git a/redback-users/redback-authentication-users/pom.xml b/redback-users/redback-authentication-users/pom.xml
new file mode 100644
index 000000000..b4c88dc58
--- /dev/null
+++ b/redback-users/redback-authentication-users/pom.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2006 The Codehaus.
+ ~
+ ~ 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.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.codehaus.redback</groupId>
+ <artifactId>redback-authentication-providers</artifactId>
+ <version>1.5-SNAPSHOT</version>
+ <relativePath>../../redback-authentication/redback-authentication-providers/pom.xml</relativePath>
+ </parent>
+ <artifactId>redback-authentication-users</artifactId>
+ <name>Redback :: Authentication Provider :: Users</name>
+ <dependencies>
+ <dependency>
+ <groupId>org.codehaus.redback</groupId>
+ <artifactId>redback-authentication-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.redback</groupId>
+ <artifactId>redback-policy</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.redback</groupId>
+ <artifactId>redback-users-configurable</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.redback</groupId>
+ <artifactId>redback-users-cached</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-context-support</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>javax.annotation</groupId>
+ <artifactId>jsr250-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.redback</groupId>
+ <artifactId>redback-users-memory</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/redback-users/redback-authentication-users/src/main/java/org/codehaus/plexus/redback/authentication/users/UserManagerAuthenticator.java b/redback-users/redback-authentication-users/src/main/java/org/codehaus/plexus/redback/authentication/users/UserManagerAuthenticator.java
new file mode 100644
index 000000000..81bbd63af
--- /dev/null
+++ b/redback-users/redback-authentication-users/src/main/java/org/codehaus/plexus/redback/authentication/users/UserManagerAuthenticator.java
@@ -0,0 +1,194 @@
+package org.codehaus.plexus.redback.authentication.users;
+
+/*
+ * Copyright 2005 The Codehaus.
+ *
+ * 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.
+ */
+
+import org.codehaus.plexus.redback.authentication.AuthenticationConstants;
+import org.codehaus.plexus.redback.authentication.AuthenticationDataSource;
+import org.codehaus.plexus.redback.authentication.AuthenticationException;
+import org.codehaus.plexus.redback.authentication.AuthenticationResult;
+import org.codehaus.plexus.redback.authentication.Authenticator;
+import org.codehaus.plexus.redback.authentication.PasswordBasedAuthenticationDataSource;
+import org.codehaus.plexus.redback.policy.AccountLockedException;
+import org.codehaus.plexus.redback.policy.MustChangePasswordException;
+import org.codehaus.plexus.redback.policy.PasswordEncoder;
+import org.codehaus.plexus.redback.policy.PolicyViolationException;
+import org.codehaus.plexus.redback.policy.UserSecurityPolicy;
+import org.codehaus.plexus.redback.users.User;
+import org.codehaus.plexus.redback.users.UserManager;
+import org.codehaus.plexus.redback.users.UserNotFoundException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.inject.Named;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * {@link Authenticator} implementation that uses a wrapped {@link UserManager} to authenticate.
+ *
+ * @author <a href='mailto:rahul.thakur.xdev@gmail.com'>Rahul Thakur</a>
+ * @version $Id$
+ */
+@Service( "authenticator#user-manager" )
+public class UserManagerAuthenticator
+ implements Authenticator
+{
+ private Logger log = LoggerFactory.getLogger( UserManagerAuthenticator.class );
+
+ @Inject
+ @Named( value = "userManager#jdo" )
+ private UserManager userManager;
+
+ @Inject
+ private UserSecurityPolicy securityPolicy;
+
+ public String getId()
+ {
+ return "UserManagerAuthenticator";
+ }
+
+ /**
+ * @throws org.codehaus.plexus.redback.policy.AccountLockedException
+ *
+ * @throws MustChangePasswordException
+ * @throws MustChangePasswordException
+ * @throws PolicyViolationException
+ * @see org.codehaus.plexus.redback.authentication.Authenticator#authenticate(org.codehaus.plexus.redback.authentication.AuthenticationDataSource)
+ */
+ public AuthenticationResult authenticate( AuthenticationDataSource ds )
+ throws AuthenticationException, AccountLockedException, MustChangePasswordException
+ {
+ boolean authenticationSuccess = false;
+ String username = null;
+ Exception resultException = null;
+ PasswordBasedAuthenticationDataSource source = (PasswordBasedAuthenticationDataSource) ds;
+ Map<String, String> authnResultExceptionsMap = new HashMap<String, String>();
+
+ try
+ {
+ log.debug( "Authenticate: {}", source );
+ User user = userManager.findUser( source.getPrincipal() );
+ username = user.getUsername();
+
+ if ( user.isLocked() )
+ {
+ throw new AccountLockedException( "Account " + source.getPrincipal() + " is locked.", user );
+ }
+
+ if ( user.isPasswordChangeRequired() && source.isEnforcePasswordChange() )
+ {
+ throw new MustChangePasswordException( "Password expired.", user );
+ }
+
+ PasswordEncoder encoder = securityPolicy.getPasswordEncoder();
+ log.debug( "PasswordEncoder: {}", encoder.getClass().getName() );
+
+ boolean isPasswordValid = encoder.isPasswordValid( user.getEncodedPassword(), source.getPassword() );
+ if ( isPasswordValid )
+ {
+ log.debug( "User {} provided a valid password", source.getPrincipal() );
+
+ try
+ {
+ securityPolicy.extensionPasswordExpiration( user );
+ }
+ catch ( MustChangePasswordException e )
+ {
+ user.setPasswordChangeRequired( true );
+ throw e;
+ }
+
+ authenticationSuccess = true;
+
+ //REDBACK-151 do not make unnessesary updates to the user object
+ if ( user.getCountFailedLoginAttempts() > 0 )
+ {
+ user.setCountFailedLoginAttempts( 0 );
+ userManager.updateUser( user );
+ }
+
+ return new AuthenticationResult( true, source.getPrincipal(), null );
+ }
+ else
+ {
+ log.warn( "Password is Invalid for user " + source.getPrincipal() + "." );
+ authnResultExceptionsMap.put( AuthenticationConstants.AUTHN_NO_SUCH_USER,
+ "Password is Invalid for user " + source.getPrincipal() + "." );
+
+ try
+ {
+ securityPolicy.extensionExcessiveLoginAttempts( user );
+ }
+ finally
+ {
+ userManager.updateUser( user );
+ }
+
+ return new AuthenticationResult( false, source.getPrincipal(), null, authnResultExceptionsMap );
+ }
+ }
+ catch ( UserNotFoundException e )
+ {
+ log.warn( "Login for user " + source.getPrincipal() + " failed. user not found." );
+ resultException = e;
+ authnResultExceptionsMap.put( AuthenticationConstants.AUTHN_NO_SUCH_USER,
+ "Login for user \" + source.getPrincipal() + \" failed. user not found." );
+ }
+
+ return new AuthenticationResult( authenticationSuccess, username, resultException, authnResultExceptionsMap );
+ }
+
+ /**
+ * Returns the wrapped {@link UserManager} used by this {@link Authenticator}
+ * implementation for authentication.
+ *
+ * @return the userManager
+ */
+ public UserManager getUserManager()
+ {
+ return userManager;
+ }
+
+ /**
+ * Sets a {@link UserManager} to be used by this {@link Authenticator}
+ * implementation for authentication.
+ *
+ * @param userManager the userManager to set
+ */
+ public void setUserManager( UserManager userManager )
+ {
+ this.userManager = userManager;
+ }
+
+ public boolean supportsDataSource( AuthenticationDataSource source )
+ {
+ return ( source instanceof PasswordBasedAuthenticationDataSource );
+ }
+
+ public UserSecurityPolicy getSecurityPolicy()
+ {
+ return securityPolicy;
+ }
+
+ public void setSecurityPolicy( UserSecurityPolicy securityPolicy )
+ {
+ this.securityPolicy = securityPolicy;
+ }
+}
diff --git a/redback-users/redback-authentication-users/src/main/resources/META-INF/spring-context.xml b/redback-users/redback-authentication-users/src/main/resources/META-INF/spring-context.xml
new file mode 100644
index 000000000..de5596e90
--- /dev/null
+++ b/redback-users/redback-authentication-users/src/main/resources/META-INF/spring-context.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0"?>
+
+<!--
+ ~ 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.
+ -->
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+ http://www.springframework.org/schema/context
+ http://www.springframework.org/schema/context/spring-context-3.0.xsd"
+ default-lazy-init="true">
+
+ <context:annotation-config />
+ <context:component-scan
+ base-package="org.codehaus.plexus.redback.authentication.users"/>
+
+</beans> \ No newline at end of file
diff --git a/redback-users/redback-authentication-users/src/test/java/org/codehaus/plexus/redback/authentication/users/UserManagerAuthenticatorTest.java b/redback-users/redback-authentication-users/src/test/java/org/codehaus/plexus/redback/authentication/users/UserManagerAuthenticatorTest.java
new file mode 100644
index 000000000..4238c34a7
--- /dev/null
+++ b/redback-users/redback-authentication-users/src/test/java/org/codehaus/plexus/redback/authentication/users/UserManagerAuthenticatorTest.java
@@ -0,0 +1,211 @@
+package org.codehaus.plexus.redback.authentication.users;
+
+/*
+ * Copyright 2005 The Codehaus.
+ *
+ * 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.
+ */
+
+import junit.framework.TestCase;
+import org.codehaus.plexus.redback.authentication.AuthenticationException;
+import org.codehaus.plexus.redback.authentication.AuthenticationResult;
+import org.codehaus.plexus.redback.authentication.Authenticator;
+import org.codehaus.plexus.redback.authentication.PasswordBasedAuthenticationDataSource;
+import org.codehaus.plexus.redback.policy.AccountLockedException;
+import org.codehaus.plexus.redback.policy.MustChangePasswordException;
+import org.codehaus.plexus.redback.policy.UserSecurityPolicy;
+import org.codehaus.plexus.redback.users.User;
+import org.codehaus.plexus.redback.users.UserManager;
+import org.codehaus.plexus.redback.users.UserNotFoundException;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import java.util.Calendar;
+import java.util.Date;
+
+/**
+ * Tests for {@link UserManagerAuthenticator} implementation.
+ *
+ * @author <a href='mailto:rahul.thakur.xdev@gmail.com'>Rahul Thakur</a>
+ */
+@RunWith( SpringJUnit4ClassRunner.class )
+@ContextConfiguration( locations = { "classpath*:/META-INF/spring-context.xml", "classpath*:/spring-context.xml" } )
+public class UserManagerAuthenticatorTest
+ extends TestCase
+{
+ @Inject
+ private UserSecurityPolicy userSecurityPolicy;
+
+ @Inject
+ @Named(value = "authenticator#user-manager")
+ Authenticator component;
+
+ @Inject
+ @Named(value = "userManager#memory")
+ UserManager um;
+
+ @Before
+ public void setUp()
+ throws Exception
+ {
+ super.setUp();
+ userSecurityPolicy.setEnabled( false );
+ }
+
+ @Test
+ public void testLookup()
+ throws Exception
+ {
+ assertNotNull( component );
+ assertEquals( UserManagerAuthenticator.class.getName(), component.getClass().getName() );
+ }
+
+ @Test
+ public void testAuthenticate()
+ throws Exception
+ {
+ // Set up a few users for the Authenticator
+
+ User user = um.createUser( "test", "Test User", "testuser@somedomain.com" );
+ user.setPassword( "testpass" );
+ um.addUser( user );
+
+ user = um.createUser( "guest", "Guest User", "testuser@somedomain.com" );
+ user.setPassword( "guestpass" );
+ um.addUser( user );
+
+ user = um.createUser( "anonymous", "Anonymous User", "testuser@somedomain.com" );
+ user.setPassword( "nopass" );
+ um.addUser( user );
+
+ // test with valid credentials
+ Authenticator auth = component;
+ assertNotNull( auth );
+
+ AuthenticationResult result = auth.authenticate( createAuthDataSource( "anonymous", "nopass" ) );
+ assertTrue( result.isAuthenticated() );
+
+ // test with invalid password
+ result = auth.authenticate( createAuthDataSource( "anonymous", "wrongpass" ) );
+ assertFalse( result.isAuthenticated() );
+ assertNull( result.getException() );
+
+ // test with unknown user
+ result = auth.authenticate( createAuthDataSource( "unknownuser", "wrongpass" ) );
+ assertFalse( result.isAuthenticated() );
+ assertNotNull( result.getException() );
+ assertEquals( result.getException().getClass().getName(), UserNotFoundException.class.getName() );
+ }
+
+ @Test
+ public void testAuthenticateLockedPassword()
+ throws AuthenticationException, MustChangePasswordException, UserNotFoundException
+ {
+ userSecurityPolicy.setEnabled( true );
+
+ // Set up a user for the Authenticator
+ User user = um.createUser( "testuser", "Test User Locked Password", "testuser@somedomain.com" );
+ user.setPassword( "correctpass1" );
+ user.setValidated( true );
+ user.setPasswordChangeRequired( false );
+ um.addUser( user );
+
+ Authenticator auth = component;
+ assertNotNull( auth );
+
+ boolean hasException = false;
+ AuthenticationResult result = null;
+
+ try
+ {
+ // test password lock
+ for ( int i = 0; i < 11; i++ )
+ {
+ result = auth.authenticate( createAuthDataSource( "testuser", "wrongpass" ) );
+ }
+ }
+ catch ( AccountLockedException e )
+ {
+ hasException = true;
+ }
+ finally
+ {
+ assertNotNull( result );
+ assertFalse( result.isAuthenticated() );
+ assertTrue( hasException );
+ }
+ }
+
+ @Test
+ public void testAuthenticateExpiredPassword()
+ throws AuthenticationException, AccountLockedException, UserNotFoundException
+ {
+ userSecurityPolicy.setEnabled( true );
+ userSecurityPolicy.setPasswordExpirationDays( 15 );
+
+ // Set up a user for the Authenticator
+ User user = um.createUser( "testuser", "Test User Expired Password", "testuser@somedomain.com" );
+ user.setPassword( "expiredpass1" );
+ user.setValidated( true );
+ user.setPasswordChangeRequired( false );
+ um.addUser( user );
+
+ Authenticator auth = component;
+ assertNotNull( auth );
+
+ boolean hasException = false;
+
+ try
+ {
+ // test successful authentication
+ AuthenticationResult result = auth.authenticate( createAuthDataSource( "testuser", "expiredpass1" ) );
+ assertTrue( result.isAuthenticated() );
+
+ // test expired password
+ user = um.findUser( "testuser" );
+
+ Calendar currentDate = Calendar.getInstance();
+ currentDate.set( Calendar.YEAR, currentDate.get( Calendar.YEAR ) - 1 );
+ Date lastPasswordChange = currentDate.getTime();
+ user.setLastPasswordChange( lastPasswordChange );
+
+ um.updateUser( user );
+
+ auth.authenticate( createAuthDataSource( "testuser", "expiredpass1" ) );
+ }
+ catch ( MustChangePasswordException e )
+ {
+ hasException = true;
+ }
+ finally
+ {
+ assertTrue( hasException );
+ }
+ }
+
+ private PasswordBasedAuthenticationDataSource createAuthDataSource( String username, String password )
+ {
+ PasswordBasedAuthenticationDataSource source = new PasswordBasedAuthenticationDataSource();
+
+ source.setPrincipal( username );
+ source.setPassword( password );
+
+ return source;
+
+ }
+}
diff --git a/redback-users/redback-authentication-users/src/test/resources/spring-context.xml b/redback-users/redback-authentication-users/src/test/resources/spring-context.xml
new file mode 100644
index 000000000..19c84c510
--- /dev/null
+++ b/redback-users/redback-authentication-users/src/test/resources/spring-context.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0"?>
+
+<!--
+ ~ 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.
+ -->
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+ http://www.springframework.org/schema/context
+ http://www.springframework.org/schema/context/spring-context-3.0.xsd"
+ default-lazy-init="true">
+
+ <bean name="authenticator#user-manager" class="org.codehaus.plexus.redback.authentication.users.UserManagerAuthenticator">
+ <property name="userManager" ref="userManager#memory"/>
+ <property name="securityPolicy" ref="userSecurityPolicy"/>
+ </bean>
+
+ <alias name="userManager#memory" alias="userManager#jdo"/>
+
+</beans> \ No newline at end of file
diff --git a/redback-users/redback-users-api/pom.xml b/redback-users/redback-users-api/pom.xml
new file mode 100644
index 000000000..4aa7188b6
--- /dev/null
+++ b/redback-users/redback-users-api/pom.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2006 The Codehaus.
+ ~
+ ~ 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.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.codehaus.redback</groupId>
+ <artifactId>redback-users</artifactId>
+ <version>1.5-SNAPSHOT</version>
+ </parent>
+ <artifactId>redback-users-api</artifactId>
+ <name>Redback :: User Management API</name>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/redback-users/redback-users-api/src/main/java/org/codehaus/plexus/redback/users/AbstractUserManager.java b/redback-users/redback-users-api/src/main/java/org/codehaus/plexus/redback/users/AbstractUserManager.java
new file mode 100644
index 000000000..55eea3c61
--- /dev/null
+++ b/redback-users/redback-users-api/src/main/java/org/codehaus/plexus/redback/users/AbstractUserManager.java
@@ -0,0 +1,139 @@
+package org.codehaus.plexus.redback.users;
+
+/*
+ * Copyright 2001-2006 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.
+ */
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * AbstractUserManager
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id$
+ */
+public abstract class AbstractUserManager
+ implements UserManager
+{
+ protected Logger log = LoggerFactory.getLogger( getClass() );
+
+ private List<UserManagerListener> listeners = new ArrayList<UserManagerListener>();
+
+ public void addUserManagerListener( UserManagerListener listener )
+ {
+ if ( !listeners.contains( listener ) )
+ {
+ listeners.add( listener );
+ }
+ }
+
+ public void removeUserManagerListener( UserManagerListener listener )
+ {
+ listeners.remove( listener );
+ }
+
+ protected void fireUserManagerInit( boolean freshDatabase )
+ {
+ for ( UserManagerListener listener : listeners )
+ {
+ try
+ {
+ listener.userManagerInit( freshDatabase );
+ }
+ catch ( Exception e )
+ {
+ // Ignore
+ }
+ }
+ }
+
+ protected void fireUserManagerUserAdded( User addedUser )
+ {
+ for ( UserManagerListener listener : listeners )
+ {
+ try
+ {
+ listener.userManagerUserAdded( addedUser );
+ }
+ catch ( Exception e )
+ {
+ // Ignore
+ }
+ }
+ }
+
+ protected void fireUserManagerUserRemoved( User removedUser )
+ {
+ for ( UserManagerListener listener : listeners )
+ {
+ try
+ {
+ listener.userManagerUserRemoved( removedUser );
+ }
+ catch ( Exception e )
+ {
+ // Ignore
+ }
+ }
+ }
+
+ protected void fireUserManagerUserUpdated( User updatedUser )
+ {
+ for ( UserManagerListener listener : listeners )
+ {
+ try
+ {
+ listener.userManagerUserUpdated( updatedUser );
+ }
+ catch ( Exception e )
+ {
+ // Ignore
+ }
+ }
+ }
+
+ public User getGuestUser()
+ throws UserNotFoundException
+ {
+ return findUser( GUEST_USERNAME );
+ }
+
+ public User createGuestUser()
+ {
+ try
+ {
+ User u = getGuestUser();
+ if ( u != null )
+ {
+ return u;
+ }
+ }
+ catch ( UserNotFoundException e )
+ {
+ //Nothing to do
+ }
+
+ User user = createUser( GUEST_USERNAME, "Guest", "" );
+ user.setPermanent( true );
+ user.setPasswordChangeRequired( false );
+
+ user = addUser( user );
+ return user;
+ }
+}
diff --git a/redback-users/redback-users-api/src/main/java/org/codehaus/plexus/redback/users/AbstractUserQuery.java b/redback-users/redback-users-api/src/main/java/org/codehaus/plexus/redback/users/AbstractUserQuery.java
new file mode 100644
index 000000000..746b51685
--- /dev/null
+++ b/redback-users/redback-users-api/src/main/java/org/codehaus/plexus/redback/users/AbstractUserQuery.java
@@ -0,0 +1,119 @@
+package org.codehaus.plexus.redback.users;
+
+/*
+ * Copyright 2001-2006 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.
+ */
+
+/**
+ * Abstract Implementation of UserQuery.
+ * Intended to be subclassed by UserManager providers.
+ */
+public abstract class AbstractUserQuery
+ implements UserQuery
+{
+
+ private String username;
+
+ private String fullName;
+
+ private String email;
+
+ private long maxResults = -1;
+
+ private long firstResult;
+
+ private String orderBy = ORDER_BY_USERNAME;
+
+ private boolean ascending = true;
+
+ public String getUsername()
+ {
+ return username;
+ }
+
+ public void setUsername( String userName )
+ {
+ this.username = userName;
+ }
+
+ public String getFullName()
+ {
+ return fullName;
+ }
+
+ public void setFullName( String fullName )
+ {
+ this.fullName = fullName;
+ }
+
+ public String getEmail()
+ {
+ return email;
+ }
+
+ public void setEmail( String email )
+ {
+ this.email = email;
+ }
+
+ public long getFirstResult()
+ {
+ return firstResult;
+ }
+
+ public void setFirstResult( int firstResult )
+ {
+ this.firstResult = firstResult;
+ }
+
+ public long getMaxResults()
+ {
+ return maxResults;
+ }
+
+ public void setMaxResults( int maxResults )
+ {
+ this.maxResults = maxResults;
+ }
+
+ public String getOrderBy()
+ {
+ return orderBy;
+ }
+
+ public void setOrderBy( String orderBy )
+ {
+ if ( orderBy == null )
+ {
+ throw new IllegalArgumentException( "orderBy cannot be set to null" );
+ }
+ else if ( !ALLOWED_ORDER_FIELDS.contains( orderBy ) )
+ {
+ throw new IllegalArgumentException( orderBy + " is not an allowed orderBy field: " + orderBy );
+ }
+ this.orderBy = orderBy;
+ }
+
+ public boolean isAscending()
+ {
+ return ascending;
+ }
+
+ public void setAscending( boolean ascending )
+ {
+ this.ascending = ascending;
+ }
+
+} \ No newline at end of file
diff --git a/redback-users/redback-users-api/src/main/java/org/codehaus/plexus/redback/users/Messages.java b/redback-users/redback-users-api/src/main/java/org/codehaus/plexus/redback/users/Messages.java
new file mode 100644
index 000000000..e5727beac
--- /dev/null
+++ b/redback-users/redback-users-api/src/main/java/org/codehaus/plexus/redback/users/Messages.java
@@ -0,0 +1,91 @@
+package org.codehaus.plexus.redback.users;
+
+/*
+ * Copyright 2001-2006 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.
+ */
+
+import java.text.MessageFormat;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+/**
+ * Localized Message Handling.
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id$
+ */
+public class Messages
+{
+ private static final String BUNDLE_NAME = "org.codehaus.plexus.redback.users.messages"; //$NON-NLS-1$
+
+ private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle.getBundle( BUNDLE_NAME );
+
+ /**
+ * Get a Message as-is from the Resource Bundle.
+ *
+ * @param key the key for the message to get.
+ * @return the value of the key, or "!key!" if the key is not found.
+ */
+ public static String getString( String key )
+ {
+ try
+ {
+ return RESOURCE_BUNDLE.getString( key );
+ }
+ catch ( MissingResourceException e )
+ {
+ return '!' + key + '!';
+ }
+ }
+
+ /**
+ * Gets a Message from the Resource Bundle, with {1} and {2} style arguments.
+ *
+ * @param key the key for the message to get.
+ * @param arg the argument to pass in.
+ * @return the value of the key, or "!key!" if the key is not found.
+ */
+ public static String getString( String key, Object arg )
+ {
+ return getString( key, new Object[] { arg } );
+ }
+
+ /**
+ * Gets a Message from the Resource Bundle, with {1} and {2} style arguments.
+ *
+ * @param key the key for the message to get.
+ * @param args the arguments to pass in.
+ * @return the value of the key, or "!key!" if the key is not found.
+ */
+ public static String getString( String key, Object args[] )
+ {
+ try
+ {
+ String pattern = RESOURCE_BUNDLE.getString( key );
+ return MessageFormat.format( pattern, args );
+ }
+ catch ( MissingResourceException e )
+ {
+ return '!' + key + '!';
+ }
+ }
+
+ /**
+ * Prevent Instantiation.
+ */
+ private Messages()
+ {
+ }
+}
diff --git a/redback-users/redback-users-api/src/main/java/org/codehaus/plexus/redback/users/PermanentUserException.java b/redback-users/redback-users-api/src/main/java/org/codehaus/plexus/redback/users/PermanentUserException.java
new file mode 100644
index 000000000..025e443ac
--- /dev/null
+++ b/redback-users/redback-users-api/src/main/java/org/codehaus/plexus/redback/users/PermanentUserException.java
@@ -0,0 +1,49 @@
+package org.codehaus.plexus.redback.users;
+
+/*
+ * Copyright 2001-2006 The Codehaus.
+ *
+ * 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.
+ */
+
+/**
+ * PermanentUserException - tossed when a forbidden action against a permanent user
+ * occurs.
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id$
+ */
+public class PermanentUserException
+ extends UserManagerException
+{
+
+ public PermanentUserException()
+ {
+ super();
+ }
+
+ public PermanentUserException( String message, Throwable cause )
+ {
+ super( message, cause );
+ }
+
+ public PermanentUserException( String message )
+ {
+ super( message );
+ }
+
+ public PermanentUserException( Throwable cause )
+ {
+ super( cause );
+ }
+}
diff --git a/redback-users/redback-users-api/src/main/java/org/codehaus/plexus/redback/users/User.java b/redback-users/redback-users-api/src/main/java/org/codehaus/plexus/redback/users/User.java
new file mode 100644
index 000000000..40a0ce8bd
--- /dev/null
+++ b/redback-users/redback-users-api/src/main/java/org/codehaus/plexus/redback/users/User.java
@@ -0,0 +1,298 @@
+package org.codehaus.plexus.redback.users;
+
+/*
+ * Copyright 2001-2006 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.
+ */
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * The User Object.
+ *
+ * @author Jason van Zyl
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ */
+public interface User
+ extends Serializable
+{
+
+ /**
+ * This is the object used to track the user within the provider.
+ *
+ * @return the principal for this user.
+ */
+ Object getPrincipal();
+
+ // --------------------------------------------------------------------
+ // Standard User Requirements.
+ // --------------------------------------------------------------------
+
+ /**
+ * Gets the User Name for this user.
+ * <p/>
+ * This field is required, and should never be empty.
+ *
+ * @return the user name.
+ */
+ String getUsername();
+
+ /**
+ * Sets the User Name for this user.
+ * <p/>
+ * This field is required, and should never be empty.
+ *
+ * @param name the user name.
+ */
+ void setUsername( String name );
+
+ /**
+ * Gets the Full Name for this user.
+ * <p/>
+ * This field is required, and should never be empty.
+ *
+ * @return the full name.
+ */
+ String getFullName();
+
+ /**
+ * Sets the Full Name for this user.
+ * <p/>
+ * This field is required, and should never be empty.
+ *
+ * @param name the full name.
+ */
+ void setFullName( String name );
+
+ /**
+ * Gets the email address for this user.
+ * <p/>
+ * This field is required, and should never be empty.
+ *
+ * @return the email address.
+ */
+ String getEmail();
+
+ /**
+ * Sets the email address for this user.
+ * <p/>
+ * This field is required, and should never be empty.
+ *
+ * @param address the email address.
+ */
+ void setEmail( String address );
+
+ // --------------------------------------------------------------------
+ // Password Requirements.
+ // --------------------------------------------------------------------
+
+ /**
+ * Gets the Raw (unencoded) Password.
+ * Used only on password change requests.
+ * <p/>
+ * <p>
+ * <b>Notes for User Providers</b>
+ * </p>
+ * <ol>
+ * <li>
+ * Providers need to look for a value in here to indicate if the user is
+ * intending to change their password.
+ * </li>
+ * <li>
+ * The providers of this interface need to use this field, encode the password, place it's value
+ * into the encodedPassword field, and clear out the raw unencoded password field.
+ * </li>
+ * <li>
+ * This field should never be stored on disk.
+ * </li>
+ * </ol>
+ *
+ * @return the raw encoded password.
+ */
+ String getPassword();
+
+ /**
+ * Sets the raw (unencoded) password for this user.
+ *
+ * @param rawPassword the raw unencoded password for this user.
+ * @see #getPassword()
+ */
+ void setPassword( String rawPassword );
+
+ /**
+ * Gets the Encoded Password.
+ *
+ * @return the encoded password.
+ */
+ String getEncodedPassword();
+
+ /**
+ * Sets the Encoded Password.
+ * <p/>
+ * This field is populated by the {@link UserManager} process.
+ *
+ * @param encodedPassword
+ */
+ void setEncodedPassword( String encodedPassword );
+
+ /**
+ * Gets the Date of the Last Password Change.
+ * <p/>
+ * Used by password management policies to enforce password expiration rules.
+ *
+ * @return the date of the last password change.
+ */
+ Date getLastPasswordChange();
+
+ /**
+ * Sets the Last Password Change Date.
+ * <p/>
+ * This field is populated by the {@link UserManager} process.
+ *
+ * @param passwordChangeDate the date that the last password change occured.
+ */
+ void setLastPasswordChange( Date passwordChangeDate );
+
+ /**
+ * Gets the list of previous password (in encoded format).
+ * <p/>
+ * Used by password management policies to enforce password reuse rules.
+ *
+ * @return the list of {@link String} objects. Represents previous passwords (in encoded format).
+ */
+ List<String> getPreviousEncodedPasswords();
+
+ /**
+ * Sets the list of previous passwords (in encoded format)
+ *
+ * @param encodedPasswordList (list of {@link String} objects.) the previously passwords in encoded format.
+ */
+ void setPreviousEncodedPasswords( List<String> encodedPasswordList );
+
+ /**
+ * Add encoded password to previously passwords in encoded format.
+ *
+ * @param encodedPassword the encoded password to add.
+ */
+ void addPreviousEncodedPassword( String encodedPassword );
+
+ // --------------------------------------------------------------------
+ // State
+ // --------------------------------------------------------------------
+
+ /**
+ * Gets the flag indicating if this user is a permanent user or not.
+ * <p/>
+ * Usually Root / Admin / Guest users are flagged as such.
+ */
+ boolean isPermanent();
+
+ /**
+ * Sets the permanent flag for this user.
+ * <p/>
+ * Users such as Root / Admin / Guest are typically flagged as permanent.
+ *
+ * @param permanent true if permanent.
+ */
+ void setPermanent( boolean permanent );
+
+ /**
+ * Determines if this user account is locked from use or not.
+ * <p/>
+ * This state is set from an administrative point of view, or due to
+ * excessive failed login attempts.
+ *
+ * @return true if account is locked.
+ */
+ boolean isLocked();
+
+ /**
+ * Sets the locked state of this account.
+ *
+ * @param locked true if account is to be locked.
+ */
+ void setLocked( boolean locked );
+
+ /**
+ * Determines if this user account must change their password on next login.
+ *
+ * @return true if user must change password on next login.
+ */
+ boolean isPasswordChangeRequired();
+
+ /**
+ * Sets the flag to indicate if this user must change their password on next login.
+ *
+ * @param changeRequired true if user must change password on next login.
+ */
+ void setPasswordChangeRequired( boolean changeRequired );
+
+ /**
+ * Gets the flag indicating if this user has been validated (or not)
+ *
+ * @return true if validated.
+ */
+ boolean isValidated();
+
+ /**
+ * Sets the flag indicating if this user has been validated (or not)
+ *
+ * @param valid true if validated.
+ */
+ void setValidated( boolean valid );
+
+ // --------------------------------------------------------------------
+ // Statistics
+ // --------------------------------------------------------------------
+
+ /**
+ * Get Count of Failed Login Attempts.
+ *
+ * @return the count of failed login attempts.
+ */
+ int getCountFailedLoginAttempts();
+
+ /**
+ * Set the count of failed login attempts.
+ *
+ * @param count the count of failed login attempts.
+ */
+ void setCountFailedLoginAttempts( int count );
+
+ /**
+ * Get the Creation Date for this account.
+ *
+ * @return the date of creation for this account.
+ */
+ Date getAccountCreationDate();
+
+ /**
+ * Set the Creation Date for this account.
+ */
+ void setAccountCreationDate( Date date );
+
+ /**
+ * Get the Last Successful Login Date for this account.
+ *
+ * @return the date of the last successful login
+ */
+ Date getLastLoginDate();
+
+ /**
+ * Sets the Last Successful Login Date for this account.
+ */
+ void setLastLoginDate( Date date );
+}
diff --git a/redback-users/redback-users-api/src/main/java/org/codehaus/plexus/redback/users/UserManager.java b/redback-users/redback-users-api/src/main/java/org/codehaus/plexus/redback/users/UserManager.java
new file mode 100644
index 000000000..41a02551e
--- /dev/null
+++ b/redback-users/redback-users-api/src/main/java/org/codehaus/plexus/redback/users/UserManager.java
@@ -0,0 +1,198 @@
+package org.codehaus.plexus.redback.users;
+
+/*
+ * Copyright 2001-2006 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.
+ */
+
+import java.util.List;
+
+/**
+ * User Manager Interface
+ *
+ * @author Jason van Zyl
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ */
+public interface UserManager
+{
+
+ static final String GUEST_USERNAME = "guest";
+
+ /**
+ * Is the UserManager read only? if so then create and modify actions are to be disabled
+ *
+ * @return boolean true if user manager is disabled
+ */
+ boolean isReadOnly();
+
+ /**
+ * An Identifier for the UserManager.
+ *
+ * @return the user manager identifier.
+ */
+ String getId();
+
+ /**
+ * Add a {@link UserManagerListener} to track major events in the
+ * UserManager.
+ *
+ * @param listener the listener to add.
+ */
+ void addUserManagerListener( UserManagerListener listener );
+
+ /**
+ * Remove a {@link UserManagerListener} from the collection of listeners.
+ *
+ * @param listener the listener to remove.
+ */
+ void removeUserManagerListener( UserManagerListener listener );
+
+ /**
+ * Factory method to create new User Objects based on provider specific
+ * implementation.
+ * <p/>
+ * User objects created this way do not exist in the provider's underlying
+ * data store until a call to {@link #addUser(User)} is made.
+ *
+ * @param username the username for this user.
+ * @param fullName the full name for this user.
+ * @param emailAddress the email address for this user.
+ * @return the new user object ready to use.
+ */
+ User createUser( String username, String fullName, String emailAddress );
+
+ /**
+ * Factory method to create the guest user.
+ *
+ * @return The guest user
+ */
+ User createGuestUser();
+
+ /**
+ * Factory method to create {@link UserQuery}s based on provider specific
+ * implementations.
+ *
+ * @return the provider implementation of UserQuery
+ */
+ UserQuery createUserQuery();
+
+ /**
+ * Get the List of {@link User} objects.
+ *
+ * @return the List of {@link User} Objects.
+ */
+ List<User> getUsers();
+
+ List<User> getUsers( boolean orderAscending );
+
+ /**
+ * Add a User.
+ *
+ * @param user the user to add.
+ * @return the user that was just added.
+ */
+ User addUser( User user );
+
+ /**
+ * Update a User.
+ *
+ * @param user the user to update.
+ * @return the user that was just updated.
+ * @throws UserNotFoundException if the user was not found to update.
+ */
+ User updateUser( User user )
+ throws UserNotFoundException;
+
+ /**
+ * Find a User using a User name.
+ *
+ * @param username the username to find.
+ * @return the user.
+ * @throws UserNotFoundException if the user was not found.
+ */
+ User findUser( String username )
+ throws UserNotFoundException;
+
+ /**
+ * Get the guest user.
+ *
+ * @return the guest user.
+ */
+ User getGuestUser()
+ throws UserNotFoundException;
+
+ List<User> findUsersByUsernameKey( String usernameKey, boolean orderAscending );
+
+ List<User> findUsersByFullNameKey( String fullNameKey, boolean orderAscending );
+
+ List<User> findUsersByEmailKey( String emailKey, boolean orderAscending );
+
+ /**
+ * Find users matching properties, ordering and range as specified by the
+ * {@link UserQuery}.
+ *
+ * @param query the query.
+ * @return a List of {@link User} objects.
+ */
+ List<User> findUsersByQuery( UserQuery query );
+
+ /**
+ * Find a User using the principal.
+ *
+ * @param principal the principal to look for.
+ * @return the user.
+ * @throws UserNotFoundException if the user was not found.
+ */
+ User findUser( Object principal )
+ throws UserNotFoundException;
+
+ /**
+ * true if the user exists, false if it doesn't
+ *
+ * @param principal
+ * @return true, if user exists
+ */
+ boolean userExists( Object principal );
+
+ /**
+ * Delete a user using the principal.
+ *
+ * @param principal the principal to look for.
+ * @throws UserNotFoundException the user was not found.
+ */
+ void deleteUser( Object principal )
+ throws UserNotFoundException;
+
+ /**
+ * Delete a user using the username.
+ *
+ * @param username the username to look for.
+ * @throws UserNotFoundException the user was not found.
+ */
+ void deleteUser( String username )
+ throws UserNotFoundException;
+
+ /**
+ * Add a user to the database without checking for consistency or adjusting the password. Should only be used for
+ * re-importing known-good data.
+ *
+ * @param user the user to add
+ */
+ void addUserUnchecked( User user );
+
+ void eraseDatabase();
+
+ User updateUser( User user, boolean passwordChangeRequired )
+ throws UserNotFoundException;
+}
diff --git a/redback-users/redback-users-api/src/main/java/org/codehaus/plexus/redback/users/UserManagerException.java b/redback-users/redback-users-api/src/main/java/org/codehaus/plexus/redback/users/UserManagerException.java
new file mode 100644
index 000000000..e1826a658
--- /dev/null
+++ b/redback-users/redback-users-api/src/main/java/org/codehaus/plexus/redback/users/UserManagerException.java
@@ -0,0 +1,48 @@
+package org.codehaus.plexus.redback.users;
+
+/*
+ * Copyright 2001-2006 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.
+ */
+
+/**
+ * UserManagerException
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id$
+ */
+public class UserManagerException
+ extends RuntimeException
+{
+
+ public UserManagerException()
+ {
+ super();
+ }
+
+ public UserManagerException( String message, Throwable cause )
+ {
+ super( message, cause );
+ }
+
+ public UserManagerException( String message )
+ {
+ super( message );
+ }
+
+ public UserManagerException( Throwable cause )
+ {
+ super( cause );
+ }
+}
diff --git a/redback-users/redback-users-api/src/main/java/org/codehaus/plexus/redback/users/UserManagerListener.java b/redback-users/redback-users-api/src/main/java/org/codehaus/plexus/redback/users/UserManagerListener.java
new file mode 100644
index 000000000..9da3a07b8
--- /dev/null
+++ b/redback-users/redback-users-api/src/main/java/org/codehaus/plexus/redback/users/UserManagerListener.java
@@ -0,0 +1,15 @@
+package org.codehaus.plexus.redback.users;
+
+/**
+ * UserManagerListener
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id$
+ */
+public interface UserManagerListener
+{
+ public void userManagerInit( boolean freshDatabase );
+ public void userManagerUserAdded( User user );
+ public void userManagerUserRemoved( User user );
+ public void userManagerUserUpdated( User user );
+}
diff --git a/redback-users/redback-users-api/src/main/java/org/codehaus/plexus/redback/users/UserNotFoundException.java b/redback-users/redback-users-api/src/main/java/org/codehaus/plexus/redback/users/UserNotFoundException.java
new file mode 100644
index 000000000..71d7f820e
--- /dev/null
+++ b/redback-users/redback-users-api/src/main/java/org/codehaus/plexus/redback/users/UserNotFoundException.java
@@ -0,0 +1,24 @@
+package org.codehaus.plexus.redback.users;
+
+/**
+ * @author Jason van Zyl
+ */
+public class UserNotFoundException
+ extends Exception
+{
+ public UserNotFoundException( String string )
+ {
+ super( string );
+ }
+
+ public UserNotFoundException( String string,
+ Throwable throwable )
+ {
+ super( string, throwable );
+ }
+
+ public UserNotFoundException( Throwable throwable )
+ {
+ super( throwable );
+ }
+}
diff --git a/redback-users/redback-users-api/src/main/java/org/codehaus/plexus/redback/users/UserQuery.java b/redback-users/redback-users-api/src/main/java/org/codehaus/plexus/redback/users/UserQuery.java
new file mode 100644
index 000000000..42f9d16f8
--- /dev/null
+++ b/redback-users/redback-users-api/src/main/java/org/codehaus/plexus/redback/users/UserQuery.java
@@ -0,0 +1,134 @@
+package org.codehaus.plexus.redback.users;
+
+/*
+ * Copyright 2001-2006 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.
+ */
+
+import java.util.Set;
+import java.util.HashSet;
+import java.util.Arrays;
+
+
+public interface UserQuery
+{
+ final static String ORDER_BY_USERNAME = "username";
+
+ final static String ORDER_BY_FULLNAME = "fullname";
+
+ final static String ORDER_BY_EMAIL = "email";
+
+ final static Set<String> ALLOWED_ORDER_FIELDS =
+ new HashSet<String>( Arrays.asList( ORDER_BY_USERNAME, ORDER_BY_FULLNAME, ORDER_BY_EMAIL ) );
+
+ /**
+ * Returns the case insensitive substring user name criteria.
+ *
+ * @return the username criteria.
+ */
+ String getUsername();
+
+ /**
+ * Sets the case insensitive substring user name criteria.
+ *
+ * @param userName the username criteria
+ */
+ void setUsername( String userName );
+
+ /**
+ * Returns the case insensitive substring full name criteria.
+ *
+ * @return the username criteria.
+ */
+ String getFullName();
+
+ /**
+ * Sets the case insensitive substring full name criteria.
+ *
+ * @param fullName the full name criteria
+ */
+ void setFullName( String fullName );
+
+ /**
+ * Returns the case insensitive substring email criteria.
+ *
+ * @return the email criteria.
+ */
+ String getEmail();
+
+ /**
+ * Sets the case insensitive substring email criteria.
+ *
+ * @param email the email criteria
+ */
+ void setEmail( String email );
+
+ /**
+ * Returns the index (zero based) of the first result to include. Useful for paging.
+ *
+ * @return the first index
+ */
+ long getFirstResult();
+
+ /**
+ * Sets the index (zero based) of the first result to include. Useful for paging.
+ *
+ * @param firstResult the first index
+ */
+ void setFirstResult( int firstResult );
+
+ /**
+ * Returns the maximum number of users to return.
+ *
+ * @return the maximum number of users to return.
+ */
+ long getMaxResults();
+
+ /**
+ * Sets the maximum number of users to return.
+ *
+ * @param maxResults the maximum number of users to return.
+ */
+ void setMaxResults( int maxResults );
+
+ /**
+ * Returns the property used to order the results of this query.
+ * This is one of {@link #ORDER_BY_USERNAME}, {@link #ORDER_BY_FULLNAME} or {@link #ORDER_BY_EMAIL}.
+ *
+ * @return the order property.
+ */
+ String getOrderBy();
+
+ /**
+ * Sets the property used to order the results of this query.
+ * This is one of {@link #ORDER_BY_USERNAME}, {@link #ORDER_BY_FULLNAME} or {@link #ORDER_BY_EMAIL}.
+ *
+ * @param orderBy the order property.
+ */
+ void setOrderBy( String orderBy );
+
+ /**
+ * Returns true if the results should be returned in ascending order.
+ *
+ * @return ascending
+ */
+ boolean isAscending();
+
+ /**
+ * Set this to true if the results should be returned in ascending order.
+ *
+ * @param ascending true if the results should be returned in ascending
+ */
+ void setAscending( boolean ascending );
+}
diff --git a/redback-users/redback-users-api/src/main/resources/org/codehaus/plexus/redback/users/messages.properties b/redback-users/redback-users-api/src/main/resources/org/codehaus/plexus/redback/users/messages.properties
new file mode 100644
index 000000000..c1ca0b89f
--- /dev/null
+++ b/redback-users/redback-users-api/src/main/resources/org/codehaus/plexus/redback/users/messages.properties
@@ -0,0 +1,10 @@
+password.encoder.no.such.algoritm=The specified algorithm {0} is not available in the JAAS Implementation of this JVM.
+password.encoder.unsupported.encoding=The UTF-8 Encoding is not available in the JAAS Implementation of this JVM.
+user.password.violation.missing=You must provide a password.
+user.password.violation.length=You must provide a password between {0} and {1} characters in length.
+user.password.violation.length.misconfigured=Password Length Rule is misconfigured. Specified minimum of ({0}) is larger than specified maximum of ({1}). Rule disabled.
+user.password.violation.alpha=You must provide a password containing at least {0} alphabetic character(s).
+user.password.violation.numeric=You must provide a password containing at least {0} numeric character(s).
+user.password.violation.reuse=Your password cannot match any of your previous {0} password(s).
+user.password.violation.alphanum.only=You must provide a password containing all alpha-numeric characters.
+user.password.violation.whitespace.detected=You must provide a password without whitespace characters. \ No newline at end of file
diff --git a/redback-users/redback-users-providers/pom.xml b/redback-users/redback-users-providers/pom.xml
new file mode 100644
index 000000000..738602896
--- /dev/null
+++ b/redback-users/redback-users-providers/pom.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2006 The Codehaus.
+ ~
+ ~ 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.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.codehaus.redback</groupId>
+ <artifactId>redback-users</artifactId>
+ <version>1.5-SNAPSHOT</version>
+ </parent>
+ <artifactId>redback-users-providers</artifactId>
+ <name>Redback :: Users Providers</name>
+ <packaging>pom</packaging>
+ <dependencies>
+ <dependency>
+ <groupId>org.codehaus.redback</groupId>
+ <artifactId>redback-users-api</artifactId>
+ </dependency>
+ </dependencies>
+ <modules>
+ <module>redback-users-cached</module>
+ <module>redback-users-memory</module>
+ <module>redback-users-jdo</module>
+ <module>redback-users-ldap</module>
+ <module>redback-users-configurable</module>
+ </modules>
+</project>
diff --git a/redback-users/redback-users-providers/redback-users-cached/pom.xml b/redback-users/redback-users-providers/redback-users-cached/pom.xml
new file mode 100644
index 000000000..3b8cc7dab
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-cached/pom.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2006 The Codehaus.
+ ~
+ ~ 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.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.codehaus.redback</groupId>
+ <artifactId>redback-users-providers</artifactId>
+ <version>1.5-SNAPSHOT</version>
+ </parent>
+ <artifactId>redback-users-cached</artifactId>
+ <name>Redback :: Users Provider :: Cached</name>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.codehaus.redback</groupId>
+ <artifactId>redback-policy</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.redback.components.cache</groupId>
+ <artifactId>spring-cache-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.redback.components.cache</groupId>
+ <artifactId>spring-cache-ehcache</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.ehcache</groupId>
+ <artifactId>ehcache-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-context-support</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>javax.annotation</groupId>
+ <artifactId>jsr250-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.redback</groupId>
+ <artifactId>redback-users-jdo</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.redback</groupId>
+ <artifactId>redback-users-tests</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.redback</groupId>
+ <artifactId>redback-users-memory</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.hsqldb</groupId>
+ <artifactId>hsqldb</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/redback-users/redback-users-providers/redback-users-cached/src/main/java/org/codehaus/plexus/redback/users/cached/CachedUserManager.java b/redback-users/redback-users-providers/redback-users-cached/src/main/java/org/codehaus/plexus/redback/users/cached/CachedUserManager.java
new file mode 100644
index 000000000..b0df43c65
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-cached/src/main/java/org/codehaus/plexus/redback/users/cached/CachedUserManager.java
@@ -0,0 +1,318 @@
+package org.codehaus.plexus.redback.users.cached;
+
+/*
+ * Copyright 2001-2006 The Codehaus.
+ *
+ * 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.
+ */
+
+import org.codehaus.plexus.cache.Cache;
+import org.codehaus.plexus.redback.users.User;
+import org.codehaus.plexus.redback.users.UserManager;
+import org.codehaus.plexus.redback.users.UserManagerListener;
+import org.codehaus.plexus.redback.users.UserNotFoundException;
+import org.codehaus.plexus.redback.users.UserQuery;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import java.util.List;
+
+/**
+ * CachedUserManager
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id$
+ */
+@Service( "userManager#cached" )
+public class CachedUserManager
+ implements UserManager, UserManagerListener
+{
+
+ private Logger log = LoggerFactory.getLogger( getClass() );
+
+ @Inject
+ @Named( value = "userManager#jdo" )
+ private UserManager userImpl;
+
+ @Inject
+ @Named( value = "cache#users" )
+ private Cache usersCache;
+
+ public boolean isReadOnly()
+ {
+ return userImpl.isReadOnly();
+ }
+
+ public User createGuestUser()
+ {
+ return userImpl.createGuestUser();
+ }
+
+ public User addUser( User user )
+ {
+ if ( user != null )
+ {
+ usersCache.remove( user.getPrincipal() );
+ }
+ return this.userImpl.addUser( user );
+ }
+
+ public void addUserManagerListener( UserManagerListener listener )
+ {
+ this.userImpl.addUserManagerListener( listener );
+ }
+
+ public void addUserUnchecked( User user )
+ {
+ if ( user != null )
+ {
+ usersCache.remove( user.getPrincipal() );
+ }
+ this.userImpl.addUserUnchecked( user );
+ }
+
+ public User createUser( String username, String fullName, String emailAddress )
+ {
+ usersCache.remove( username );
+ return this.userImpl.createUser( username, fullName, emailAddress );
+ }
+
+ public void deleteUser( Object principal )
+ throws UserNotFoundException
+ {
+ usersCache.remove( principal );
+ this.userImpl.deleteUser( principal );
+ }
+
+ public void deleteUser( String username )
+ throws UserNotFoundException
+ {
+ usersCache.remove( username );
+ this.userImpl.deleteUser( username );
+ }
+
+ public void eraseDatabase()
+ {
+ try
+ {
+ this.userImpl.eraseDatabase();
+ }
+ finally
+ {
+ usersCache.clear();
+ }
+ }
+
+ public User findUser( String username )
+ throws UserNotFoundException
+ {
+ if ( GUEST_USERNAME.equals( username ) )
+ {
+ return getGuestUser();
+ }
+
+ Object el = usersCache.get( username );
+ if ( el != null )
+ {
+ return (User) el;
+ }
+ else
+ {
+ User user = this.userImpl.findUser( username );
+ usersCache.put( username, user );
+ return user;
+ }
+ }
+
+ public User getGuestUser()
+ throws UserNotFoundException
+ {
+ Object el = usersCache.get( GUEST_USERNAME );
+ if ( el != null )
+ {
+ return (User) el;
+ }
+ else
+ {
+ User user = this.userImpl.getGuestUser();
+ usersCache.put( GUEST_USERNAME, user );
+ return user;
+ }
+ }
+
+ public User findUser( Object principal )
+ throws UserNotFoundException
+ {
+ Object el = usersCache.get( principal );
+ if ( el != null )
+ {
+ return (User) el;
+ }
+ else
+ {
+ User user = this.userImpl.findUser( principal );
+ usersCache.put( principal, user );
+ return user;
+ }
+ }
+
+ public UserQuery createUserQuery()
+ {
+ return userImpl.createUserQuery();
+ }
+
+
+ public List<User> findUsersByQuery( UserQuery query )
+ {
+ log.debug( "NOT CACHED - .findUsersByQuery(UserQuery)" );
+ return this.userImpl.findUsersByQuery( query );
+ }
+
+ public List<User> findUsersByEmailKey( String emailKey, boolean orderAscending )
+ {
+ log.debug( "NOT CACHED - .findUsersByEmailKey(String, boolean)" );
+ return this.userImpl.findUsersByEmailKey( emailKey, orderAscending );
+ }
+
+ public List<User> findUsersByFullNameKey( String fullNameKey, boolean orderAscending )
+ {
+ log.debug( "NOT CACHED - .findUsersByFullNameKey(String, boolean)" );
+ return this.userImpl.findUsersByFullNameKey( fullNameKey, orderAscending );
+ }
+
+ public List<User> findUsersByUsernameKey( String usernameKey, boolean orderAscending )
+ {
+ log.debug( "NOT CACHED - .findUsersByUsernameKey(String, boolean)" );
+ return this.userImpl.findUsersByUsernameKey( usernameKey, orderAscending );
+ }
+
+ public String getId()
+ {
+ return "Cached User Manager [" + this.userImpl.getId() + "]";
+ }
+
+ public List<User> getUsers()
+ {
+ log.debug( "NOT CACHED - .getUsers()" );
+ return this.userImpl.getUsers();
+ }
+
+ public List<User> getUsers( boolean orderAscending )
+ {
+ log.debug( "NOT CACHED - .getUsers(boolean)" );
+ return this.userImpl.getUsers( orderAscending );
+ }
+
+ public void removeUserManagerListener( UserManagerListener listener )
+ {
+ this.userImpl.removeUserManagerListener( listener );
+ }
+
+ public User updateUser( User user )
+ throws UserNotFoundException
+ {
+ return updateUser( user, false );
+ }
+
+ public User updateUser( User user, boolean passwordChangeRequired )
+ throws UserNotFoundException
+ {
+ if ( user != null )
+ {
+ usersCache.remove( user.getPrincipal() );
+ }
+ return this.userImpl.updateUser( user, passwordChangeRequired );
+ }
+
+ public boolean userExists( Object principal )
+ {
+ if ( usersCache.hasKey( principal ) )
+ {
+ return true;
+ }
+
+ return this.userImpl.userExists( principal );
+ }
+
+ public void userManagerInit( boolean freshDatabase )
+ {
+ if ( userImpl instanceof UserManager )
+ {
+ ( (UserManagerListener) this.userImpl ).userManagerInit( freshDatabase );
+ }
+
+ usersCache.clear();
+ }
+
+ public void userManagerUserAdded( User user )
+ {
+ if ( userImpl instanceof UserManager )
+ {
+ ( (UserManagerListener) this.userImpl ).userManagerUserAdded( user );
+ }
+
+ if ( user != null )
+ {
+ usersCache.remove( user.getPrincipal() );
+ }
+ }
+
+ public void userManagerUserRemoved( User user )
+ {
+ if ( userImpl instanceof UserManager )
+ {
+ ( (UserManagerListener) this.userImpl ).userManagerUserRemoved( user );
+ }
+
+ if ( user != null )
+ {
+ usersCache.remove( user.getPrincipal() );
+ }
+ }
+
+ public void userManagerUserUpdated( User user )
+ {
+ if ( userImpl instanceof UserManager )
+ {
+ ( (UserManagerListener) this.userImpl ).userManagerUserUpdated( user );
+ }
+
+ if ( user != null )
+ {
+ usersCache.remove( user.getPrincipal() );
+ }
+ }
+
+ public UserManager getUserImpl()
+ {
+ return userImpl;
+ }
+
+ public void setUserImpl( UserManager userImpl )
+ {
+ this.userImpl = userImpl;
+ }
+
+ public Cache getUsersCache()
+ {
+ return usersCache;
+ }
+
+ public void setUsersCache( Cache usersCache )
+ {
+ this.usersCache = usersCache;
+ }
+}
diff --git a/redback-users/redback-users-providers/redback-users-cached/src/main/resources/META-INF/spring-context.xml b/redback-users/redback-users-providers/redback-users-cached/src/main/resources/META-INF/spring-context.xml
new file mode 100644
index 000000000..10e03f5e9
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-cached/src/main/resources/META-INF/spring-context.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0"?>
+
+<!--
+ ~ 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.
+ -->
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+ http://www.springframework.org/schema/context
+ http://www.springframework.org/schema/context/spring-context-3.0.xsd"
+ default-lazy-init="true">
+
+ <context:annotation-config />
+ <context:component-scan
+ base-package="org.codehaus.plexus.redback.users.cached"/>
+
+ <bean name="cache#users" class="org.codehaus.plexus.cache.ehcache.EhcacheCache"
+ init-method="initialize">
+ <property name="diskPersistent" value="false"/>
+ <property name="eternal" value="false"/>
+ <property name="maxElementsInMemory" value="1000"/>
+ <property name="memoryEvictionPolicy" value="LRU"/>
+ <property name="name" value="usersCache"/>
+ <property name="timeToIdleSeconds" value="1800"/>
+ <property name="timeToLiveSeconds" value="14400"/>
+ </bean>
+
+</beans> \ No newline at end of file
diff --git a/redback-users/redback-users-providers/redback-users-cached/src/test/java/org/codehaus/plexus/redback/users/cached/CachedUserManagerTest.java b/redback-users/redback-users-providers/redback-users-cached/src/test/java/org/codehaus/plexus/redback/users/cached/CachedUserManagerTest.java
new file mode 100644
index 000000000..ba1c6c142
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-cached/src/test/java/org/codehaus/plexus/redback/users/cached/CachedUserManagerTest.java
@@ -0,0 +1,67 @@
+package org.codehaus.plexus.redback.users.cached;
+
+/*
+ * Copyright 2001-2006 The Codehaus.
+ *
+ * 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.
+ */
+
+import net.sf.ehcache.CacheManager;
+
+import org.codehaus.plexus.redback.users.UserManager;
+import org.codehaus.plexus.redback.users.provider.test.AbstractUserManagerTestCase;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * CachedUserManagerTest
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id$
+ */
+public class CachedUserManagerTest
+ extends AbstractUserManagerTestCase
+{
+
+ @Inject @Named(value = "userManager#cached")
+ UserManager userManager;
+
+
+ @Before
+ public void setUp()
+ throws Exception
+ {
+ super.setUp();
+
+ setUserManager( userManager );
+
+ assertTrue( getUserManager() instanceof CachedUserManager );
+ }
+
+ @After
+ public void tearDown()
+ throws Exception
+ {
+ super.tearDown();
+ }
+
+ @AfterClass
+ public static void cleanCache()
+ {
+ CacheManager.getInstance().removalAll();
+ }
+}
diff --git a/redback-users/redback-users-providers/redback-users-cached/src/test/resources/org/codehaus/plexus/redback/config-defaults.properties b/redback-users/redback-users-providers/redback-users-cached/src/test/resources/org/codehaus/plexus/redback/config-defaults.properties
new file mode 100644
index 000000000..d0dd8d724
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-cached/src/test/resources/org/codehaus/plexus/redback/config-defaults.properties
@@ -0,0 +1,127 @@
+#
+# Copyright 2006 The Codehaus.
+#
+# 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.
+#
+
+# --------------------------------------------------------------------
+# Application Configuration
+
+application.timestamp=EEE d MMM yyyy HH:mm:ss Z
+
+# --------------------------------------------------------------------
+# JDBC Setup
+
+jdbc.driver.name=org.hsqldb.jdbcDriver
+jdbc.url=jdbc:hsqldb:mem:redback-test
+jdbc.username=sa
+jdbc.password=
+
+# --------------------------------------------------------------------
+# Email Settings
+
+email.jndiSessionName=java:comp/env/mail/Session
+email.smtp.host=localhost
+email.smtp.port=25
+email.smtp.ssl.enabled=false
+email.smtp.tls.enabled=false
+email.smtp.username=
+email.smtp.password=
+
+#TODO: move description elsewhere, remove bad default
+# All emails sent by the system will be from the following address
+#email.from.address=${user.name}@localhost
+# All emails sent by the system will be from the following user name (used in conjunction with address)
+#email.from.name=Unconfigured Username
+
+# If all email addresses (from new user registration) require an account validation email.
+email.validation.required=true
+# Timeout (in minutes) for the key generated for an email validation to remain valid.
+# 2880 minutes = 48 hours
+email.validation.timeout=2880
+# The subject line for the email message.
+email.validation.subject=Welcome
+
+#TODO: move description elsewhere, remove bad default
+# Get the Feedback to use for any outgoing emails.
+# NOTE: if feedback.path starts with a "/" it is appended to the end of the value provided in application.url
+# This value can be in the format/syntax of "/feedback.action" or even "mailto:feedback@application.com"
+#email.feedback.path=/feedback.action
+
+#Set the application base URL. The default is to derive it from the HTTP request
+#application.url=http://myurl.mycompany.com
+
+# --------------------------------------------------------------------
+# Auto Login Settings
+
+security.rememberme.enabled=true
+# Timeout in minutes ( 525600 minutes = 1 year )
+security.rememberme.timeout=525600
+
+# Single Sign On
+# Timeout in minutes
+security.signon.timeout=30
+
+# --------------------------------------------------------------------
+# Default Username Values
+redback.default.admin=admin
+
+# --------------------------------------------------------------------
+# Security Policies
+
+#security.policy.password.encoder=
+security.policy.password.previous.count=6
+security.policy.password.expiration.enabled=true
+security.policy.password.expiration.days=90
+security.policy.password.expiration.notify.days=10
+security.policy.allowed.login.attempt=10
+
+# turn off the perclick enforcement of various security policies, slightly
+# more heavyweight since it will ensure that the User object on each click
+# is up to date
+security.policy.strict.enforcement.enabled=true
+security.policy.strict.force.password.change.enabled=true
+
+# --------------------------------------------------------------------
+# Password Rules
+security.policy.password.rule.alphanumeric.enabled=false
+security.policy.password.rule.alphacount.enabled=true
+security.policy.password.rule.alphacount.minimum=1
+security.policy.password.rule.characterlength.enabled=true
+security.policy.password.rule.characterlength.minimum=1
+security.policy.password.rule.characterlength.maximum=24
+security.policy.password.rule.musthave.enabled=true
+security.policy.password.rule.numericalcount.enabled=true
+security.policy.password.rule.numericalcount.minimum=1
+security.policy.password.rule.reuse.enabled=true
+security.policy.password.rule.nowhitespace.enabled=true
+
+# --------------------------------------------------------------------
+# ldap settings
+#
+ldap.bind.authenticator.enabled=false
+
+# ldap options for configuration via properties file
+#ldap.config.hostname=
+#ldap.config.port=
+#ldap.config.base.dn=
+#ldap.config.context.factory=
+#ldap.config.bind.dn=
+#ldap.config.password=
+#ldap.config.authentication.method=
+
+# config parameter for the ConfigurableUserManager
+user.manager.impl=cached
+
+
+
diff --git a/redback-users/redback-users-providers/redback-users-cached/src/test/resources/spring-context.xml b/redback-users/redback-users-providers/redback-users-cached/src/test/resources/spring-context.xml
new file mode 100644
index 000000000..1168e2c56
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-cached/src/test/resources/spring-context.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0"?>
+
+<!--
+ ~ 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.
+ -->
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+ http://www.springframework.org/schema/context
+ http://www.springframework.org/schema/context/spring-context-3.0.xsd"
+ default-lazy-init="true">
+
+ <bean name="userManager#cached" class="org.codehaus.plexus.redback.users.cached.CachedUserManager">
+ <property name="userImpl" ref="userManager#memory"/>
+ <property name="usersCache" ref="cache#users"/>
+ </bean>
+
+ <bean name="cache#users" class="org.codehaus.plexus.cache.ehcache.EhcacheCache"
+ init-method="initialize">
+ <property name="diskPersistent" value="false"/>
+ <property name="eternal" value="false"/>
+ <property name="maxElementsInMemory" value="1000"/>
+ <property name="memoryEvictionPolicy" value="LRU"/>
+ <property name="name" value="usersCache"/>
+ <property name="timeToIdleSeconds" value="1800"/>
+ <property name="timeToLiveSeconds" value="14400"/>
+ </bean>
+
+
+</beans> \ No newline at end of file
diff --git a/redback-users/redback-users-providers/redback-users-configurable/pom.xml b/redback-users/redback-users-providers/redback-users-configurable/pom.xml
new file mode 100644
index 000000000..56d2a1c55
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-configurable/pom.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2006 The Codehaus.
+ ~
+ ~ 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.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.codehaus.redback</groupId>
+ <artifactId>redback-users-providers</artifactId>
+ <version>1.5-SNAPSHOT</version>
+ </parent>
+ <artifactId>redback-users-configurable</artifactId>
+ <name>Redback :: Users Provider :: Configurable</name>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.codehaus.redback</groupId>
+ <artifactId>redback-policy</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-context-support</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>javax.annotation</groupId>
+ <artifactId>jsr250-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.redback</groupId>
+ <artifactId>redback-users-tests</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/redback-users/redback-users-providers/redback-users-configurable/src/main/java/org/codehaus/plexus/redback/users/configurable/ConfigurableUserManager.java b/redback-users/redback-users-providers/redback-users-configurable/src/main/java/org/codehaus/plexus/redback/users/configurable/ConfigurableUserManager.java
new file mode 100644
index 000000000..f0d7e3331
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-configurable/src/main/java/org/codehaus/plexus/redback/users/configurable/ConfigurableUserManager.java
@@ -0,0 +1,179 @@
+package org.codehaus.plexus.redback.users.configurable;
+
+/*
+ * Copyright 2001-2007 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.
+ */
+
+import org.codehaus.plexus.redback.configuration.UserConfiguration;
+import org.codehaus.plexus.redback.users.AbstractUserManager;
+import org.codehaus.plexus.redback.users.User;
+import org.codehaus.plexus.redback.users.UserManager;
+import org.codehaus.plexus.redback.users.UserNotFoundException;
+import org.codehaus.plexus.redback.users.UserQuery;
+import org.springframework.context.ApplicationContext;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import javax.inject.Inject;
+import javax.inject.Named;
+import java.util.List;
+
+/**
+ * @author <a href="jesse@codehaus.org"> jesse
+ * @version "Id$
+ */
+@Service( "userManager#configurable" )
+public class ConfigurableUserManager
+ extends AbstractUserManager
+{
+ @Inject
+ @Named( value = "userConfiguration" )
+ private UserConfiguration config;
+
+ @Inject
+ private ApplicationContext applicationContext;
+
+ private UserManager userManagerImpl;
+
+ public static final String USER_MANAGER_IMPL = "user.manager.impl";
+
+ @PostConstruct
+ public void initialize()
+ {
+ String userManagerRole = config.getString( USER_MANAGER_IMPL );
+
+ if ( userManagerRole == null )
+ {
+ throw new RuntimeException(
+ "User Manager Configuration Missing: " + USER_MANAGER_IMPL + " configuration property" );
+ }
+
+ userManagerImpl = applicationContext.getBean( "userManager#" + userManagerRole, UserManager.class );
+ // (UserManager) container.lookup( UserManager.class.getName(), userManagerRole );
+ }
+
+ public User addUser( User user )
+ {
+ return userManagerImpl.addUser( user );
+ }
+
+ public void addUserUnchecked( User user )
+ {
+ userManagerImpl.addUserUnchecked( user );
+ }
+
+ public User createUser( String username, String fullName, String emailAddress )
+ {
+ return userManagerImpl.createUser( username, fullName, emailAddress );
+ }
+
+ public UserQuery createUserQuery()
+ {
+ return userManagerImpl.createUserQuery();
+ }
+
+ public void deleteUser( Object principal )
+ throws UserNotFoundException
+ {
+ userManagerImpl.deleteUser( principal );
+ }
+
+ public void deleteUser( String username )
+ throws UserNotFoundException
+ {
+ userManagerImpl.deleteUser( username );
+ }
+
+ public void eraseDatabase()
+ {
+ userManagerImpl.eraseDatabase();
+ }
+
+ public User findUser( String username )
+ throws UserNotFoundException
+ {
+ return userManagerImpl.findUser( username );
+ }
+
+ public User findUser( Object principal )
+ throws UserNotFoundException
+ {
+ return userManagerImpl.findUser( principal );
+ }
+
+ @Override
+ public User getGuestUser()
+ throws UserNotFoundException
+ {
+ return userManagerImpl.getGuestUser();
+ }
+
+ public List<User> findUsersByEmailKey( String emailKey, boolean orderAscending )
+ {
+ return userManagerImpl.findUsersByEmailKey( emailKey, orderAscending );
+ }
+
+ public List<User> findUsersByFullNameKey( String fullNameKey, boolean orderAscending )
+ {
+ return userManagerImpl.findUsersByFullNameKey( fullNameKey, orderAscending );
+ }
+
+ public List<User> findUsersByQuery( UserQuery query )
+ {
+ return userManagerImpl.findUsersByQuery( query );
+ }
+
+ public List<User> findUsersByUsernameKey( String usernameKey, boolean orderAscending )
+ {
+ return userManagerImpl.findUsersByUsernameKey( usernameKey, orderAscending );
+ }
+
+ public String getId()
+ {
+ return ConfigurableUserManager.class.getName() + " wrapping " + userManagerImpl.getId();
+ }
+
+ public List<User> getUsers()
+ {
+ return userManagerImpl.getUsers();
+ }
+
+ public List<User> getUsers( boolean orderAscending )
+ {
+ return userManagerImpl.getUsers( orderAscending );
+ }
+
+ public boolean isReadOnly()
+ {
+ return userManagerImpl.isReadOnly();
+ }
+
+ public User updateUser( User user )
+ throws UserNotFoundException
+ {
+ return updateUser( user, false );
+ }
+
+ public User updateUser( User user, boolean passwordChangeRequired )
+ throws UserNotFoundException
+ {
+ return userManagerImpl.updateUser( user, passwordChangeRequired );
+ }
+
+ public boolean userExists( Object principal )
+ {
+ return userManagerImpl.userExists( principal );
+ }
+}
diff --git a/redback-users/redback-users-providers/redback-users-configurable/src/main/resources/META-INF/spring-context.xml b/redback-users/redback-users-providers/redback-users-configurable/src/main/resources/META-INF/spring-context.xml
new file mode 100644
index 000000000..6b39e503b
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-configurable/src/main/resources/META-INF/spring-context.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+
+<!--
+ ~ 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.
+ -->
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+ http://www.springframework.org/schema/context
+ http://www.springframework.org/schema/context/spring-context-3.0.xsd"
+ default-lazy-init="true">
+
+ <context:annotation-config />
+ <context:component-scan base-package="org.codehaus.plexus.redback.users.configurable"/>
+
+</beans> \ No newline at end of file
diff --git a/redback-users/redback-users-providers/redback-users-jdo/pom.xml b/redback-users/redback-users-providers/redback-users-jdo/pom.xml
new file mode 100644
index 000000000..c9042c63d
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-jdo/pom.xml
@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2006 The Codehaus.
+ ~
+ ~ 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.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.codehaus.redback</groupId>
+ <artifactId>redback-users-providers</artifactId>
+ <version>1.5-SNAPSHOT</version>
+ </parent>
+ <artifactId>redback-users-jdo</artifactId>
+ <name>Redback :: Users Provider :: JDO</name>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.codehaus.plexus</groupId>
+ <artifactId>plexus-utils</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.redback</groupId>
+ <artifactId>redback-common-jdo</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.redback</groupId>
+ <artifactId>redback-policy</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-context-support</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>javax.annotation</groupId>
+ <artifactId>jsr250-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.redback</groupId>
+ <artifactId>redback-users-tests</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.hsqldb</groupId>
+ <artifactId>hsqldb</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.java.dev.stax-utils</groupId>
+ <artifactId>stax-utils</artifactId>
+ <version>20060502</version>
+ <exclusions>
+ <exclusion>
+ <groupId>com.bea.xml</groupId>
+ <artifactId>jsr173-ri</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>javax.xml.stream</groupId>
+ <artifactId>stax-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.woodstox</groupId>
+ <artifactId>wstx-asl</artifactId>
+ <version>3.2.0</version>
+ <scope>test</scope>
+ <exclusions>
+ <exclusion>
+ <groupId>stax</groupId>
+ <artifactId>stax-api</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.derby</groupId>
+ <artifactId>derby</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.modello</groupId>
+ <artifactId>modello-maven-plugin</artifactId>
+ <version>1.0-alpha-15</version>
+ <configuration>
+ <version>1.0.1</version>
+ <packageWithVersion>false</packageWithVersion>
+ <model>src/main/mdo/user.xml</model>
+ </configuration>
+ <executions>
+ <execution>
+ <id>modello-java</id>
+ <goals>
+ <goal>java</goal>
+ <goal>jpox-metadata-class</goal>
+ <goal>stax-reader</goal>
+ <goal>stax-writer</goal>
+ </goals>
+ </execution>
+ <execution>
+ <id>jpox-jdo-mapping</id>
+ <goals>
+ <goal>jpox-jdo-mapping</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${basedir}/target/classes/org/codehaus/plexus/redback/users/jdo</outputDirectory>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>jpox-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>enhance</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/redback-users/redback-users-providers/redback-users-jdo/src/main/java/org/codehaus/plexus/redback/users/jdo/JdoUserManager.java b/redback-users/redback-users-providers/redback-users-jdo/src/main/java/org/codehaus/plexus/redback/users/jdo/JdoUserManager.java
new file mode 100644
index 000000000..c04ef0c65
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-jdo/src/main/java/org/codehaus/plexus/redback/users/jdo/JdoUserManager.java
@@ -0,0 +1,487 @@
+package org.codehaus.plexus.redback.users.jdo;
+
+/*
+ * Copyright 2001-2006 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.
+ */
+
+import org.codehaus.plexus.jdo.JdoFactory;
+import org.codehaus.plexus.jdo.PlexusJdoUtils;
+import org.codehaus.plexus.jdo.PlexusObjectNotFoundException;
+import org.codehaus.plexus.jdo.PlexusStoreException;
+import org.codehaus.plexus.redback.policy.UserSecurityPolicy;
+import org.codehaus.plexus.redback.users.AbstractUserManager;
+import org.codehaus.plexus.redback.users.PermanentUserException;
+import org.codehaus.plexus.redback.users.User;
+import org.codehaus.plexus.redback.users.UserManagerException;
+import org.codehaus.plexus.redback.users.UserNotFoundException;
+import org.codehaus.plexus.redback.users.UserQuery;
+import org.codehaus.plexus.util.StringUtils;
+import org.jpox.JDOClassLoaderResolver;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.jdo.Extent;
+import javax.jdo.PersistenceManager;
+import javax.jdo.PersistenceManagerFactory;
+import javax.jdo.Query;
+import javax.jdo.Transaction;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * JdoUserManager
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id$
+ */
+@Service("userManager#jdo")
+public class JdoUserManager
+ extends AbstractUserManager
+{
+ @Inject @Named(value="jdoFactory#users")
+ private JdoFactory jdoFactory;
+
+ @Inject
+ private UserSecurityPolicy userSecurityPolicy;
+
+ private PersistenceManagerFactory pmf;
+
+ public String getId()
+ {
+ return "JDO UserManager - " + this.getClass().getName();
+ }
+
+
+ public boolean isReadOnly()
+ {
+ return false;
+ }
+
+ public UserQuery createUserQuery()
+ {
+ return new JdoUserQuery();
+ }
+
+ // ------------------------------------------------------------------
+
+ public User createUser( String username, String fullname, String email )
+ {
+ User user = new JdoUser();
+ user.setUsername( username );
+ user.setFullName( fullname );
+ user.setEmail( email );
+ user.setAccountCreationDate( new Date() );
+
+ return user;
+ }
+
+ public List<User> getUsers()
+ {
+ return getAllObjectsDetached( null );
+ }
+
+ public List<User> getUsers( boolean orderAscending )
+ {
+ String ordering = orderAscending ? "username ascending" : "username descending";
+
+ return getAllObjectsDetached( ordering );
+ }
+
+ @SuppressWarnings("unchecked")
+ private List<User> getAllObjectsDetached( String ordering )
+ {
+ return PlexusJdoUtils.getAllObjectsDetached( getPersistenceManager(), JdoUser.class, ordering, (String) null );
+ }
+
+ public List<User> findUsersByUsernameKey( String usernameKey, boolean orderAscending )
+ {
+ return findUsers( "username", usernameKey, orderAscending );
+ }
+
+ public List<User> findUsersByFullNameKey( String fullNameKey, boolean orderAscending )
+ {
+ return findUsers( "fullName", fullNameKey, orderAscending );
+ }
+
+ public List<User> findUsersByEmailKey( String emailKey, boolean orderAscending )
+ {
+ return findUsers( "email", emailKey, orderAscending );
+ }
+
+ @SuppressWarnings("unchecked")
+ public List<User> findUsersByQuery( UserQuery userQuery )
+ {
+ JdoUserQuery uq = (JdoUserQuery) userQuery;
+
+ PersistenceManager pm = getPersistenceManager();
+
+ Transaction tx = pm.currentTransaction();
+
+ try
+ {
+ tx.begin();
+
+ Extent extent = pm.getExtent( JdoUser.class, true );
+
+ Query query = pm.newQuery( extent );
+
+ String ordering = uq.getOrdering();
+
+ query.setOrdering( ordering );
+
+ query.declareImports( "import java.lang.String" );
+
+ query.declareParameters( uq.getParameters() );
+
+ query.setFilter( uq.getFilter() );
+
+ query.setRange( uq.getFirstResult(),
+ uq.getMaxResults() < 0 ? Long.MAX_VALUE : uq.getFirstResult() + uq.getMaxResults() );
+
+ List<User> result = (List<User>) query.executeWithArray( uq.getSearchKeys() );
+
+ result = (List<User>) pm.detachCopyAll( result );
+
+ tx.commit();
+
+ return result;
+ }
+ finally
+ {
+ rollback( tx );
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private List<User> findUsers( String searchField, String searchKey, boolean ascendingUsername )
+ {
+ PersistenceManager pm = getPersistenceManager();
+
+ Transaction tx = pm.currentTransaction();
+
+ try
+ {
+ tx.begin();
+
+ Extent extent = pm.getExtent( JdoUser.class, true );
+
+ Query query = pm.newQuery( extent );
+
+ String ordering = ascendingUsername ? "username ascending" : "username descending";
+
+ query.setOrdering( ordering );
+
+ query.declareImports( "import java.lang.String" );
+
+ query.declareParameters( "String searchKey" );
+
+ query.setFilter( "this." + searchField + ".toLowerCase().indexOf(searchKey.toLowerCase()) > -1" );
+
+ List<User> result = (List<User>) query.execute( searchKey );
+
+ result = (List<User>) pm.detachCopyAll( result );
+
+ tx.commit();
+
+ return result;
+ }
+ finally
+ {
+ rollback( tx );
+ }
+ }
+
+ public User addUser( User user )
+ {
+ if ( !( user instanceof JdoUser ) )
+ {
+ throw new UserManagerException( "Unable to Add User. User object " + user.getClass().getName() +
+ " is not an instance of " + JdoUser.class.getName() );
+ }
+
+ if ( StringUtils.isEmpty( user.getUsername() ) )
+ {
+ throw new IllegalStateException(
+ Messages.getString( "user.manager.cannot.add.user.without.username" ) ); //$NON-NLS-1$
+ }
+
+ userSecurityPolicy.extensionChangePassword( user );
+
+ fireUserManagerUserAdded( user );
+
+ // TODO: find a better solution
+ // workaround for avoiding the admin from providing another password on the next login after the
+ // admin account has been created
+ // extensionChangePassword by default sets the password change status to false
+ if ( "admin".equals( user.getUsername() ) )
+ {
+ user.setPasswordChangeRequired( false );
+ }
+ else
+ {
+ user.setPasswordChangeRequired( true );
+ }
+
+ return (User) addObject( user );
+ }
+
+ public void deleteUser( Object principal )
+ {
+ try
+ {
+ User user = findUser( principal );
+
+ if ( user.isPermanent() )
+ {
+ throw new PermanentUserException( "Cannot delete permanent user [" + user.getUsername() + "]." );
+ }
+
+ fireUserManagerUserRemoved( user );
+
+ removeObject( user );
+ }
+ catch ( UserNotFoundException e )
+ {
+ log.warn( "Unable to delete user " + principal + ", user not found.", e );
+ }
+ }
+
+ public void deleteUser( String username )
+ {
+ try
+ {
+ User user = findUser( username );
+
+ if ( user.isPermanent() )
+ {
+ throw new PermanentUserException( "Cannot delete permanent user [" + user.getUsername() + "]." );
+ }
+
+ fireUserManagerUserRemoved( user );
+
+ PlexusJdoUtils.removeObject( getPersistenceManager(), user );
+ }
+ catch ( UserNotFoundException e )
+ {
+ log.warn( "Unable to delete user " + username + ", user not found.", e );
+ }
+ }
+
+ public void addUserUnchecked( User user )
+ {
+ if ( !( user instanceof JdoUser ) )
+ {
+ throw new UserManagerException( "Unable to Add User. User object " + user.getClass().getName() +
+ " is not an instance of " + JdoUser.class.getName() );
+ }
+
+ if ( StringUtils.isEmpty( user.getUsername() ) )
+ {
+ throw new IllegalStateException(
+ Messages.getString( "user.manager.cannot.add.user.without.username" ) ); //$NON-NLS-1$
+ }
+
+ addObject( user );
+ }
+
+ public void eraseDatabase()
+ {
+ PlexusJdoUtils.removeAll( getPersistenceManager(), JdoUser.class );
+ PlexusJdoUtils.removeAll( getPersistenceManager(), UsersManagementModelloMetadata.class );
+ }
+
+ public User findUser( Object principal )
+ throws UserNotFoundException
+ {
+ if ( principal == null )
+ {
+ throw new UserNotFoundException( "Unable to find user based on null principal." );
+ }
+
+ try
+ {
+ return (User) PlexusJdoUtils.getObjectById( getPersistenceManager(), JdoUser.class, principal.toString(),
+ null );
+ }
+ catch ( PlexusObjectNotFoundException e )
+ {
+ throw new UserNotFoundException( "Unable to find user: " + e.getMessage(), e );
+ }
+ catch ( PlexusStoreException e )
+ {
+ throw new UserNotFoundException( "Unable to find user: " + e.getMessage(), e );
+ }
+ }
+
+ public User findUser( String username )
+ throws UserNotFoundException
+ {
+ if ( StringUtils.isEmpty( username ) )
+ {
+ throw new UserNotFoundException( "User with empty username not found." );
+ }
+
+ return (User) getObjectById( username, null );
+ }
+
+ public boolean userExists( Object principal )
+ {
+ try
+ {
+ findUser( principal );
+ return true;
+ }
+ catch ( UserNotFoundException ne )
+ {
+ return false;
+ }
+ }
+
+ public User updateUser( User user )
+ throws UserNotFoundException
+ {
+ return updateUser( user, false );
+ }
+
+ public User updateUser( User user, boolean passwordChangeRequired )
+ throws UserNotFoundException
+ {
+ if ( !( user instanceof JdoUser ) )
+ {
+ throw new UserManagerException( "Unable to Update User. User object " + user.getClass().getName() +
+ " is not an instance of " + JdoUser.class.getName() );
+ }
+
+ // If password is supplied, assume changing of password.
+ // TODO: Consider adding a boolean to the updateUser indicating a password change or not.
+ if ( StringUtils.isNotEmpty( user.getPassword() ) )
+ {
+ userSecurityPolicy.extensionChangePassword( user, passwordChangeRequired );
+ }
+
+ updateObject( user );
+
+ fireUserManagerUserUpdated( user );
+
+ return user;
+ }
+
+ @PostConstruct
+ public void initialize()
+ {
+ JDOClassLoaderResolver d;
+ pmf = jdoFactory.getPersistenceManagerFactory();
+ }
+
+ public PersistenceManager getPersistenceManager()
+ {
+ PersistenceManager pm = pmf.getPersistenceManager();
+
+ pm.getFetchPlan().setMaxFetchDepth( -1 );
+
+ triggerInit();
+
+ return pm;
+ }
+
+ // ----------------------------------------------------------------------
+ // jdo utility methods
+ // ----------------------------------------------------------------------
+
+ private Object addObject( Object object )
+ {
+ return PlexusJdoUtils.addObject( getPersistenceManager(), object );
+ }
+
+ private Object getObjectById( String id, String fetchGroup )
+ throws UserNotFoundException, UserManagerException
+ {
+ try
+ {
+ return PlexusJdoUtils.getObjectById( getPersistenceManager(), JdoUser.class, id, fetchGroup );
+ }
+ catch ( PlexusObjectNotFoundException e )
+ {
+ throw new UserNotFoundException( e.getMessage() );
+ }
+ catch ( PlexusStoreException e )
+ {
+ throw new UserManagerException( "Unable to get object '" + JdoUser.class.getName() + "', id '" + id +
+ "', fetch-group '" + fetchGroup + "' from jdo store." );
+ }
+ }
+
+ private Object removeObject( Object o )
+ {
+ if ( o == null )
+ {
+ throw new UserManagerException( "Unable to remove null object" );
+ }
+
+ PlexusJdoUtils.removeObject( getPersistenceManager(), o );
+ return o;
+ }
+
+ private Object updateObject( Object object )
+ throws UserNotFoundException, UserManagerException
+ {
+ try
+ {
+ return PlexusJdoUtils.updateObject( getPersistenceManager(), object );
+ }
+ catch ( PlexusStoreException e )
+ {
+ throw new UserManagerException(
+ "Unable to update the '" + object.getClass().getName() + "' object in the jdo database.", e );
+ }
+ }
+
+ private void rollback( Transaction tx )
+ {
+ PlexusJdoUtils.rollbackIfActive( tx );
+ }
+
+ private boolean hasTriggeredInit = false;
+
+ public void triggerInit()
+ {
+ if ( !hasTriggeredInit )
+ {
+ hasTriggeredInit = true;
+ List<User> users = getAllObjectsDetached( null );
+
+ fireUserManagerInit( users.isEmpty() );
+ }
+ }
+
+ public JdoFactory getJdoFactory()
+ {
+ return jdoFactory;
+ }
+
+ public void setJdoFactory( JdoFactory jdoFactory )
+ {
+ this.jdoFactory = jdoFactory;
+ }
+
+ public UserSecurityPolicy getUserSecurityPolicy()
+ {
+ return userSecurityPolicy;
+ }
+}
diff --git a/redback-users/redback-users-providers/redback-users-jdo/src/main/java/org/codehaus/plexus/redback/users/jdo/JdoUserQuery.java b/redback-users/redback-users-providers/redback-users-jdo/src/main/java/org/codehaus/plexus/redback/users/jdo/JdoUserQuery.java
new file mode 100644
index 000000000..1ceb0ba14
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-jdo/src/main/java/org/codehaus/plexus/redback/users/jdo/JdoUserQuery.java
@@ -0,0 +1,133 @@
+package org.codehaus.plexus.redback.users.jdo;
+
+/*
+ * Copyright 2001-2006 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.
+ */
+
+import org.codehaus.plexus.redback.users.AbstractUserQuery;
+import org.codehaus.plexus.redback.users.UserQuery;
+import org.codehaus.plexus.util.StringUtils;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+
+public class JdoUserQuery
+ extends AbstractUserQuery
+{
+
+ /**
+ * Create the ordering string for use in {@link javax.jdo.Query#setOrdering(String)}
+ *
+ * @return the created filter
+ */
+ public String getOrdering()
+ {
+ StringBuffer ordering = new StringBuffer();
+
+ if ( UserQuery.ORDER_BY_EMAIL.equals( getOrderBy() ) )
+ {
+ ordering.append( "email" );
+ }
+ else if ( UserQuery.ORDER_BY_FULLNAME.equals( getOrderBy() ) )
+ {
+ ordering.append( "fullName" );
+ }
+ else
+ {
+ ordering.append( "username" );
+ }
+ ordering.append( " " ).append( isAscending() ? "ascending" : "descending" );
+ return ordering.toString();
+ }
+
+ /**
+ * Create and return the filter string for use in {@link javax.jdo.Query#setFilter(String)}
+ *
+ * @return the query filter
+ */
+ public String getFilter()
+ {
+ Set<String> terms = new HashSet<String>();
+
+ if ( getUsername() != null )
+ {
+ terms.add( "this.username.toLowerCase().indexOf(usernameKey.toLowerCase()) > -1" );
+ }
+ if ( getFullName() != null )
+ {
+ terms.add( "this.fullName.toLowerCase().indexOf(fullNameKey.toLowerCase()) > -1" );
+ }
+ if ( getEmail() != null )
+ {
+ terms.add( "this.email.toLowerCase().indexOf(emailKey.toLowerCase()) > -1" );
+ }
+
+ return StringUtils.join( terms.iterator(), " && " );
+ }
+
+ /**
+ * Return an array of parameters for user in {@link javax.jdo.Query#executeWithArray(Object[])}
+ *
+ * @return the parameter array
+ */
+ public String[] getSearchKeys()
+ {
+ List<String> keys = new ArrayList<String>();
+
+ if ( getUsername() != null )
+ {
+ keys.add( getUsername() );
+ }
+ if ( getFullName() != null )
+ {
+ keys.add( getFullName() );
+ }
+ if ( getEmail() != null )
+ {
+ keys.add( getEmail() );
+ }
+
+ return (String[]) keys.toArray( new String[0] );
+ }
+
+ /**
+ * Returns the parameters for use in {@link javax.jdo.Query#declareParameters(String)}
+ *
+ * @return the parameter list
+ */
+ public String getParameters()
+ {
+
+ List<String> params = new ArrayList<String>();
+
+ if ( getUsername() != null )
+ {
+ params.add( "String usernameKey" );
+ }
+ if ( getFullName() != null )
+ {
+ params.add( "String fullNameKey" );
+ }
+ if ( getEmail() != null )
+ {
+ params.add( "String emailKey" );
+ }
+
+ return StringUtils.join( params.iterator(), ", " );
+ }
+}
diff --git a/redback-users/redback-users-providers/redback-users-jdo/src/main/java/org/codehaus/plexus/redback/users/jdo/Messages.java b/redback-users/redback-users-providers/redback-users-jdo/src/main/java/org/codehaus/plexus/redback/users/jdo/Messages.java
new file mode 100644
index 000000000..e9882574e
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-jdo/src/main/java/org/codehaus/plexus/redback/users/jdo/Messages.java
@@ -0,0 +1,91 @@
+package org.codehaus.plexus.redback.users.jdo;
+
+/*
+ * Copyright 2001-2006 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.
+ */
+
+import java.text.MessageFormat;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+/**
+ * Localized Message Handling.
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id$
+ */
+public class Messages
+{
+ private static final String BUNDLE_NAME = "org.codehaus.plexus.redback.users.jdo"; //$NON-NLS-1$
+
+ private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle.getBundle( BUNDLE_NAME );
+
+ /**
+ * Get a Message as-is from the Resource Bundle.
+ *
+ * @param key the key for the message to get.
+ * @return the value of the key, or "!key!" if the key is not found.
+ */
+ public static String getString( String key )
+ {
+ try
+ {
+ return RESOURCE_BUNDLE.getString( key );
+ }
+ catch ( MissingResourceException e )
+ {
+ return '!' + key + '!';
+ }
+ }
+
+ /**
+ * Gets a Message from the Resource Bundle, with {1} and {2} style arguments.
+ *
+ * @param key the key for the message to get.
+ * @param arg the argument to pass in.
+ * @return the value of the key, or "!key!" if the key is not found.
+ */
+ public static String getString( String key, Object arg )
+ {
+ return getString( key, new Object[] { arg } );
+ }
+
+ /**
+ * Gets a Message from the Resource Bundle, with {1} and {2} style arguments.
+ *
+ * @param key the key for the message to get.
+ * @param args the arguments to pass in.
+ * @return the value of the key, or "!key!" if the key is not found.
+ */
+ public static String getString( String key, Object args[] )
+ {
+ try
+ {
+ String pattern = RESOURCE_BUNDLE.getString( key );
+ return MessageFormat.format( pattern, args );
+ }
+ catch ( MissingResourceException e )
+ {
+ return '!' + key + '!';
+ }
+ }
+
+ /**
+ * Prevent Instantiation.
+ */
+ private Messages()
+ {
+ }
+}
diff --git a/redback-users/redback-users-providers/redback-users-jdo/src/main/mdo/user.xml b/redback-users/redback-users-providers/redback-users-jdo/src/main/mdo/user.xml
new file mode 100644
index 000000000..d72004481
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-jdo/src/main/mdo/user.xml
@@ -0,0 +1,143 @@
+<?xml version="1.0" ?>
+<model>
+ <id>redback-users-jdo</id>
+ <name>UsersManagement</name>
+ <version>1.0.1</version>
+ <description>Plexus Redback Users object model.</description>
+ <defaults>
+ <default>
+ <key>package</key>
+ <value>org.codehaus.plexus.redback.users.jdo</value>
+ </default>
+ </defaults>
+
+ <classes>
+ <class stash.storable="false" rootElement="true">
+ <name>UserDatabase</name>
+ <version>1.0.1+</version>
+ <fields>
+ <field>
+ <name>users</name>
+ <version>1.0.1+</version>
+ <association>
+ <type>JdoUser</type>
+ <multiplicity>*</multiplicity>
+ </association>
+ </field>
+ </fields>
+ </class>
+ <class stash.storable="true" jpox.use-identifiers-as-primary-key="false">
+ <name>JdoUser</name>
+ <version>1.0.0+</version>
+ <interfaces>
+ <interface>org.codehaus.plexus.redback.users.User</interface>
+ </interfaces>
+ <description></description>
+ <fields>
+ <field jpox.primary-key="true" jpox.value-strategy="off" jpox.persistence-modifier="persistent">
+ <name>username</name>
+ <version>1.0.0+</version>
+ <type>String</type>
+ <identifier>true</identifier>
+ </field>
+ <field jpox.column="USER_PASSWORD">
+ <name>password</name>
+ <version>1.0.0+</version>
+ <type>String</type>
+ <identifier>true</identifier>
+ </field>
+ <field>
+ <name>encodedPassword</name>
+ <version>1.0.0+</version>
+ <type>String</type>
+ <identifier>true</identifier>
+ </field>
+ <field>
+ <name>fullName</name>
+ <version>1.0.0+</version>
+ <type>String</type>
+ <identifier>true</identifier>
+ </field>
+ <field>
+ <name>email</name>
+ <version>1.0.0+</version>
+ <type>String</type>
+ <identifier>true</identifier>
+ </field>
+ <field>
+ <name>lastPasswordChange</name>
+ <version>1.0.0+</version>
+ <type>Date</type>
+ </field>
+ <field>
+ <name>lastLoginDate</name>
+ <version>1.0.0+</version>
+ <type>Date</type>
+ </field>
+ <field>
+ <name>countFailedLoginAttempts</name>
+ <version>1.0.0+</version>
+ <type>int</type>
+ </field>
+ <field>
+ <name>locked</name>
+ <version>1.0.0+</version>
+ <type>boolean</type>
+ <defaultValue>false</defaultValue>
+ <identifier>true</identifier>
+ </field>
+ <field>
+ <name>permanent</name>
+ <version>1.0.0+</version>
+ <type>boolean</type>
+ <defaultValue>false</defaultValue>
+ <identifier>true</identifier>
+ </field>
+ <field>
+ <name>validated</name>
+ <version>1.0.0+</version>
+ <type>boolean</type>
+ <defaultValue>false</defaultValue>
+ <identifier>true</identifier>
+ </field>
+ <field>
+ <name>passwordChangeRequired</name>
+ <version>1.0.0+</version>
+ <type>boolean</type>
+ <defaultValue>false</defaultValue>
+ <identifier>true</identifier>
+ </field>
+ <field>
+ <name>previousEncodedPasswords</name>
+ <version>1.0.0+</version>
+ <association stash.part="true">
+ <type>String</type>
+ <multiplicity>*</multiplicity>
+ </association>
+ </field>
+ <field>
+ <name>accountCreationDate</name>
+ <version>1.0.0+</version>
+ <type>Date</type>
+ </field>
+ </fields>
+ <codeSegments>
+ <codeSegment>
+ <version>1.0.0+</version>
+ <code><![CDATA[
+ public JdoUser()
+ {
+ // Intentionally initialize List to avoid JPOX NullPointerException Issues.
+ previousEncodedPasswords = new java.util.ArrayList();
+ }
+
+ public Object getPrincipal()
+ {
+ return username;
+ }
+ ]]></code>
+ </codeSegment>
+ </codeSegments>
+ </class>
+ </classes>
+</model>
diff --git a/redback-users/redback-users-providers/redback-users-jdo/src/main/resources/META-INF/spring-context.xml b/redback-users/redback-users-providers/redback-users-jdo/src/main/resources/META-INF/spring-context.xml
new file mode 100644
index 000000000..cc2328dfe
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-jdo/src/main/resources/META-INF/spring-context.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+
+<!--
+ ~ 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.
+ -->
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+ http://www.springframework.org/schema/context
+ http://www.springframework.org/schema/context/spring-context-3.0.xsd"
+ default-lazy-init="true">
+
+ <context:annotation-config />
+ <context:component-scan base-package="org.codehaus.plexus.redback.users.jdo"/>
+
+</beans> \ No newline at end of file
diff --git a/redback-users/redback-users-providers/redback-users-jdo/src/main/resources/org/codehaus/plexus/redback/users/jdo/messages.properties b/redback-users/redback-users-providers/redback-users-jdo/src/main/resources/org/codehaus/plexus/redback/users/jdo/messages.properties
new file mode 100644
index 000000000..df9bad860
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-jdo/src/main/resources/org/codehaus/plexus/redback/users/jdo/messages.properties
@@ -0,0 +1,2 @@
+user.manager.cannot.add.user.without.username=User.username must be supplied on an .addUser() request.
+user.manager.cannot.add.user.without.password=User.password must be supplied on an .addUser() request.
diff --git a/redback-users/redback-users-providers/redback-users-jdo/src/test/java/org/codehaus/plexus/redback/users/jdo/JdoUserManagerTest.java b/redback-users/redback-users-providers/redback-users-jdo/src/test/java/org/codehaus/plexus/redback/users/jdo/JdoUserManagerTest.java
new file mode 100644
index 000000000..d23ed7822
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-jdo/src/test/java/org/codehaus/plexus/redback/users/jdo/JdoUserManagerTest.java
@@ -0,0 +1,120 @@
+package org.codehaus.plexus.redback.users.jdo;
+
+/*
+ * Copyright 2001-2006 The Codehaus.
+ *
+ * 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.
+ */
+
+import org.codehaus.plexus.jdo.DefaultConfigurableJdoFactory;
+import org.codehaus.plexus.redback.common.jdo.test.StoreManagerDebug;
+import org.codehaus.plexus.redback.users.UserManager;
+import org.codehaus.plexus.redback.users.UserNotFoundException;
+import org.codehaus.plexus.redback.users.provider.test.AbstractUserManagerTestCase;
+import org.jpox.AbstractPersistenceManagerFactory;
+import org.jpox.SchemaTool;
+import org.junit.Before;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.jdo.PersistenceManager;
+import javax.jdo.PersistenceManagerFactory;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * JdoUserManagerTest
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id$
+ */
+public class JdoUserManagerTest
+ extends AbstractUserManagerTestCase
+{
+ @Inject
+ @Named( value = "jdoFactory#users" )
+ DefaultConfigurableJdoFactory jdoFactory;
+
+ @Inject
+ @Named( value = "userManager#jdo" )
+ JdoUserManager jdoUserManager;
+
+ private StoreManagerDebug storeManager;
+
+ @Before
+ public void setUp()
+ throws Exception
+ {
+ super.setUp();
+
+ jdoFactory.setPersistenceManagerFactoryClass( "org.jpox.PersistenceManagerFactoryImpl" ); //$NON-NLS-1$
+
+ jdoFactory.setDriverName( "org.hsqldb.jdbcDriver" ); //$NON-NLS-1$
+
+ jdoFactory.setUrl( "jdbc:hsqldb:mem:" + getName() ); //$NON-NLS-1$
+
+ jdoFactory.setUserName( "sa" ); //$NON-NLS-1$
+
+ jdoFactory.setPassword( "" ); //$NON-NLS-1$
+
+ jdoFactory.setProperty( "org.jpox.transactionIsolation", "READ_COMMITTED" ); //$NON-NLS-1$ //$NON-NLS-2$
+
+ jdoFactory.setProperty( "org.jpox.poid.transactionIsolation", "READ_COMMITTED" ); //$NON-NLS-1$ //$NON-NLS-2$
+
+ jdoFactory.setProperty( "org.jpox.autoCreateSchema", "true" ); //$NON-NLS-1$ //$NON-NLS-2$
+
+ Properties properties = jdoFactory.getProperties();
+
+ for ( Map.Entry<?, ?> entry : properties.entrySet() )
+ {
+ System.setProperty( (String) entry.getKey(), (String) entry.getValue() );
+ }
+
+ PersistenceManagerFactory pmf = jdoFactory.getPersistenceManagerFactory();
+
+ assertNotNull( pmf );
+
+ /* set our own Store Manager to allow counting SQL statements */
+ StoreManagerDebug.setup( (AbstractPersistenceManagerFactory) pmf );
+
+ SchemaTool.createSchemaTables(
+ new URL[]{ getClass().getResource( "/org/codehaus/plexus/redback/users/jdo/package.jdo" ) }, new URL[]{ },
+ null, false, null ); //$NON-NLS-1$
+
+ PersistenceManager pm = pmf.getPersistenceManager();
+
+ pm.close();
+
+ setUserManager( jdoUserManager );
+
+ /* save the store manager to access the queries executed */
+ JdoUserManager userManager = (JdoUserManager) getUserManager();
+ storeManager = StoreManagerDebug.getConfiguredStoreManager( userManager.getPersistenceManager() );
+
+ }
+
+ protected void assertCleanUserManager()
+ {
+ // database cleanup
+ ( (JdoUserManager) getUserManager()).eraseDatabase();
+
+
+
+ super.assertCleanUserManager();
+ }
+
+
+}
diff --git a/redback-users/redback-users-providers/redback-users-jdo/src/test/resources/org/codehaus/plexus/redback/config-defaults.properties b/redback-users/redback-users-providers/redback-users-jdo/src/test/resources/org/codehaus/plexus/redback/config-defaults.properties
new file mode 100644
index 000000000..d0dd8d724
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-jdo/src/test/resources/org/codehaus/plexus/redback/config-defaults.properties
@@ -0,0 +1,127 @@
+#
+# Copyright 2006 The Codehaus.
+#
+# 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.
+#
+
+# --------------------------------------------------------------------
+# Application Configuration
+
+application.timestamp=EEE d MMM yyyy HH:mm:ss Z
+
+# --------------------------------------------------------------------
+# JDBC Setup
+
+jdbc.driver.name=org.hsqldb.jdbcDriver
+jdbc.url=jdbc:hsqldb:mem:redback-test
+jdbc.username=sa
+jdbc.password=
+
+# --------------------------------------------------------------------
+# Email Settings
+
+email.jndiSessionName=java:comp/env/mail/Session
+email.smtp.host=localhost
+email.smtp.port=25
+email.smtp.ssl.enabled=false
+email.smtp.tls.enabled=false
+email.smtp.username=
+email.smtp.password=
+
+#TODO: move description elsewhere, remove bad default
+# All emails sent by the system will be from the following address
+#email.from.address=${user.name}@localhost
+# All emails sent by the system will be from the following user name (used in conjunction with address)
+#email.from.name=Unconfigured Username
+
+# If all email addresses (from new user registration) require an account validation email.
+email.validation.required=true
+# Timeout (in minutes) for the key generated for an email validation to remain valid.
+# 2880 minutes = 48 hours
+email.validation.timeout=2880
+# The subject line for the email message.
+email.validation.subject=Welcome
+
+#TODO: move description elsewhere, remove bad default
+# Get the Feedback to use for any outgoing emails.
+# NOTE: if feedback.path starts with a "/" it is appended to the end of the value provided in application.url
+# This value can be in the format/syntax of "/feedback.action" or even "mailto:feedback@application.com"
+#email.feedback.path=/feedback.action
+
+#Set the application base URL. The default is to derive it from the HTTP request
+#application.url=http://myurl.mycompany.com
+
+# --------------------------------------------------------------------
+# Auto Login Settings
+
+security.rememberme.enabled=true
+# Timeout in minutes ( 525600 minutes = 1 year )
+security.rememberme.timeout=525600
+
+# Single Sign On
+# Timeout in minutes
+security.signon.timeout=30
+
+# --------------------------------------------------------------------
+# Default Username Values
+redback.default.admin=admin
+
+# --------------------------------------------------------------------
+# Security Policies
+
+#security.policy.password.encoder=
+security.policy.password.previous.count=6
+security.policy.password.expiration.enabled=true
+security.policy.password.expiration.days=90
+security.policy.password.expiration.notify.days=10
+security.policy.allowed.login.attempt=10
+
+# turn off the perclick enforcement of various security policies, slightly
+# more heavyweight since it will ensure that the User object on each click
+# is up to date
+security.policy.strict.enforcement.enabled=true
+security.policy.strict.force.password.change.enabled=true
+
+# --------------------------------------------------------------------
+# Password Rules
+security.policy.password.rule.alphanumeric.enabled=false
+security.policy.password.rule.alphacount.enabled=true
+security.policy.password.rule.alphacount.minimum=1
+security.policy.password.rule.characterlength.enabled=true
+security.policy.password.rule.characterlength.minimum=1
+security.policy.password.rule.characterlength.maximum=24
+security.policy.password.rule.musthave.enabled=true
+security.policy.password.rule.numericalcount.enabled=true
+security.policy.password.rule.numericalcount.minimum=1
+security.policy.password.rule.reuse.enabled=true
+security.policy.password.rule.nowhitespace.enabled=true
+
+# --------------------------------------------------------------------
+# ldap settings
+#
+ldap.bind.authenticator.enabled=false
+
+# ldap options for configuration via properties file
+#ldap.config.hostname=
+#ldap.config.port=
+#ldap.config.base.dn=
+#ldap.config.context.factory=
+#ldap.config.bind.dn=
+#ldap.config.password=
+#ldap.config.authentication.method=
+
+# config parameter for the ConfigurableUserManager
+user.manager.impl=cached
+
+
+
diff --git a/redback-users/redback-users-providers/redback-users-jdo/src/test/resources/spring-context.xml b/redback-users/redback-users-providers/redback-users-jdo/src/test/resources/spring-context.xml
new file mode 100644
index 000000000..8ee5839c8
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-jdo/src/test/resources/spring-context.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0"?>
+
+<!--
+ ~ 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.
+ -->
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+ http://www.springframework.org/schema/context
+ http://www.springframework.org/schema/context/spring-context-3.0.xsd">
+
+ <bean name="jdoFactory#users" class="org.codehaus.plexus.redback.common.jdo.UserConfigurableJdoFactory">
+ <property name="config" ref="userConfiguration"/>
+ <property name="driverName" value="org.hsqldb.jdbcDriver"/>
+ <property name="url" value="jdbc:hsqldb:mem:redback-users-tests" />
+ <property name="userName" value="sa"/>
+ <property name="password" value=""/>
+ </bean>
+
+ <bean name="userConfiguration" class="org.codehaus.plexus.redback.configuration.UserConfiguration">
+ <property name="registry" ref="test-conf"/>
+ </bean>
+
+ <bean name="commons-configuration" class="org.codehaus.redback.components.registry.commons.CommonsConfigurationRegistry">
+ <property name="properties">
+ <value>
+ <![CDATA[
+ <configuration>
+ <properties fileName="test.properties" config-name="properties"/>
+ </configuration>
+ ]]>
+ </value>
+ </property>
+ </bean>
+
+ <alias name="commons-configuration" alias="test-conf"/>
+
+</beans> \ No newline at end of file
diff --git a/redback-users/redback-users-providers/redback-users-jdo/src/test/resources/test.properties b/redback-users/redback-users-providers/redback-users-jdo/src/test/resources/test.properties
new file mode 100644
index 000000000..db486ea3e
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-jdo/src/test/resources/test.properties
@@ -0,0 +1 @@
+jdbc.driver.name=org.hsqldb.jdbcDriver \ No newline at end of file
diff --git a/redback-users/redback-users-providers/redback-users-ldap/pom.xml b/redback-users/redback-users-providers/redback-users-ldap/pom.xml
new file mode 100644
index 000000000..0bda5365c
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-ldap/pom.xml
@@ -0,0 +1,95 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.codehaus.redback</groupId>
+ <artifactId>redback-users-providers</artifactId>
+ <version>1.5-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>redback-users-ldap</artifactId>
+
+ <name>Redback :: Users Provider :: LDAP</name>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.codehaus.redback</groupId>
+ <artifactId>redback-users-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.redback</groupId>
+ <artifactId>redback-policy</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.redback</groupId>
+ <artifactId>redback-common-ldap</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-context-support</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.redback.components.cache</groupId>
+ <artifactId>spring-cache-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.redback.components.cache</groupId>
+ <artifactId>spring-cache-ehcache</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>javax.annotation</groupId>
+ <artifactId>jsr250-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.codehaus.redback.components</groupId>
+ <artifactId>spring-apacheds</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.sonatype.plugins</groupId>
+ <artifactId>port-allocator-maven-plugin</artifactId>
+ <version>1.1</version>
+ <executions>
+ <execution>
+ <id>allocate-ldap-port</id>
+ <phase>process-classes</phase>
+ <goals>
+ <goal>allocate-ports</goal>
+ </goals>
+ <configuration>
+ <ports>
+ <port>
+ <name>ldapPort</name>
+ <portNumber>10390</portNumber>
+ </port>
+ </ports>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <systemPropertyVariables>
+ <ldapPort>${ldapPort}</ldapPort>
+ <basedir>${basedir}</basedir>
+ </systemPropertyVariables>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+
+</project>
diff --git a/redback-users/redback-users-providers/redback-users-ldap/src/main/java/org/codehaus/plexus/redback/users/ldap/LdapUserManager.java b/redback-users/redback-users-providers/redback-users-ldap/src/main/java/org/codehaus/plexus/redback/users/ldap/LdapUserManager.java
new file mode 100644
index 000000000..3e410a862
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-ldap/src/main/java/org/codehaus/plexus/redback/users/ldap/LdapUserManager.java
@@ -0,0 +1,498 @@
+package org.codehaus.plexus.redback.users.ldap;
+/*
+ * Copyright 2001-2007 The Codehaus.
+ *
+ * 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.
+ */
+
+
+import org.codehaus.plexus.redback.common.ldap.LdapUser;
+import org.codehaus.plexus.redback.common.ldap.MappingException;
+import org.codehaus.plexus.redback.common.ldap.UserMapper;
+import org.codehaus.plexus.redback.common.ldap.connection.LdapConnection;
+import org.codehaus.plexus.redback.common.ldap.connection.LdapConnectionFactory;
+import org.codehaus.plexus.redback.common.ldap.connection.LdapException;
+import org.codehaus.plexus.redback.users.AbstractUserManager;
+import org.codehaus.plexus.redback.users.User;
+import org.codehaus.plexus.redback.users.UserNotFoundException;
+import org.codehaus.plexus.redback.users.UserQuery;
+import org.codehaus.plexus.redback.users.ldap.ctl.LdapController;
+import org.codehaus.plexus.redback.users.ldap.ctl.LdapControllerException;
+import org.codehaus.plexus.redback.users.ldap.service.LdapCacheService;
+import org.springframework.stereotype.Service;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.naming.directory.DirContext;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author <a href="jesse@codehaus.org"> jesse
+ * @version "$Id$"
+ */
+@Service( "userManager#ldap" )
+public class LdapUserManager
+ extends AbstractUserManager
+{
+ @Inject
+ @Named( value = "ldapConnectionFactory#configurable" )
+ private LdapConnectionFactory connectionFactory;
+
+ @Inject
+ private LdapController controller;
+
+ @Inject
+ @Named( value = "userMapper#ldap" )
+ private UserMapper mapper;
+
+ @Inject
+ private LdapCacheService ldapCacheService;
+
+ private User guestUser;
+
+ public boolean isReadOnly()
+ {
+ return true;
+ }
+
+ public User addUser( User user )
+ {
+ return addUser( user, true );
+ }
+
+ public void addUserUnchecked( User user )
+ {
+ addUser( user, false );
+ }
+
+ private User addUser( User user, boolean checked )
+ {
+ if ( user == null )
+ {
+ return null;
+ }
+
+ if ( GUEST_USERNAME.equals( user.getUsername() ) )
+ {
+ guestUser = user;
+ return guestUser;
+ }
+
+ LdapConnection ldapConnection = getLdapConnection();
+ try
+ {
+ DirContext context = ldapConnection.getDirContext();
+ controller.createUser( user, context, checked );
+ }
+ catch ( LdapControllerException e )
+ {
+ log.error( "Error mapping user: " + user.getPrincipal() + " to LDAP attributes.", e );
+ }
+ catch ( MappingException e )
+ {
+ log.error( "Error mapping user: " + user.getPrincipal() + " to LDAP attributes.", e );
+ }
+ finally
+ {
+ closeLdapConnection( ldapConnection );
+ }
+ return user;
+ }
+
+ public User createUser( String username, String fullName, String emailAddress )
+ {
+ return mapper.newUserInstance( username, fullName, emailAddress );
+ }
+
+ public UserQuery createUserQuery()
+ {
+ return new LdapUserQuery();
+ }
+
+ public void deleteUser( Object principal )
+ throws UserNotFoundException
+ {
+ if ( principal != null )
+ {
+ clearFromCache( principal.toString() );
+ }
+
+ LdapConnection ldapConnection = getLdapConnection();
+ try
+ {
+ DirContext context = ldapConnection.getDirContext();
+ controller.removeUser( principal, context );
+ }
+ catch ( LdapControllerException e )
+ {
+ log.error( "Failed to delete user: {}", principal, e );
+ }
+ finally
+ {
+ closeLdapConnection( ldapConnection );
+ }
+ }
+
+ public void deleteUser( String username )
+ throws UserNotFoundException
+ {
+ if ( username != null )
+ {
+ clearFromCache( username );
+ }
+
+ LdapConnection ldapConnection = getLdapConnection();
+ try
+ {
+ DirContext context = ldapConnection.getDirContext();
+ controller.removeUser( username, context );
+ }
+ catch ( LdapControllerException e )
+ {
+ log.error( "Failed to delete user: " + username, e );
+ }
+ finally
+ {
+ closeLdapConnection( ldapConnection );
+ }
+ }
+
+ public void eraseDatabase()
+ {
+ // TODO Implement erase!
+ }
+
+ public User findUser( String username )
+ throws UserNotFoundException
+ {
+ if ( username == null )
+ {
+ throw new UserNotFoundException( "Unable to find user based on null username." );
+ }
+
+ if ( GUEST_USERNAME.equals( username ) )
+ {
+ return getGuestUser();
+ }
+
+ // REDBACK-289/MRM-1488
+ // look for the user in the cache first
+ LdapUser ldapUser = ldapCacheService.getUser( username );
+ if ( ldapUser != null )
+ {
+ log.debug( "User {} found in cache.", username );
+ return ldapUser;
+ }
+
+ LdapConnection ldapConnection = getLdapConnection();
+ try
+ {
+ DirContext context = ldapConnection.getDirContext();
+ User user = controller.getUser( username, context );
+ if ( user == null )
+ {
+ throw new UserNotFoundException( "user with name " + username + " not found " );
+ }
+
+ // REDBACK-289/MRM-1488
+ log.debug( "Adding user {} to cache..", username );
+
+ ldapCacheService.addUser( (LdapUser) user );
+
+ return user;
+ }
+ catch ( LdapControllerException e )
+ {
+ log.error( "Failed to find user: {}", username, e );
+ return null;
+ }
+ catch ( MappingException e )
+ {
+ log.error( "Failed to map user: {}", username, e );
+ return null;
+ }
+ finally
+ {
+ closeLdapConnection( ldapConnection );
+ }
+ }
+
+ public User getGuestUser()
+ throws UserNotFoundException
+ {
+ if ( guestUser == null )
+ {
+ throw new UserNotFoundException( "Guest user doesn't exist." );
+ }
+ return guestUser;
+ }
+
+ public User findUser( Object principal )
+ throws UserNotFoundException
+ {
+ if ( principal == null )
+ {
+ throw new UserNotFoundException( "Unable to find user based on null principal." );
+ }
+
+ if ( GUEST_USERNAME.equals( principal.toString() ) )
+ {
+ return getGuestUser();
+ }
+
+ // REDBACK-289/MRM-1488
+ // look for the user in the cache first
+ LdapUser ldapUser = ldapCacheService.getUser( principal.toString() );
+ if ( ldapUser != null )
+ {
+ log.debug( "User {} found in cache.", principal );
+ return ldapUser;
+ }
+
+ LdapConnection ldapConnection = getLdapConnection();
+ try
+ {
+ DirContext context = ldapConnection.getDirContext();
+
+ User user = controller.getUser( principal, context );
+
+ // REDBACK-289/MRM-1488
+ log.debug( "Adding user {} to cache..", principal );
+
+ ldapCacheService.addUser( (LdapUser) user );
+
+ return user;
+ }
+ catch ( LdapControllerException e )
+ {
+ log.error( "Failed to find user: {}", principal, e );
+ return null;
+ }
+ catch ( MappingException e )
+ {
+ log.error( "Failed to map user: {}", principal, e );
+ return null;
+ }
+ finally
+ {
+ closeLdapConnection( ldapConnection );
+ }
+ }
+
+ public List<User> findUsersByEmailKey( String emailKey, boolean orderAscending )
+ {
+ LdapUserQuery query = new LdapUserQuery();
+ query.setEmail( emailKey );
+ query.setOrderBy( UserQuery.ORDER_BY_EMAIL );
+ query.setAscending( orderAscending );
+ return findUsersByQuery( query );
+ }
+
+ public List<User> findUsersByFullNameKey( String fullNameKey, boolean orderAscending )
+ {
+ LdapUserQuery query = new LdapUserQuery();
+ query.setFullName( fullNameKey );
+ query.setOrderBy( UserQuery.ORDER_BY_FULLNAME );
+ query.setAscending( orderAscending );
+ return findUsersByQuery( query );
+ }
+
+ public List<User> findUsersByQuery( UserQuery query )
+ {
+ if ( query == null )
+ {
+ return Collections.emptyList();
+ }
+
+ LdapConnection ldapConnection = getLdapConnection();
+ try
+ {
+ DirContext context = ldapConnection.getDirContext();
+ return controller.getUsersByQuery( (LdapUserQuery) query, context );
+ }
+ catch ( LdapControllerException e )
+ {
+ log.error( "Failed to find user", e );
+ return null;
+ }
+ catch ( MappingException e )
+ {
+ log.error( "Failed to map user", e );
+ return null;
+ }
+ finally
+ {
+ closeLdapConnection( ldapConnection );
+ }
+ }
+
+ /**
+ * @see org.codehaus.plexus.redback.users.UserManager#findUsersByUsernameKey(java.lang.String, boolean)
+ */
+ public List<User> findUsersByUsernameKey( String usernameKey, boolean orderAscending )
+ {
+ LdapUserQuery query = new LdapUserQuery();
+ query.setUsername( usernameKey );
+ query.setOrderBy( UserQuery.ORDER_BY_USERNAME );
+ query.setAscending( orderAscending );
+ return findUsersByQuery( query );
+ }
+
+ public String getId()
+ {
+ return "LDAP User-Manager: " + getClass().getName();
+ }
+
+ /**
+ * @see org.codehaus.plexus.redback.users.UserManager#getUsers()
+ */
+ public List<User> getUsers()
+ {
+ LdapConnection ldapConnection = getLdapConnection();
+ try
+ {
+ DirContext context = ldapConnection.getDirContext();
+ List<User> users = new ArrayList<User>( controller.getUsers( context ) );
+ //We add the guest user because it isn't in LDAP
+ try
+ {
+ User u = getGuestUser();
+ if ( u != null )
+ {
+ users.add( u );
+ }
+ }
+ catch ( UserNotFoundException e )
+ {
+ //Nothing to do
+ }
+ return users;
+ }
+ catch ( Exception e )
+ {
+ log.error( e.getMessage(), e );
+ }
+ finally
+ {
+ closeLdapConnection( ldapConnection );
+ }
+ return Collections.emptyList();
+ }
+
+ public List<User> getUsers( boolean orderAscending )
+ {
+ return getUsers();
+ }
+
+ public User updateUser( User user )
+ throws UserNotFoundException
+ {
+ return updateUser( user, false );
+ }
+
+ public User updateUser( User user, boolean passwordChangeRequired )
+ throws UserNotFoundException
+ {
+ if ( user != null )
+ {
+ clearFromCache( user.getUsername() );
+ }
+
+ LdapConnection ldapConnection = getLdapConnection();
+ try
+ {
+ DirContext context = ldapConnection.getDirContext();
+ controller.updateUser( user, context );
+ }
+ catch ( LdapControllerException e )
+ {
+ log.error( "Failed to update user: " + user.getPrincipal(), e );
+ }
+ catch ( MappingException e )
+ {
+ log.error( "Failed to update user: " + user.getPrincipal(), e );
+ }
+ finally
+ {
+ closeLdapConnection( ldapConnection );
+ }
+ return user;
+ }
+
+ public boolean userExists( Object principal )
+ {
+ if ( principal == null )
+ {
+ return false;
+ }
+
+ // REDBACK-289/MRM-1488
+ // look for the user in the cache first
+ LdapUser ldapUser = ldapCacheService.getUser( principal.toString() );
+ if ( ldapUser != null )
+ {
+ log.debug( "User {} found in cache.", principal );
+ return true;
+ }
+
+ LdapConnection ldapConnection = getLdapConnection();
+ try
+ {
+ DirContext context = ldapConnection.getDirContext();
+ return controller.userExists( principal, context );
+ }
+ catch ( LdapControllerException e )
+ {
+ log.warn( "Failed to search for user: " + principal, e );
+ return false;
+ }
+ finally
+ {
+ closeLdapConnection( ldapConnection );
+ }
+ }
+
+ private LdapConnection getLdapConnection()
+ {
+ try
+ {
+ return connectionFactory.getConnection();
+ }
+ catch ( LdapException e )
+ {
+ log.warn( "failed to get a ldap connection " + e.getMessage(), e );
+ throw new RuntimeException( "failed to get a ldap connection " + e.getMessage(), e );
+ }
+ }
+
+ private void closeLdapConnection( LdapConnection ldapConnection )
+ {
+ if ( ldapConnection != null )
+ {
+ ldapConnection.close();
+ }
+ }
+
+ // REDBACK-289/MRM-1488
+ private void clearFromCache( String username )
+ {
+ log.debug( "Removing user {} from cache..", username );
+ ldapCacheService.removeUser( username );
+
+ log.debug( "Removing userDn for user {} from cache..", username );
+ ldapCacheService.removeLdapUserDn( username );
+ }
+
+}
diff --git a/redback-users/redback-users-providers/redback-users-ldap/src/main/java/org/codehaus/plexus/redback/users/ldap/LdapUserQuery.java b/redback-users/redback-users-providers/redback-users-ldap/src/main/java/org/codehaus/plexus/redback/users/ldap/LdapUserQuery.java
new file mode 100644
index 000000000..6c0580543
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-ldap/src/main/java/org/codehaus/plexus/redback/users/ldap/LdapUserQuery.java
@@ -0,0 +1,60 @@
+package org.codehaus.plexus.redback.users.ldap;
+
+/*
+ * Copyright 2001-2007 The Codehaus.
+ *
+ * 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.
+ */
+
+import org.codehaus.plexus.redback.common.ldap.UserMapper;
+import org.codehaus.plexus.redback.users.AbstractUserQuery;
+
+public class LdapUserQuery
+ extends AbstractUserQuery
+{
+
+ public void setFirstResult( int firstResult )
+ {
+ super.setFirstResult( firstResult );
+ throw new UnsupportedOperationException( "Result limiting is not yet supported for LDAP." );
+ }
+
+ public void setMaxResults( int maxResults )
+ {
+ super.setMaxResults( maxResults );
+ throw new UnsupportedOperationException( "Result limiting is not yet supported for LDAP." );
+ }
+
+ public void setOrderBy( String orderBy )
+ {
+ super.setOrderBy( orderBy );
+ throw new UnsupportedOperationException( "Free-form ordering is not yet supported for LDAP." );
+ }
+
+ public String getLdapFilter( UserMapper mapper )
+ {
+ String filter = "";
+ if (this.getEmail() != null )
+ {
+ filter += "(" + mapper.getEmailAddressAttribute() + "=" + this.getEmail() + ")";
+ }
+ if ( this.getFullName() != null )
+ {
+ filter += "(" + mapper.getUserFullNameAttribute() + "=" + this.getFullName() + ")";
+ }
+ filter += "(" + mapper.getUserIdAttribute() + "=" + ( this.getUsername() != null ? this.getUsername() : "*" ) + ")";
+
+ return filter;
+ }
+
+}
diff --git a/redback-users/redback-users-providers/redback-users-ldap/src/main/java/org/codehaus/plexus/redback/users/ldap/ctl/DefaultLdapController.java b/redback-users/redback-users-providers/redback-users-ldap/src/main/java/org/codehaus/plexus/redback/users/ldap/ctl/DefaultLdapController.java
new file mode 100644
index 000000000..4aeb71841
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-ldap/src/main/java/org/codehaus/plexus/redback/users/ldap/ctl/DefaultLdapController.java
@@ -0,0 +1,298 @@
+package org.codehaus.plexus.redback.users.ldap.ctl;
+
+/*
+ * Copyright 2001-2007 The Codehaus.
+ *
+ * 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.
+ */
+
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+
+import org.codehaus.plexus.redback.common.ldap.LdapUser;
+import org.codehaus.plexus.redback.common.ldap.LdapUserMapper;
+import org.codehaus.plexus.redback.common.ldap.MappingException;
+import org.codehaus.plexus.redback.common.ldap.UserMapper;
+import org.codehaus.plexus.redback.users.User;
+import org.codehaus.plexus.redback.users.UserManager;
+import org.codehaus.plexus.redback.users.ldap.LdapUserQuery;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author <a href="jesse@codehaus.org"> jesse
+ * @version "$Id$"
+ */
+@Service
+public class DefaultLdapController
+ implements LdapController
+{
+
+ private Logger log = LoggerFactory.getLogger( getClass() );
+
+ @Inject
+ @Named(value = "userMapper#ldap")
+ private UserMapper mapper;
+
+ /**
+ * @see org.codehaus.plexus.redback.users.ldap.ctl.LdapControllerI#removeUser(java.lang.Object, javax.naming.directory.DirContext)
+ */
+ public void removeUser( Object principal, DirContext context )
+ throws LdapControllerException
+ {
+
+ }
+
+ /**
+ * @see org.codehaus.plexus.redback.users.ldap.ctl.LdapControllerI#updateUser(org.codehaus.plexus.redback.users.User, javax.naming.directory.DirContext)
+ */
+ public void updateUser( User user, DirContext context )
+ throws LdapControllerException, MappingException
+ {
+
+ }
+
+ /**
+ * @see org.codehaus.plexus.redback.users.ldap.ctl.LdapControllerI#userExists(java.lang.Object, javax.naming.directory.DirContext)
+ */
+ public boolean userExists( Object key, DirContext context )
+ throws LdapControllerException
+ {
+ NamingEnumeration<SearchResult> results = null;
+ try
+ {
+ results = searchUsers( key, context );
+ return results.hasMoreElements();
+ }
+ catch ( NamingException e )
+ {
+ throw new LdapControllerException( "Error searching for the existence of user: " + key, e );
+ }
+ finally
+ {
+ if ( results != null )
+ try
+ {
+ results.close();
+ }
+ catch ( NamingException e )
+ {
+ log.warn( "Error closing search results", e );
+ }
+ }
+ }
+
+ protected NamingEnumeration<SearchResult> searchUsers( Object key, DirContext context )
+ throws NamingException
+ {
+ LdapUserQuery query = new LdapUserQuery();
+ query.setUsername( "" + key );
+ return searchUsers( context, null, query );
+ }
+
+ protected NamingEnumeration<SearchResult> searchUsers( DirContext context )
+ throws NamingException
+ {
+ return searchUsers( context, null, null );
+ }
+
+ protected NamingEnumeration<SearchResult> searchUsers( DirContext context, String[] returnAttributes )
+ throws NamingException
+ {
+ return searchUsers( context, returnAttributes, null );
+ }
+
+ protected NamingEnumeration<SearchResult> searchUsers( DirContext context, String[] returnAttributes, LdapUserQuery query )
+ throws NamingException
+ {
+ if ( query == null )
+ {
+ query = new LdapUserQuery();
+ }
+ SearchControls ctls = new SearchControls();
+
+ ctls.setDerefLinkFlag( true );
+ ctls.setSearchScope( SearchControls.SUBTREE_SCOPE );
+ ctls.setReturningAttributes( mapper.getReturningAttributes() );
+ ctls.setCountLimit( ( ( LdapUserMapper ) mapper ).getMaxResultCount() );
+
+ String finalFilter = "(&(objectClass=" + mapper.getUserObjectClass() + ")" +
+ ( mapper.getUserFilter() != null ? mapper.getUserFilter() : "" ) + query.getLdapFilter(mapper) + ")";
+
+ log.info( "Searching for users with filter: \'{}\'" + " from base dn: {}",finalFilter, mapper.getUserBaseDn() );
+
+ return context.search( mapper.getUserBaseDn(), finalFilter, ctls );
+ }
+
+ /**
+ * @see org.codehaus.plexus.redback.users.ldap.ctl.LdapControllerI#getUsers(javax.naming.directory.DirContext)
+ */
+ public Collection<User> getUsers( DirContext context )
+ throws LdapControllerException, MappingException
+ {
+ NamingEnumeration<SearchResult> results = null;
+ try
+ {
+ results = searchUsers( context, null, null );
+ Set<User> users = new LinkedHashSet<User>();
+
+ while ( results.hasMoreElements() )
+ {
+ SearchResult result = results.nextElement();
+
+ users.add( mapper.getUser( result.getAttributes() ) );
+ }
+
+ return users;
+ }
+ catch ( NamingException e )
+ {
+ String message = "Failed to retrieve ldap information for users.";
+
+ throw new LdapControllerException( message, e );
+ }
+ finally
+ {
+ if ( results != null )
+ try
+ {
+ results.close();
+ }
+ catch ( NamingException e )
+ {
+ log.warn( "failed to close search results", e );
+ }
+ }
+ }
+
+ /**
+ * @see org.codehaus.plexus.redback.users.ldap.ctl.LdapControllerI#getUsersByQuery(org.codehaus.plexus.redback.users.ldap.LdapUserQuery, javax.naming.directory.DirContext)
+ */
+ public List<User> getUsersByQuery( LdapUserQuery query, DirContext context )
+ throws LdapControllerException, MappingException
+ {
+ NamingEnumeration<SearchResult> results = null;
+ try
+ {
+ results = searchUsers( context, null, query );
+ List<User> users = new LinkedList<User>();
+
+ while ( results.hasMoreElements() )
+ {
+ SearchResult result = results.nextElement();
+
+ users.add( mapper.getUser( result.getAttributes() ) );
+ }
+
+ return users;
+ }
+ catch ( NamingException e )
+ {
+ String message = "Failed to retrieve ldap information for users.";
+
+ throw new LdapControllerException( message, e );
+ }
+ finally
+ {
+ if ( results != null )
+ try
+ {
+ results.close();
+ }
+ catch ( NamingException e )
+ {
+ log.warn( "failed to close search results", e );
+ }
+ }
+ }
+
+ /**
+ * @see org.codehaus.plexus.redback.users.ldap.ctl.LdapControllerI#createUser(org.codehaus.plexus.redback.users.User, javax.naming.directory.DirContext, boolean)
+ */
+ public void createUser( User user, DirContext context, boolean encodePasswordIfChanged )
+ throws LdapControllerException, MappingException
+ {
+ if ( user == null )
+ {
+ return;
+ }
+ if ( user.getUsername().equals( UserManager.GUEST_USERNAME ) )
+ {
+ //We don't store guest
+ return;
+ }
+
+ }
+
+ /**
+ * @see org.codehaus.plexus.redback.users.ldap.ctl.LdapControllerI#getUser(java.lang.Object, javax.naming.directory.DirContext)
+ */
+ public LdapUser getUser( Object key, DirContext context )
+ throws LdapControllerException, MappingException
+ {
+ String username = key.toString();
+
+ log.info( "Searching for user: {}", username );
+ LdapUserQuery query = new LdapUserQuery();
+ query.setUsername( username );
+
+ NamingEnumeration<SearchResult> result = null;
+ try
+ {
+ result = searchUsers( context, null, query );
+
+ if ( result.hasMoreElements() )
+ {
+ SearchResult next = result.nextElement();
+
+ return mapper.getUser( next.getAttributes() );
+ }
+ else
+ {
+ return null;
+ }
+ }
+ catch ( NamingException e )
+ {
+ String message = "Failed to retrieve information for user: " + username;
+
+ throw new LdapControllerException( message, e );
+ }
+ finally
+ {
+ if ( result != null )
+ try
+ {
+ result.close();
+ }
+ catch ( NamingException e )
+ {
+ log.warn( "failed to close search results", e );
+ }
+ }
+ }
+
+}
diff --git a/redback-users/redback-users-providers/redback-users-ldap/src/main/java/org/codehaus/plexus/redback/users/ldap/ctl/LdapController.java b/redback-users/redback-users-providers/redback-users-ldap/src/main/java/org/codehaus/plexus/redback/users/ldap/ctl/LdapController.java
new file mode 100644
index 000000000..d94da8179
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-ldap/src/main/java/org/codehaus/plexus/redback/users/ldap/ctl/LdapController.java
@@ -0,0 +1,54 @@
+package org.codehaus.plexus.redback.users.ldap.ctl;
+
+/*
+ * Copyright 2001-2007 The Codehaus.
+ *
+ * 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.
+ */
+
+import org.codehaus.plexus.redback.common.ldap.LdapUser;
+import org.codehaus.plexus.redback.common.ldap.MappingException;
+import org.codehaus.plexus.redback.users.User;
+import org.codehaus.plexus.redback.users.ldap.LdapUserQuery;
+
+import javax.naming.directory.DirContext;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * @version $Id$
+ */
+public interface LdapController
+{
+
+ void removeUser( Object principal, DirContext context )
+ throws LdapControllerException;
+
+ void updateUser( User user, DirContext context )
+ throws LdapControllerException, MappingException;
+
+ boolean userExists( Object key, DirContext context )
+ throws LdapControllerException;
+
+ Collection<User> getUsers( DirContext context )
+ throws LdapControllerException, MappingException;
+
+ void createUser( User user, DirContext context, boolean encodePasswordIfChanged )
+ throws LdapControllerException, MappingException;
+
+ LdapUser getUser( Object key, DirContext context )
+ throws LdapControllerException, MappingException;
+
+ List<User> getUsersByQuery( LdapUserQuery query, DirContext context )
+ throws LdapControllerException, MappingException;
+}
diff --git a/redback-users/redback-users-providers/redback-users-ldap/src/main/java/org/codehaus/plexus/redback/users/ldap/ctl/LdapControllerException.java b/redback-users/redback-users-providers/redback-users-ldap/src/main/java/org/codehaus/plexus/redback/users/ldap/ctl/LdapControllerException.java
new file mode 100644
index 000000000..821f36681
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-ldap/src/main/java/org/codehaus/plexus/redback/users/ldap/ctl/LdapControllerException.java
@@ -0,0 +1,33 @@
+package org.codehaus.plexus.redback.users.ldap.ctl;
+
+/*
+ * Copyright 2001-2007 The Codehaus.
+ *
+ * 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.
+ */
+
+public class LdapControllerException
+ extends Exception
+{
+
+ public LdapControllerException( String message, Throwable cause )
+ {
+ super( message, cause );
+ }
+
+ public LdapControllerException( String message )
+ {
+ super( message );
+ }
+
+}
diff --git a/redback-users/redback-users-providers/redback-users-ldap/src/main/java/org/codehaus/plexus/redback/users/ldap/service/DefaultLdapCacheService.java b/redback-users/redback-users-providers/redback-users-ldap/src/main/java/org/codehaus/plexus/redback/users/ldap/service/DefaultLdapCacheService.java
new file mode 100644
index 000000000..75a185372
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-ldap/src/main/java/org/codehaus/plexus/redback/users/ldap/service/DefaultLdapCacheService.java
@@ -0,0 +1,118 @@
+package org.codehaus.plexus.redback.users.ldap.service;
+
+/*
+ * Copyright 2011 The Codehaus.
+ *
+ * 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.
+ */
+
+import org.codehaus.plexus.cache.builder.CacheBuilder;
+import org.codehaus.plexus.redback.common.ldap.LdapUser;
+import org.springframework.stereotype.Service;
+
+import javax.inject.Inject;
+
+/**
+ * DefaultLdapCacheService
+ *
+ * @author: Maria Odea Ching <oching@apache.org>
+ * @version
+ */
+@Service
+public class DefaultLdapCacheService
+ implements LdapCacheService
+{
+ @Inject
+ private CacheBuilder cacheBuilder;
+
+ // LDAP Users
+
+ /**
+ * @see LdapCacheService#getUser(String)
+ */
+ public LdapUser getUser( String username )
+ {
+ return (LdapUser) cacheBuilder.getCache( "ldapUser" ).get( username );
+ }
+
+ /**
+ * @see LdapCacheService#removeUser(String)
+ */
+ public boolean removeUser( String username )
+ {
+ return ( cacheBuilder.getCache( "ldapUser" ).remove( username ) == null ? false : true );
+ }
+
+ /**
+ * @see LdapCacheService#removeAllUsers()
+ */
+ public void removeAllUsers()
+ {
+ cacheBuilder.getCache( "ldapUser" ).clear();
+ }
+
+ /**
+ * @see LdapCacheService#addUser(org.codehaus.plexus.redback.common.ldap.LdapUser)
+ */
+ public void addUser( LdapUser user )
+ {
+ LdapUser existingUser = (LdapUser) cacheBuilder.getCache( "ldapUser" ).get( user.getUsername() );
+ if( existingUser != null )
+ {
+ removeUser( user.getUsername() );
+ }
+
+ cacheBuilder.getCache( "ldapUser" ).put( user.getUsername(), user );
+ }
+
+ // LDAP UserDn
+
+ /**
+ * @see LdapCacheService#getLdapUserDn(String)
+ */
+ public String getLdapUserDn( String username )
+ {
+ return (String) cacheBuilder.getCache( "ldapUserDn" ).get( username );
+ }
+
+ /**
+ * @see LdapCacheService#removeLdapUserDn(String)
+ */
+ public boolean removeLdapUserDn( String username )
+ {
+ return ( cacheBuilder.getCache( "ldapUserDn" ).remove( username ) == null ? false : true );
+ }
+
+ /**
+ * @see org.codehaus.plexus.redback.users.ldap.service.LdapCacheService#removeAllLdapUserDn()
+ */
+ public void removeAllLdapUserDn()
+ {
+ cacheBuilder.getCache( "ldapUserDn" ).clear();
+ }
+
+ /**
+ * @see LdapCacheService#addLdapUserDn(String, String)
+ */
+ public void addLdapUserDn( String username, String userDn )
+ {
+ String existingUserDn = (String) cacheBuilder.getCache( "ldapUserDn" ).get( username );
+ if( existingUserDn != null )
+ {
+ removeUser( username );
+ }
+
+ cacheBuilder.getCache( "ldapUserDn" ).put( username, userDn );
+ }
+
+}
diff --git a/redback-users/redback-users-providers/redback-users-ldap/src/main/java/org/codehaus/plexus/redback/users/ldap/service/LdapCacheService.java b/redback-users/redback-users-providers/redback-users-ldap/src/main/java/org/codehaus/plexus/redback/users/ldap/service/LdapCacheService.java
new file mode 100644
index 000000000..322f56bf9
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-ldap/src/main/java/org/codehaus/plexus/redback/users/ldap/service/LdapCacheService.java
@@ -0,0 +1,90 @@
+package org.codehaus.plexus.redback.users.ldap.service;
+
+/*
+ * Copyright 2011 The Codehaus.
+ *
+ * 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.
+ */
+
+import org.codehaus.plexus.redback.common.ldap.LdapUser;
+
+/**
+ * LdapCacheService
+ *
+ * Service that manages the LDAP caches: LDAP connections and LDAP users
+ *
+ * @author: Maria Odea Ching <oching@apache.org>
+ * @version
+ */
+public interface LdapCacheService
+{
+ /**
+ * Retrieve LDAP user with the given username from the cache.
+ * Returns null if user is not found.
+ *
+ * @param username
+ * @return
+ */
+ LdapUser getUser( String username );
+
+ /**
+ * Remove LDAP user with the given username from the cache.
+ * Returns the removed object if it was in the cache. Otherwise, returns null.
+ *
+ * @param username
+ * @return
+ */
+ boolean removeUser( String username );
+
+ /**
+ * Remove all LDAP users in the cache. In short, it flushes the cache.
+ *
+ */
+ void removeAllUsers();
+
+ /**
+ * Adds the user to the LDAP users cache.
+ *
+ * @param user
+ */
+ void addUser( LdapUser user );
+
+ /**
+ * Retrieve the cached LDAP userDn for the given user.
+ *
+ * @param username
+ * @return
+ */
+ String getLdapUserDn( String username );
+
+ /**
+ * Remove the cached LDAP userDn for the given user.
+ *
+ * @param username
+ * @return
+ */
+ boolean removeLdapUserDn( String username );
+
+ /**
+ * Remove all cached LDAP userDn
+ */
+ void removeAllLdapUserDn();
+
+ /**
+ * All the LDAP userDn for the given user to the cache
+ *
+ * @param username
+ * @param userDn
+ */
+ void addLdapUserDn( String username, String userDn );
+}
diff --git a/redback-users/redback-users-providers/redback-users-ldap/src/main/resources/META-INF/spring-context.xml b/redback-users/redback-users-providers/redback-users-ldap/src/main/resources/META-INF/spring-context.xml
new file mode 100644
index 000000000..4daa87b8d
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-ldap/src/main/resources/META-INF/spring-context.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0"?>
+
+<!--
+ ~ 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.
+ -->
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+ http://www.springframework.org/schema/context
+ http://www.springframework.org/schema/context/spring-context-3.0.xsd"
+ default-lazy-init="true">
+
+ <context:annotation-config />
+ <context:component-scan
+ base-package="org.codehaus.plexus.redback.users.ldap"/>
+
+ <!-- REDBACK-289/MRM-1488 -->
+ <!-- Cache for LDAP users. Data is refreshed every 3 mins. -->
+ <bean name="cache#ldapUser" class="org.codehaus.plexus.cache.ehcache.EhcacheCache">
+ <property name="diskExpiryThreadIntervalSeconds" value="180"/>
+ <property name="diskPersistent" value="false"/>
+ <property name="eternal" value="false"/>
+ <property name="maxElementsInMemory" value="1000"/>
+ <property name="memoryEvictionPolicy" value="LRU"/>
+ <property name="name" value="ldapUsersCache"/>
+ <property name="overflowToDisk" value="false"/>
+ <property name="timeToIdleSeconds" value="90"/>
+ <property name="timeToLiveSeconds" value="180"/>
+ </bean>
+
+ <!-- Cache for users' LDAP userDn. Data is refreshed every 3 mins. -->
+ <bean name="cache#ldapUserDn" class="org.codehaus.plexus.cache.ehcache.EhcacheCache">
+ <property name="diskExpiryThreadIntervalSeconds" value="180"/>
+ <property name="diskPersistent" value="false"/>
+ <property name="eternal" value="false"/>
+ <property name="maxElementsInMemory" value="1000"/>
+ <property name="memoryEvictionPolicy" value="LRU"/>
+ <property name="name" value="ldapUserDnCache"/>
+ <property name="overflowToDisk" value="false"/>
+ <property name="timeToIdleSeconds" value="90"/>
+ <property name="timeToLiveSeconds" value="180"/>
+ </bean>
+
+</beans> \ No newline at end of file
diff --git a/redback-users/redback-users-providers/redback-users-ldap/src/test/java/org/codehaus/plexus/redback/users/ldap/LdapUserManagerTest.java b/redback-users/redback-users-providers/redback-users-ldap/src/test/java/org/codehaus/plexus/redback/users/ldap/LdapUserManagerTest.java
new file mode 100644
index 000000000..372dff20c
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-ldap/src/test/java/org/codehaus/plexus/redback/users/ldap/LdapUserManagerTest.java
@@ -0,0 +1,337 @@
+package org.codehaus.plexus.redback.users.ldap;
+
+/*
+ * Copyright 2001-2006 The Codehaus.
+ *
+ * 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.
+ */
+
+import junit.framework.TestCase;
+import org.codehaus.plexus.redback.common.ldap.connection.LdapConnection;
+import org.codehaus.plexus.redback.common.ldap.connection.LdapConnectionFactory;
+import org.codehaus.plexus.redback.policy.PasswordEncoder;
+import org.codehaus.plexus.redback.policy.encoders.SHA1PasswordEncoder;
+import org.codehaus.plexus.redback.users.User;
+import org.codehaus.plexus.redback.users.UserManager;
+import org.codehaus.plexus.redback.users.UserNotFoundException;
+import org.codehaus.plexus.redback.users.ldap.service.LdapCacheService;
+import org.codehaus.redback.components.apacheds.ApacheDs;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttribute;
+import javax.naming.directory.BasicAttributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.InitialDirContext;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+import java.util.List;
+
+
+/**
+ * LdapUserManagerTest
+ *
+ * @author <a href="mailto:jesse@codehaus.org">Jesse McConnell</a>
+ * @version $Id$
+ */
+
+@RunWith( SpringJUnit4ClassRunner.class )
+@ContextConfiguration( locations = { "classpath*:/META-INF/spring-context.xml", "classpath*:/spring-context.xml" } )
+public class LdapUserManagerTest
+ extends TestCase
+{
+
+ protected Logger log = LoggerFactory.getLogger( getClass() );
+
+ @Inject
+ @Named(value = "userManager#ldap")
+ private UserManager userManager;
+
+ @Inject
+ @Named( value = "apacheDS#test" )
+ private ApacheDs apacheDs;
+
+ private String suffix;
+
+ private PasswordEncoder passwordEncoder;
+
+ @Inject
+ @Named(value = "ldapConnectionFactory#configurable")
+ private LdapConnectionFactory connectionFactory;
+
+ @Inject
+ private LdapCacheService ldapCacheService;
+
+ public void testFoo()
+ throws Exception
+ {
+
+ }
+
+ @Before
+ public void setUp()
+ throws Exception
+ {
+ super.setUp();
+
+ passwordEncoder = new SHA1PasswordEncoder();
+
+ suffix = apacheDs.addSimplePartition( "test", new String[] { "redback", "plexus", "codehaus", "org" } )
+ .getSuffix();
+
+ log.info( "DN Suffix: " + suffix );
+
+ apacheDs.startServer();
+
+ clearManyUsers();
+
+ makeUsers();
+
+ }
+
+ @After
+ public void tearDown()
+ throws Exception
+ {
+ // clear cache
+ ldapCacheService.removeAllUsers();
+
+ InitialDirContext context = apacheDs.getAdminContext();
+
+ context.unbind( createDn( "jesse" ) );
+
+ context.unbind( createDn( "joakim" ) );
+
+ apacheDs.stopServer();
+
+ super.tearDown();
+ }
+
+ private void makeUsers()
+ throws Exception
+ {
+ InitialDirContext context = apacheDs.getAdminContext();
+
+ String cn = "jesse";
+ bindUserObject( context, cn, createDn( cn ) );
+ assertExist( context, createDn( cn ), "cn", cn );
+
+ cn = "joakim";
+ bindUserObject( context, cn, createDn( cn ) );
+ assertExist( context, createDn( cn ), "cn", cn );
+
+ }
+
+ @Test
+ public void testConnection()
+ throws Exception
+ {
+ assertNotNull( connectionFactory );
+
+ LdapConnection connection = null;
+ try
+ {
+ connection = connectionFactory.getConnection();
+
+ assertNotNull( connection );
+
+ DirContext context = connection.getDirContext();
+
+ assertNotNull( context );
+ } finally {
+ connection.close();
+ }
+ }
+
+ @Test
+ public void testDirectUsersExistence()
+ throws Exception
+ {
+ LdapConnection connection = null;
+ try
+ {
+ connection = connectionFactory.getConnection();
+
+ DirContext context = connection.getDirContext();
+
+ assertExist( context, createDn( "jesse" ), "cn", "jesse" );
+ assertExist( context, createDn( "joakim" ), "cn", "joakim" );
+ } finally {
+ connection.close();
+ }
+
+ }
+
+ @Test
+ public void testUserManager()
+ throws Exception
+ {
+ assertNotNull( userManager );
+
+ //assertNull( ldapCacheService.getUser( "jesse" ) );
+
+ assertTrue( userManager.userExists( "jesse" ) );
+
+ //assertNotNull( ldapCacheService.getUser( "jesse" ) );
+
+ List<User> users = userManager.getUsers();
+
+ assertNotNull( users );
+
+ assertEquals( 2, users.size() );
+
+ User jesse = userManager.findUser( "jesse" );
+
+ assertNotNull( jesse );
+
+ assertEquals( "jesse", jesse.getPrincipal().toString() );
+ assertEquals( "jesse@apache.org", jesse.getEmail() );
+ assertEquals( "foo", jesse.getFullName() );
+ System.out.println( "=====>"+jesse.getEncodedPassword());
+ System.out.println( "=====>"+passwordEncoder.encodePassword( "foo" ));
+ assertTrue( passwordEncoder.isPasswordValid( jesse.getEncodedPassword(), "foo" ) );
+
+ }
+
+ @Test
+ public void testUserNotFoundException()
+ throws Exception
+ {
+ try
+ {
+ userManager.findUser( "foo bar" );
+ fail( "not a UserNotFoundException with an unknown user" );
+ }
+ catch ( UserNotFoundException e )
+ {
+ // cool it works !
+ }
+ }
+
+ @Test
+ public void testWithManyUsers()
+ throws Exception
+ {
+ makeManyUsers();
+
+ assertNotNull( userManager );
+
+ assertTrue( userManager.userExists( "user10" ) );
+
+ List<User> users = userManager.getUsers();
+
+ assertNotNull( users );
+
+ assertEquals( 10002, users.size() );
+
+ User user10 = userManager.findUser( "user10" );
+
+ assertNotNull( user10 );
+ }
+
+ private void makeManyUsers()
+ throws Exception
+ {
+ InitialDirContext context = apacheDs.getAdminContext();
+
+ for ( int i = 0 ; i < 10000 ; i++ )
+ {
+ String cn = "user"+i;
+ bindUserObject( context, cn, createDn( cn ) );
+ }
+
+ }
+
+ private void clearManyUsers()
+ throws Exception
+ {
+ InitialDirContext context = apacheDs.getAdminContext();
+
+ for ( int i = 0 ; i < 10000 ; i++ )
+ {
+ String cn = "user"+i;
+ try
+ {
+ context.unbind( createDn( cn ) );
+ }
+ catch ( NamingException e )
+ {
+ // OK lets try with next one
+ }
+ }
+
+ }
+
+ private void bindUserObject( DirContext context, String cn, String dn )
+ throws Exception
+ {
+ Attributes attributes = new BasicAttributes( true );
+ BasicAttribute objectClass = new BasicAttribute( "objectClass" );
+ objectClass.add( "top" );
+ objectClass.add( "inetOrgPerson" );
+ objectClass.add( "person" );
+ objectClass.add( "organizationalperson" );
+ attributes.put( objectClass );
+ attributes.put( "cn", cn );
+ attributes.put( "sn", "foo" );
+ attributes.put( "mail", cn+"@apache.org" );
+ attributes.put( "userPassword", passwordEncoder.encodePassword( "foo" ) );
+ attributes.put( "givenName", "foo" );
+ context.createSubcontext( dn, attributes );
+ }
+
+ private String createDn( String cn )
+ {
+ return "cn=" + cn + "," + suffix;
+ }
+
+ private void assertExist( DirContext context, String dn, String attribute, String value )
+ throws NamingException
+ {
+ SearchControls ctls = new SearchControls();
+
+ ctls.setDerefLinkFlag( true );
+ ctls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
+ ctls.setReturningAttributes( new String[] { "*" } );
+
+ BasicAttributes matchingAttributes = new BasicAttributes();
+ matchingAttributes.put( attribute, value );
+ BasicAttribute objectClass = new BasicAttribute( "objectClass" );
+ objectClass.add( "inetOrgPerson" );
+ matchingAttributes.put( objectClass );
+
+ NamingEnumeration<SearchResult> results = context.search( suffix, matchingAttributes );
+ // NamingEnumeration<SearchResult> results = context.search( suffix, "(" + attribute + "=" + value + ")", ctls
+ // );
+
+ assertTrue( results.hasMoreElements() );
+ SearchResult result = results.nextElement();
+ Attributes attrs = result.getAttributes();
+ Attribute testAttr = attrs.get( attribute );
+ assertEquals( value, testAttr.get() );
+
+ }
+
+}
diff --git a/redback-users/redback-users-providers/redback-users-ldap/src/test/java/org/codehaus/plexus/redback/users/ldap/service/LdapCacheServiceTest.java b/redback-users/redback-users-providers/redback-users-ldap/src/test/java/org/codehaus/plexus/redback/users/ldap/service/LdapCacheServiceTest.java
new file mode 100644
index 000000000..00af774d1
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-ldap/src/test/java/org/codehaus/plexus/redback/users/ldap/service/LdapCacheServiceTest.java
@@ -0,0 +1,108 @@
+package org.codehaus.plexus.redback.users.ldap.service;
+
+/*
+ * Copyright 2011 The Codehaus.
+ *
+ * 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.
+ */
+
+import junit.framework.TestCase;
+import org.codehaus.plexus.redback.common.ldap.LdapUser;
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import javax.inject.Inject;
+
+/**
+ * @author: Maria Odea Ching <oching@apache.org>
+ * @version
+ */
+@RunWith( SpringJUnit4ClassRunner.class )
+@ContextConfiguration( locations = { "classpath*:/META-INF/spring-context.xml", "classpath*:/spring-context.xml" } )
+public class LdapCacheServiceTest
+ extends TestCase
+{
+ @Inject
+ private LdapCacheService ldapCacheService;
+
+ private static final String USERNAME = "dummy";
+
+ @After
+ public void tearDown()
+ throws Exception
+ {
+ ldapCacheService.removeAllUsers();
+ ldapCacheService.removeAllLdapUserDn();
+
+ super.tearDown();
+ }
+
+ @Test
+ public void testLdapUserDnCache()
+ throws Exception
+ {
+ ldapCacheService.addLdapUserDn( USERNAME, "userDn" );
+
+ assertNotNull( ldapCacheService.getLdapUserDn( USERNAME ) );
+
+ ldapCacheService.removeLdapUserDn( USERNAME );
+
+ assertNull( ldapCacheService.getLdapUserDn( USERNAME ) );
+ }
+
+ @Test
+ public void testClearLdapUserDnCache()
+ throws Exception
+ {
+ ldapCacheService.addLdapUserDn( USERNAME, "userDn" );
+
+ assertNotNull( ldapCacheService.getLdapUserDn( USERNAME ) );
+
+ ldapCacheService.removeLdapUserDn( USERNAME );
+
+ assertNull( ldapCacheService.getLdapUserDn( USERNAME ) );
+ }
+
+ @Test
+ public void testLdapUsersCache()
+ throws Exception
+ {
+ LdapUser ldapUser = new LdapUser( USERNAME );
+
+ ldapCacheService.addUser( ldapUser );
+
+ assertNotNull( ldapCacheService.getUser( USERNAME ) );
+
+ ldapCacheService.removeUser( USERNAME );
+
+ assertNull( ldapCacheService.getUser( USERNAME ) );
+ }
+
+ @Test
+ public void testClearLdapUsersCache()
+ throws Exception
+ {
+ LdapUser ldapUser = new LdapUser( USERNAME );
+
+ ldapCacheService.addUser( ldapUser );
+
+ assertNotNull( ldapCacheService.getUser( USERNAME ) );
+
+ ldapCacheService.removeAllUsers();
+
+ assertNull( ldapCacheService.getUser( USERNAME ) );
+ }
+}
diff --git a/redback-users/redback-users-providers/redback-users-ldap/src/test/resources/org/codehaus/plexus/redback/users/ldap/users.ldif b/redback-users/redback-users-providers/redback-users-ldap/src/test/resources/org/codehaus/plexus/redback/users/ldap/users.ldif
new file mode 100644
index 000000000..35723a4c3
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-ldap/src/test/resources/org/codehaus/plexus/redback/users/ldap/users.ldif
@@ -0,0 +1,21 @@
+dn: dc=redback,dc=plexus,dc=codehaus,dc=org
+cn: jesse
+givenName: foo
+userPassword: C+7Hteo/D9vJXQ3UfzxbwnXaijM=
+objectClass: top
+objectClass: inetorgperson
+objectClass: person
+objectClass: organizationalperson
+email: foo
+sn: foo
+
+dn: dc=redback,dc=plexus,dc=codehaus,dc=org
+cn: joakim
+givenName: foo
+userPassword: C+7Hteo/D9vJXQ3UfzxbwnXaijM=
+objectClass: top
+objectClass: inetorgperson
+objectClass: person
+objectClass: organizationalperson
+email: foo
+sn: foo
diff --git a/redback-users/redback-users-providers/redback-users-ldap/src/test/resources/spring-context.xml b/redback-users/redback-users-providers/redback-users-ldap/src/test/resources/spring-context.xml
new file mode 100644
index 000000000..9eb0fb261
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-ldap/src/test/resources/spring-context.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0"?>
+
+<!--
+ ~ 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.
+ -->
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+ http://www.springframework.org/schema/context
+ http://www.springframework.org/schema/context/spring-context-3.0.xsd">
+
+ <context:property-placeholder system-properties-mode="OVERRIDE"/>
+
+ <bean name="apacheDS#test" class="org.codehaus.redback.components.apacheds.DefaultApacheDs"
+ scope="prototype">
+ <property name="basedir" value="${basedir}/target/apacheds"/>
+ <property name="port" value="${ldapPort}"/>
+ <property name="enableNetworking" value="true"/>
+ <property name="password" value="secret"/>
+ </bean>
+
+ <bean name="ldapConnectionFactory#configurable" class="org.codehaus.plexus.redback.common.ldap.connection.ConfigurableLdapConnectionFactory">
+ <property name="hostname" value="localhost"/>
+ <property name="port" value="${ldapPort}"/>
+ <property name="baseDn" value="dc=redback,dc=plexus,dc=codehaus,dc=org"/>
+ <property name="contextFactory" value="com.sun.jndi.ldap.LdapCtxFactory"/>
+ <property name="password" value="secret"/>
+ <property name="bindDn" value="uid=admin,ou=system"/>
+ <property name="userConf" ref="userConfiguration"/>
+ </bean>
+
+ <bean name="userMapper#ldap" class="org.codehaus.plexus.redback.common.ldap.LdapUserMapper">
+ <property name="emailAttribute" value="mail"/>
+ <property name="fullNameAttribute" value="givenName"/>
+ <property name="passwordAttribute" value="userPassword"/>
+ <property name="userIdAttribute" value="cn"/>
+ <property name="userBaseDn" value="dc=redback,dc=plexus,dc=codehaus,dc=org"/>
+ <property name="userObjectClass" value="inetOrgPerson"/>
+ <property name="userConf" ref="userConfiguration"/>
+ </bean>
+
+</beans> \ No newline at end of file
diff --git a/redback-users/redback-users-providers/redback-users-memory/pom.xml b/redback-users/redback-users-providers/redback-users-memory/pom.xml
new file mode 100644
index 000000000..53d045898
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-memory/pom.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2006 The Codehaus.
+ ~
+ ~ 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.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.codehaus.redback</groupId>
+ <artifactId>redback-users-providers</artifactId>
+ <version>1.5-SNAPSHOT</version>
+ </parent>
+ <artifactId>redback-users-memory</artifactId>
+ <name>Redback :: Users Provider :: Memory</name>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.codehaus.redback</groupId>
+ <artifactId>redback-policy</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-context-support</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>javax.annotation</groupId>
+ <artifactId>jsr250-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-lang</groupId>
+ <artifactId>commons-lang</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.redback</groupId>
+ <artifactId>redback-users-tests</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/redback-users/redback-users-providers/redback-users-memory/src/main/java/org/codehaus/plexus/redback/users/memory/MemoryUserManager.java b/redback-users/redback-users-providers/redback-users-memory/src/main/java/org/codehaus/plexus/redback/users/memory/MemoryUserManager.java
new file mode 100644
index 000000000..111272166
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-memory/src/main/java/org/codehaus/plexus/redback/users/memory/MemoryUserManager.java
@@ -0,0 +1,332 @@
+package org.codehaus.plexus.redback.users.memory;
+
+/*
+ * Copyright 2001-2006 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.
+ */
+
+import org.codehaus.plexus.redback.policy.UserSecurityPolicy;
+import org.codehaus.plexus.redback.users.AbstractUserManager;
+import org.codehaus.plexus.redback.users.PermanentUserException;
+import org.codehaus.plexus.redback.users.User;
+import org.codehaus.plexus.redback.users.UserQuery;
+import org.codehaus.plexus.redback.users.UserManager;
+import org.codehaus.plexus.redback.users.UserNotFoundException;
+import org.codehaus.plexus.redback.users.memory.util.UserSorter;
+import org.apache.commons.lang.StringUtils;
+import org.springframework.stereotype.Service;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.annotation.Resource;
+
+/**
+ * @version $Id$
+ */
+@Service("userManager#memory")
+public class MemoryUserManager
+ extends AbstractUserManager
+ implements UserManager
+{
+ @Resource
+ private UserSecurityPolicy userSecurityPolicy;
+
+ public String getId()
+ {
+ Properties props = new Properties();
+ URL url = this
+ .getClass()
+ .getResource(
+ "META-INF/maven/org/codehaus/plexus/redback/redback-users-memory/pom.properties" );
+
+ if ( url != null )
+ {
+ try
+ {
+ props.load( url.openStream() );
+ return "MemoryUserManager - " + props.getProperty( "version" );
+ }
+ catch ( IOException e )
+ {
+ // Fall thru
+ }
+ }
+ return "MemoryUserManager - (unknown version)";
+ }
+
+ public boolean isReadOnly()
+ {
+ return false;
+ }
+
+ public UserQuery createUserQuery()
+ {
+ return new SimpleUserQuery();
+ }
+
+ public List<User> findUsersByQuery( UserQuery query )
+ {
+ SimpleUserQuery uq = (SimpleUserQuery) query;
+
+ List<User> list = new ArrayList<User>();
+
+ for ( Iterator<User> i = users.values().iterator(); i.hasNext(); )
+ {
+ SimpleUser user = (SimpleUser) i.next();
+ boolean matches = uq.matches( user );
+ if ( matches )
+ {
+ list.add( user );
+ }
+ }
+
+ Collections.sort( list, uq.getComparator() );
+
+ List<User> cutList = new ArrayList<User>();
+
+ for ( long i = query.getFirstResult();
+ i < list.size() && ( query.getMaxResults() == -1 || i < query.getFirstResult() + uq.getMaxResults() );
+ i++ )
+ {
+ cutList.add( list.get( (int) i ) );
+ }
+ return cutList;
+ }
+
+ private Map<Object, User> users = new HashMap<Object, User>();
+
+ public User addUser( User user )
+ {
+ saveUser( user );
+ fireUserManagerUserAdded( user );
+
+ // If there exists no encoded password, then this is a new user setup
+ if ( StringUtils.isEmpty( user.getEncodedPassword() ) )
+ {
+ userSecurityPolicy.extensionChangePassword( user );
+ }
+
+ return user;
+ }
+
+ private void saveUser( User user )
+ {
+ triggerInit();
+ users.put( user.getPrincipal(), user );
+ }
+
+ public User updateUser( User user )
+ {
+ return updateUser( user, false );
+ }
+
+ public User updateUser( User user, boolean passwordChangeRequired )
+ {
+ if ( StringUtils.isNotEmpty( user.getPassword() ) )
+ {
+ userSecurityPolicy.extensionChangePassword( user, passwordChangeRequired );
+ }
+
+ saveUser( user );
+
+ fireUserManagerUserUpdated( user );
+
+ return user;
+ }
+
+ public User findUser( Object principal )
+ throws UserNotFoundException
+ {
+ triggerInit();
+ User user = users.get( principal );
+
+ if ( user == null )
+ {
+ throw new UserNotFoundException( "Cannot find the user with the principal '" + principal + "'." );
+ }
+
+ return user;
+ }
+
+ public boolean userExists( Object principal )
+ {
+ try
+ {
+ findUser( principal );
+ return true;
+ }
+ catch ( UserNotFoundException ne )
+ {
+ return false;
+ }
+ }
+
+ public void deleteUser( Object principal )
+ throws UserNotFoundException
+ {
+ deleteUser( principal.toString() );
+ }
+
+ public User createUser( String username, String fullName, String emailAddress )
+ {
+ User user = new SimpleUser();
+ user.setUsername( username );
+ user.setFullName( fullName );
+ user.setEmail( emailAddress );
+
+ return user;
+ }
+
+ public void deleteUser( String username )
+ throws UserNotFoundException
+ {
+ User user = findUser( username );
+
+ if ( user.isPermanent() )
+ {
+ throw new PermanentUserException( "Cannot delete permanent user." );
+ }
+
+ users.remove( user.getPrincipal() );
+
+ fireUserManagerUserRemoved( user );
+ }
+
+ public void addUserUnchecked( User user )
+ {
+ addUser( user );
+ }
+
+ public void eraseDatabase()
+ {
+ users.clear();
+ }
+
+ public User findUser( String username )
+ throws UserNotFoundException
+ {
+ triggerInit();
+ User user = null;
+
+ Iterator<User> it = users.values().iterator();
+ while ( it.hasNext() )
+ {
+ User u = it.next();
+ if ( u.getUsername().equals( username ) )
+ {
+ user = u;
+ }
+ }
+
+ if ( user == null )
+ {
+ throw new UserNotFoundException( "Unable to find user '" + username + "'" );
+ }
+
+ return user;
+ }
+
+ public List<User> findUsersByUsernameKey( String usernameKey, boolean orderAscending )
+ {
+ triggerInit();
+
+ List<User> userList = new ArrayList<User>();
+
+ Iterator<User> it = users.values().iterator();
+ while ( it.hasNext() )
+ {
+ User u = it.next();
+ if ( u.getUsername().indexOf( usernameKey ) > -1 )
+ {
+ userList.add( u );
+ }
+ }
+
+ Collections.sort( userList, new UserSorter( orderAscending ) );
+
+ return userList;
+ }
+
+ public List<User> findUsersByFullNameKey( String fullNameKey, boolean orderAscending )
+ {
+ triggerInit();
+
+ List<User> userList = new ArrayList<User>();
+
+ Iterator<User> it = users.values().iterator();
+ while ( it.hasNext() )
+ {
+ User u = it.next();
+ if ( u.getFullName().indexOf( fullNameKey ) > -1 )
+ {
+ userList.add( u );
+ }
+ }
+
+ Collections.sort( userList, new UserSorter( orderAscending ) );
+
+ return userList;
+ }
+
+ public List<User> findUsersByEmailKey( String emailKey, boolean orderAscending )
+ {
+ triggerInit();
+
+ List<User> userList = new ArrayList<User>();
+
+ Iterator<User> it = users.values().iterator();
+ while ( it.hasNext() )
+ {
+ User u = it.next();
+ if ( u.getEmail().indexOf( emailKey ) > -1 )
+ {
+ userList.add( u );
+ }
+ }
+
+ Collections.sort( userList, new UserSorter( orderAscending ) );
+
+ return userList;
+ }
+
+ public List<User> getUsers()
+ {
+ triggerInit();
+ return new ArrayList<User>( users.values() );
+ }
+
+ public List<User> getUsers( boolean ascendingUsername )
+ {
+ return getUsers();
+ }
+
+ private boolean hasTriggeredInit = false;
+
+ public void triggerInit()
+ {
+ if ( !hasTriggeredInit )
+ {
+ fireUserManagerInit( users.isEmpty() );
+ hasTriggeredInit = true;
+ }
+ }
+}
diff --git a/redback-users/redback-users-providers/redback-users-memory/src/main/java/org/codehaus/plexus/redback/users/memory/SimpleUser.java b/redback-users/redback-users-providers/redback-users-memory/src/main/java/org/codehaus/plexus/redback/users/memory/SimpleUser.java
new file mode 100644
index 000000000..d473700ba
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-memory/src/main/java/org/codehaus/plexus/redback/users/memory/SimpleUser.java
@@ -0,0 +1,220 @@
+package org.codehaus.plexus.redback.users.memory;
+
+/*
+ * Copyright 2001-2006 The Codehaus.
+ *
+ * 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.
+ */
+
+import org.codehaus.plexus.redback.users.User;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * A Simple User record.
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ */
+public class SimpleUser
+ implements User, Serializable
+{
+ private String username;
+
+ private String password;
+
+ private String email;
+
+ private String fullName;
+
+ private String encodedPassword;
+
+ private Date lastPasswordChange;
+
+ private Date lastLoginDate;
+
+ private int countFailedLoginAttempts = 0;
+
+ private boolean locked = false;
+
+ private boolean permanent = false;
+
+ private boolean validated = false;
+
+ private List<String> previousEncodedPasswords;
+
+ private Date accountCreationDate;
+
+ private boolean passwordChangeRequired;
+
+ public SimpleUser()
+ {
+ // no op
+ }
+
+ public void addPreviousEncodedPassword( String encodedPassword )
+ {
+ getPreviousEncodedPasswords().add( encodedPassword );
+ }
+
+ public Date getAccountCreationDate()
+ {
+ return accountCreationDate;
+ }
+
+ public int getCountFailedLoginAttempts()
+ {
+ return countFailedLoginAttempts;
+ }
+
+ public String getEmail()
+ {
+ return email;
+ }
+
+ public String getEncodedPassword()
+ {
+ return encodedPassword;
+ }
+
+ public String getFullName()
+ {
+ return fullName;
+ }
+
+ public Date getLastLoginDate()
+ {
+ return lastLoginDate;
+ }
+
+ public Date getLastPasswordChange()
+ {
+ return lastPasswordChange;
+ }
+
+ public String getPassword()
+ {
+ return password;
+ }
+
+ public List<String> getPreviousEncodedPasswords()
+ {
+ if ( previousEncodedPasswords == null )
+ {
+ previousEncodedPasswords = new ArrayList<String>();
+ }
+ return previousEncodedPasswords;
+ }
+
+ public Object getPrincipal()
+ {
+ return username;
+ }
+
+ public String getUsername()
+ {
+ return username;
+ }
+
+ public boolean isLocked()
+ {
+ return locked;
+ }
+
+ public void setAccountCreationDate( Date accountCreationDate )
+ {
+ this.accountCreationDate = accountCreationDate;
+ }
+
+ public void setCountFailedLoginAttempts( int countFailedLoginAttempts )
+ {
+ this.countFailedLoginAttempts = countFailedLoginAttempts;
+ }
+
+ public void setEmail( String email )
+ {
+ this.email = email;
+ }
+
+ public void setEncodedPassword( String encodedPassword )
+ {
+ this.encodedPassword = encodedPassword;
+ }
+
+ public void setFullName( String fullName )
+ {
+ this.fullName = fullName;
+ }
+
+ public void setLastLoginDate( Date lastLoginDate )
+ {
+ this.lastLoginDate = lastLoginDate;
+ }
+
+ public void setLastPasswordChange( Date lastPasswordChange )
+ {
+ this.lastPasswordChange = lastPasswordChange;
+ }
+
+ public void setLocked( boolean locked )
+ {
+ this.locked = locked;
+ }
+
+ public void setPassword( String password )
+ {
+ this.password = password;
+ }
+
+ public void setPreviousEncodedPasswords( List<String> previousEncodedPasswords )
+ {
+ this.previousEncodedPasswords = previousEncodedPasswords;
+ }
+
+ public void setUsername( String username )
+ {
+ this.username = username;
+ }
+
+ public boolean isPasswordChangeRequired()
+ {
+ return passwordChangeRequired;
+ }
+
+ public void setPasswordChangeRequired( boolean passwordChangeRequired )
+ {
+ this.passwordChangeRequired = passwordChangeRequired;
+ }
+
+ public boolean isPermanent()
+ {
+ return permanent;
+ }
+
+ public void setPermanent( boolean permanent )
+ {
+ this.permanent = permanent;
+ }
+
+ public boolean isValidated()
+ {
+ return validated;
+ }
+
+ public void setValidated( boolean validated )
+ {
+ this.validated = validated;
+ }
+}
diff --git a/redback-users/redback-users-providers/redback-users-memory/src/main/java/org/codehaus/plexus/redback/users/memory/SimpleUserQuery.java b/redback-users/redback-users-providers/redback-users-memory/src/main/java/org/codehaus/plexus/redback/users/memory/SimpleUserQuery.java
new file mode 100644
index 000000000..2891b858c
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-memory/src/main/java/org/codehaus/plexus/redback/users/memory/SimpleUserQuery.java
@@ -0,0 +1,91 @@
+package org.codehaus.plexus.redback.users.memory;
+
+/*
+ * Copyright 2001-2006 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.
+ */
+
+import org.codehaus.plexus.redback.users.AbstractUserQuery;
+import org.codehaus.plexus.redback.users.User;
+
+import java.util.Comparator;
+
+public class SimpleUserQuery
+ extends AbstractUserQuery
+{
+
+ /**
+ * Returns true if this user should be considered a match of the current query
+ *
+ * @param user
+ * @return
+ */
+ public boolean matches( User user )
+ {
+ if ( getUsername() != null && user.getUsername() != null &&
+ user.getUsername().toLowerCase().indexOf( getUsername().toLowerCase() ) == -1 )
+ {
+ return false;
+ }
+ else if ( getFullName() != null && user.getFullName() != null &&
+ user.getFullName().toLowerCase().indexOf( getFullName().toLowerCase() ) == -1 )
+ {
+ return false;
+ }
+ else if ( getEmail() != null && user.getEmail() != null &&
+ user.getEmail().toLowerCase().indexOf( getEmail().toLowerCase() ) == -1 )
+ {
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+
+ }
+
+ /**
+ * Returns a comparator used for sorting a collection of User objects based on the ordering set
+ * on this UserQuery's {@link #setOrderBy(String)} and {@link #setAscending(boolean)}.
+ * @return
+ */
+ public Comparator<User> getComparator()
+ {
+ return new Comparator<User>()
+ {
+ public int compare( User user1, User user2 )
+ {
+ return ( isAscending() ? 1 : -1 ) * compareUsers( user1, user2 );
+ }
+ };
+ }
+
+ private int compareUsers( User user, User user1 )
+ {
+ if ( ORDER_BY_EMAIL.equals( getOrderBy() ) )
+ {
+ return user.getEmail() == null ? -1
+ : user1.getEmail() == null ? 1 : user.getEmail().compareTo( user1.getEmail() );
+ }
+ else if ( ORDER_BY_FULLNAME.equals( getOrderBy() ) )
+ {
+ return user.getFullName() == null ? -1
+ : user1.getFullName() == null ? 1 : user.getFullName().compareTo( user1.getFullName() );
+ }
+ else
+ {
+ return user.getUsername().compareTo( user1.getUsername() );
+ }
+ }
+}
diff --git a/redback-users/redback-users-providers/redback-users-memory/src/main/java/org/codehaus/plexus/redback/users/memory/util/UserSorter.java b/redback-users/redback-users-providers/redback-users-memory/src/main/java/org/codehaus/plexus/redback/users/memory/util/UserSorter.java
new file mode 100644
index 000000000..e1183bac8
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-memory/src/main/java/org/codehaus/plexus/redback/users/memory/util/UserSorter.java
@@ -0,0 +1,69 @@
+package org.codehaus.plexus.redback.users.memory.util;
+
+import org.codehaus.plexus.redback.users.User;
+
+import java.util.Comparator;
+
+/**
+ * UserSorter
+ */
+public class UserSorter
+ implements Comparator<User>
+{
+ private boolean ascending;
+
+ public UserSorter()
+ {
+ this.ascending = true;
+ }
+
+ public UserSorter( boolean ascending )
+ {
+ this.ascending = ascending;
+ }
+
+ public int compare( User o1, User o2 )
+ {
+ if ( ( o1 == null ) && ( o2 == null ) )
+ {
+ return 0;
+ }
+
+ if ( ( o1 == null ) && ( o2 != null ) )
+ {
+ return -1;
+ }
+
+ if ( ( o1 != null ) && ( o2 != null ) )
+ {
+ return 1;
+ }
+
+ User u1 = null;
+ User u2 = null;
+
+ if ( isAscending() )
+ {
+ u1 = o1;
+ u2 = o2;
+ }
+ else
+ {
+ u1 = o2;
+ u2 = o1;
+ }
+
+ return u1.getUsername().compareTo( u2.getUsername() );
+ }
+
+ public boolean isAscending()
+ {
+ return ascending;
+ }
+
+ public void setAscending( boolean ascending )
+ {
+ this.ascending = ascending;
+ }
+
+}
diff --git a/redback-users/redback-users-providers/redback-users-memory/src/main/resources/META-INF/spring-context.xml b/redback-users/redback-users-providers/redback-users-memory/src/main/resources/META-INF/spring-context.xml
new file mode 100644
index 000000000..9694a8829
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-memory/src/main/resources/META-INF/spring-context.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+
+<!--
+ ~ 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.
+ -->
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+ http://www.springframework.org/schema/context
+ http://www.springframework.org/schema/context/spring-context-3.0.xsd"
+ default-lazy-init="true">
+
+ <context:annotation-config />
+ <context:component-scan base-package="org.codehaus.plexus.redback.users.memory"/>
+
+</beans> \ No newline at end of file
diff --git a/redback-users/redback-users-providers/redback-users-memory/src/test/java/org/codehaus/plexus/redback/users/MemoryUserManagerTest.java b/redback-users/redback-users-providers/redback-users-memory/src/test/java/org/codehaus/plexus/redback/users/MemoryUserManagerTest.java
new file mode 100644
index 000000000..7679b8287
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-memory/src/test/java/org/codehaus/plexus/redback/users/MemoryUserManagerTest.java
@@ -0,0 +1,45 @@
+package org.codehaus.plexus.redback.users;
+
+/*
+ * Copyright 2005 The Codehaus.
+ *
+ * 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.
+ */
+
+import org.codehaus.plexus.redback.users.memory.MemoryUserManager;
+import org.codehaus.plexus.redback.users.provider.test.AbstractUserManagerTestCase;
+import org.junit.Before;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * {@link MemoryUserManager} test:
+ *
+ * @author <a href='mailto:rahul.thakur.xdev@gmail.com'>Rahul Thakur</a>
+ */
+public class MemoryUserManagerTest
+ extends AbstractUserManagerTestCase
+{
+
+ @Inject @Named(value = "userManager#memory")
+ UserManager userManager;
+
+ @Before
+ public void setUp()
+ throws Exception
+ {
+ super.setUp();
+ setUserManager( userManager );
+ }
+}
diff --git a/redback-users/redback-users-tests/pom.xml b/redback-users/redback-users-tests/pom.xml
new file mode 100644
index 000000000..4ed0ffc21
--- /dev/null
+++ b/redback-users/redback-users-tests/pom.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2006 The Codehaus.
+ ~
+ ~ 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.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.codehaus.redback</groupId>
+ <artifactId>redback-users</artifactId>
+ <version>1.5-SNAPSHOT</version>
+ </parent>
+ <artifactId>redback-users-tests</artifactId>
+ <name>Redback :: User Tests</name>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.codehaus.redback</groupId>
+ <artifactId>redback-policy</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.plexus</groupId>
+ <artifactId>plexus-utils</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-context-support</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-test</artifactId>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/redback-users/redback-users-tests/src/main/java/org/codehaus/plexus/redback/users/provider/test/AbstractUserManagerTestCase.java b/redback-users/redback-users-tests/src/main/java/org/codehaus/plexus/redback/users/provider/test/AbstractUserManagerTestCase.java
new file mode 100644
index 000000000..faf5c50aa
--- /dev/null
+++ b/redback-users/redback-users-tests/src/main/java/org/codehaus/plexus/redback/users/provider/test/AbstractUserManagerTestCase.java
@@ -0,0 +1,609 @@
+package org.codehaus.plexus.redback.users.provider.test;
+
+/*
+ * Copyright 2001-2006 The Codehaus.
+ *
+ * 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.
+ */
+
+import junit.framework.TestCase;
+import org.codehaus.plexus.redback.policy.UserSecurityPolicy;
+import org.codehaus.plexus.redback.users.PermanentUserException;
+import org.codehaus.plexus.redback.users.User;
+import org.codehaus.plexus.redback.users.UserManager;
+import org.codehaus.plexus.redback.users.UserNotFoundException;
+import org.codehaus.plexus.redback.users.UserQuery;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import javax.inject.Inject;
+import java.util.List;
+
+/**
+ * AbstractUserManagerTestCase
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id$
+ */
+@RunWith( SpringJUnit4ClassRunner.class )
+@ContextConfiguration( locations = {"classpath*:/META-INF/spring-context.xml","classpath*:/spring-context.xml"} )
+public class AbstractUserManagerTestCase
+ extends TestCase
+{
+ /**
+ * This value is set by the sub classes of this test case.
+ * They should override .setUp() and inject this value via
+ * the {@link #setUserManager(UserManager)} method call.
+ */
+ private UserManager userManager;
+
+ @Inject
+ private UserSecurityPolicy securityPolicy;
+
+ private UserManagerEventTracker eventTracker;
+
+ public UserManager getUserManager()
+ {
+ return userManager;
+ }
+
+ public void setUserManager( UserManager um )
+ {
+ this.userManager = um;
+ if ( this.userManager != null )
+ {
+ this.eventTracker = new UserManagerEventTracker();
+ this.userManager.addUserManagerListener( this.eventTracker );
+ }
+ }
+
+ public void setUp()
+ throws Exception
+ {
+ super.setUp();
+ }
+
+ public void tearDown()
+ throws Exception
+ {
+ super.tearDown();
+ }
+
+ protected void assertCleanUserManager()
+ {
+
+ getUserManager().eraseDatabase();
+ getEventTracker().userManagerInit( true );
+ assertNotNull( getUserManager() );
+
+ assertEquals( "New UserManager should contain no users. " + userManager.getUsers(), 0, userManager.getUsers().size() );
+ }
+
+ @Test
+ public void testFindUserByNullPrincipal()
+ {
+ try
+ {
+ Object obj = null;
+ getUserManager().findUser( obj );
+ fail( "findUser() with null Object Should have thrown a UserNotFoundException." );
+ }
+ catch ( UserNotFoundException e )
+ {
+ // Expected Path.
+ }
+ }
+
+ @Test
+ public void testFindUserByEmptyUsername()
+ {
+ try
+ {
+ String username = null;
+ getUserManager().findUser( username );
+ fail( "findUser() with null username Should have thrown a UserNotFoundException." );
+ }
+ catch ( UserNotFoundException e )
+ {
+ // Expected Path.
+ }
+
+ try
+ {
+ String username = "";
+ getUserManager().findUser( username );
+ fail( "findUser() with empty username Should have thrown a UserNotFoundException." );
+ }
+ catch ( UserNotFoundException e )
+ {
+ // Expected Path.
+ }
+
+ try
+ {
+ String username = " ";
+ getUserManager().findUser( username );
+ fail( "findUser() with all whitespace username Should have thrown a UserNotFoundException." );
+ }
+ catch ( UserNotFoundException e )
+ {
+ // Expected Path.
+ }
+ }
+
+ @Test
+ public void testAddFindUserByPrincipal()
+ throws UserNotFoundException
+ {
+ assertCleanUserManager();
+ securityPolicy.setEnabled( false );
+
+ User smcqueen = getUserManager().createUser( "smcqueen", "Steve McQueen", "the cooler king" );
+
+ /* Keep a reference to the object that was added.
+ * Since it has the actual principal that was managed by jpox/jdo.
+ */
+ User added = userManager.addUser( smcqueen );
+
+ assertEquals( 1, userManager.getUsers().size() );
+
+ /* Fetch user from userManager using principal returned earlier */
+ User actual = userManager.findUser( added.getPrincipal() );
+ assertEquals( added, actual );
+
+ /* Check into the event tracker. */
+ assertEquals( 1, getEventTracker().countInit );
+ assertNotNull( getEventTracker().lastDbFreshness );
+ assertTrue( getEventTracker().lastDbFreshness.booleanValue() );
+
+ assertEquals( 1, getEventTracker().addedUsernames.size() );
+ assertEquals( 0, getEventTracker().removedUsernames.size() );
+ assertEquals( 0, getEventTracker().updatedUsernames.size() );
+ }
+
+ @Test
+ public void testAddFindUserLockedStatus() throws UserNotFoundException {
+ assertCleanUserManager();
+ securityPolicy.setEnabled(false);
+
+ User smcqueen = getUserManager().createUser("smcqueen", "Steve McQueen", "the cooler king");
+
+ smcqueen.setLocked( true );
+
+ /*
+ * Keep a reference to the object that was added. Since it has the
+ * actual principal that was managed by jpox/jdo.
+ */
+ User added = userManager.addUser( smcqueen );
+
+ assertTrue( added.isLocked() );
+
+ assertEquals(1, userManager.getUsers().size());
+
+ /* Fetch user from userManager using principal returned earlier */
+ User actual = userManager.findUser(added.getPrincipal());
+ assertEquals(added, actual);
+
+ assertTrue( actual.isLocked() );
+
+ /* Check into the event tracker. */
+ assertEquals(1, getEventTracker().countInit);
+ assertNotNull(getEventTracker().lastDbFreshness);
+ assertTrue(getEventTracker().lastDbFreshness.booleanValue());
+
+ assertEquals(1, getEventTracker().addedUsernames.size());
+ assertEquals(0, getEventTracker().removedUsernames.size());
+ assertEquals(0, getEventTracker().updatedUsernames.size());
+ }
+
+ @Test
+ public void testAddFindUserByUsername()
+ throws UserNotFoundException
+ {
+ assertCleanUserManager();
+ securityPolicy.setEnabled( false );
+
+ User smcqueen = getUserManager().createUser( "smcqueen", "Steve McQueen", "the cooler king" );
+
+ User added = userManager.addUser( smcqueen );
+
+ assertEquals( 1, userManager.getUsers().size() );
+
+ User actual = userManager.findUser( "smcqueen" );
+ assertEquals( added, actual );
+
+ /* Check into the event tracker. */
+ assertEquals( 1, getEventTracker().countInit );
+ assertNotNull( getEventTracker().lastDbFreshness );
+ assertTrue( getEventTracker().lastDbFreshness.booleanValue() );
+
+ assertEquals( 1, getEventTracker().addedUsernames.size() );
+ assertEquals( 0, getEventTracker().removedUsernames.size() );
+ assertEquals( 0, getEventTracker().updatedUsernames.size() );
+ }
+
+ @Test
+ public void testCreateUser()
+ throws Exception
+ {
+ assertCleanUserManager();
+ securityPolicy.setEnabled( false );
+
+ UserManager um = getUserManager();
+ User user = um.createUser( "admin", "Administrator", "admin@somedomain.com" );
+ user.setPassword( "adminpass" );
+ um.addUser( user );
+
+ assertEquals( 1, um.getUsers().size() );
+
+ /* Check into the event tracker. */
+ assertEquals( 1, getEventTracker().countInit );
+ assertNotNull( getEventTracker().lastDbFreshness );
+ assertTrue( getEventTracker().lastDbFreshness.booleanValue() );
+
+ assertEquals( 1, getEventTracker().addedUsernames.size() );
+ assertEquals( 0, getEventTracker().removedUsernames.size() );
+ assertEquals( 0, getEventTracker().updatedUsernames.size() );
+ }
+
+ @Test
+ public void testAddUser()
+ throws Exception
+ {
+ assertCleanUserManager();
+ securityPolicy.setEnabled( false );
+
+ UserManager um = getUserManager();
+ assertNotNull( um.getUsers() );
+ assertEquals( 0, um.getUsers().size() );
+
+ User user = um.createUser( "tommy123", "Tommy Traddles", "tommy.traddles@somedomain.com" );
+ user.setPassword( "hillybilly" );
+ um.addUser( user );
+
+ assertNotNull( um.getUsers() );
+ assertEquals( 1, um.getUsers().size() );
+
+ /* Check into the event tracker. */
+ assertEquals( 1, getEventTracker().countInit );
+ assertNotNull( getEventTracker().lastDbFreshness );
+ assertTrue( getEventTracker().lastDbFreshness.booleanValue() );
+
+ assertEquals( 1, getEventTracker().addedUsernames.size() );
+ assertEquals( 0, getEventTracker().removedUsernames.size() );
+ assertEquals( 0, getEventTracker().updatedUsernames.size() );
+ }
+
+ @Test
+ public void testDeleteUser()
+ throws Exception
+ {
+ assertCleanUserManager();
+ securityPolicy.setEnabled( false );
+
+ UserManager um = getUserManager();
+ User user = um.createUser( "admin", "Administrator", "admin@somedomain.com" );
+ user.setPassword( "adminpass" );
+ um.addUser( user );
+
+ assertEquals( 1, um.getUsers().size() );
+
+ um.deleteUser( user.getPrincipal() );
+ assertEquals( 0, um.getUsers().size() );
+
+ // attempt finding a non-existent user
+ try
+ {
+ um.findUser( "admin" );
+ fail( "Expected UserNotFoundException!" );
+ }
+ catch ( UserNotFoundException e )
+ {
+ // do nothing, expected!
+ }
+
+ /* Check into the event tracker. */
+ assertEquals( 1, getEventTracker().countInit );
+ assertNotNull( getEventTracker().lastDbFreshness );
+ assertTrue( getEventTracker().lastDbFreshness.booleanValue() );
+
+ assertEquals( 1, getEventTracker().addedUsernames.size() );
+ assertEquals( 1, getEventTracker().removedUsernames.size() );
+ assertEquals( 0, getEventTracker().updatedUsernames.size() );
+ }
+
+ @Test
+ public void testFindUser()
+ throws Exception
+ {
+ assertCleanUserManager();
+ securityPolicy.setEnabled( false );
+
+ UserManager um = getUserManager();
+
+ // create and add a few users
+ User u1 = um.createUser( "admin", "Administrator", "admin@somedomain.com" );
+ u1.setPassword( "adminpass" );
+ um.addUser( u1 );
+
+ u1 = um.createUser( "administrator", "Administrator User", "administrator@somedomain.com" );
+ u1.setPassword( "password" );
+ um.addUser( u1 );
+
+ u1 = um.createUser( "root", "Root User", "root@somedomain.com" );
+ u1.setPassword( "rootpass" );
+ um.addUser( u1 );
+
+ assertEquals( 3, um.getUsers().size() );
+
+ // find an existing user
+ User user = um.findUser( "root" );
+ assertNotNull( user );
+ assertEquals( "root@somedomain.com", user.getEmail() );
+ assertEquals( "root", user.getPrincipal() );
+ assertEquals( "Root User", user.getFullName() );
+ // test if the plain string password is encoded and NULL'ified
+ assertNull( user.getPassword() );
+ // test if encoded password was as expected
+ assertTrue( securityPolicy.getPasswordEncoder().isPasswordValid( user.getEncodedPassword(), "rootpass" ) );
+
+ // attempt finding a non-existent user
+ try
+ {
+ um.findUser( "non-existent" );
+ fail( "Expected UserNotFoundException!" );
+ }
+ catch ( UserNotFoundException e )
+ {
+ // do nothing, expected!
+ }
+
+ /* Check into the event tracker. */
+ assertEquals( 1, getEventTracker().countInit );
+ assertNotNull( getEventTracker().lastDbFreshness );
+ assertTrue( getEventTracker().lastDbFreshness.booleanValue() );
+
+ assertEquals( 3, getEventTracker().addedUsernames.size() );
+ assertEquals( 0, getEventTracker().removedUsernames.size() );
+ assertEquals( 0, getEventTracker().updatedUsernames.size() );
+ }
+
+ @Test
+ public void testFindUsersByQuery()
+ {
+ assertCleanUserManager();
+ securityPolicy.setEnabled( false );
+
+ UserManager um = getUserManager();
+
+ // create and add a few users
+ User u1 = um.createUser( "admin", "Administrator", "admin@somedomain.com" );
+ u1.setPassword( "adminpass" );
+ um.addUser( u1 );
+
+ u1 = um.createUser( "administrator", "Administrator User", "administrator@somedomain.com" );
+ u1.setPassword( "password" );
+ um.addUser( u1 );
+
+ u1 = um.createUser( "root", "Root User", "root@somedomain.com" );
+ u1.setPassword( "rootpass" );
+ um.addUser( u1 );
+
+ assertEquals( 3, um.getUsers().size() );
+
+ // Query by username
+ UserQuery query = um.createUserQuery();
+ query.setUsername( "Admin" );
+ assertEquals( 2, um.findUsersByQuery( query ).size() );
+
+ // Query by full name
+ query = um.createUserQuery();
+ query.setFullName( "Admin" );
+ assertEquals( 2, um.findUsersByQuery( query ).size() );
+
+ query = um.createUserQuery();
+ query.setFullName( "Administrator" );
+ assertEquals( 2, um.findUsersByQuery( query ).size() );
+
+ query = um.createUserQuery();
+ query.setFullName( "r User" );
+ assertEquals( 1, um.findUsersByQuery( query ).size() );
+
+ // Query by user name
+ query = um.createUserQuery();
+ query.setEmail( "somedomain" );
+ assertEquals( 3, um.findUsersByQuery( query ).size() );
+
+ // Query by email
+ query = um.createUserQuery();
+ query.setEmail( "root@" );
+ assertEquals( 1, um.findUsersByQuery( query ).size() );
+
+ // Query by illegal property
+ query = um.createUserQuery();
+ try
+ {
+ query.setOrderBy( "unknownString" );
+ fail( "Expected IllegalArgumentException" );
+ }
+ catch ( IllegalArgumentException e )
+ {
+
+ }
+
+ // Query with default ordering ascending
+ query = um.createUserQuery();
+ query.setOrderBy( UserQuery.ORDER_BY_EMAIL );
+ List<User> users = um.findUsersByQuery( query );
+ assertEquals( 3, users.size() );
+ assertEquals( "admin@somedomain.com", ( (User) users.get( 0 ) ).getEmail() );
+
+ // Query with ordering ascending
+ query = um.createUserQuery();
+ query.setOrderBy( UserQuery.ORDER_BY_EMAIL );
+ query.setAscending( false );
+ users = um.findUsersByQuery( query );
+ assertEquals( 3, users.size() );
+ assertEquals( "root@somedomain.com", ( (User) users.get( 0 ) ).getEmail() );
+
+ // Query with ordering descending, max 2 results
+ query = um.createUserQuery();
+ query.setOrderBy( UserQuery.ORDER_BY_EMAIL );
+ query.setAscending( false );
+ query.setMaxResults( 2 );
+ users = um.findUsersByQuery( query );
+ assertEquals( 2, users.size() );
+ assertEquals( "root@somedomain.com", ( (User) users.get( 0 ) ).getEmail() );
+
+ // Query with ordering ascending, max 2 results, first result = 2 so only one result
+ query = um.createUserQuery();
+ query.setOrderBy( UserQuery.ORDER_BY_EMAIL );
+ query.setAscending( false );
+ query.setMaxResults( 2 );
+ query.setFirstResult( 2 );
+ users = um.findUsersByQuery( query );
+ assertEquals( 1, users.size() );
+ assertEquals( "admin@somedomain.com", ( (User) users.get( 0 ) ).getEmail() );
+
+ // Query on more than one field
+ query = um.createUserQuery();
+ query.setOrderBy( UserQuery.ORDER_BY_EMAIL );
+ query.setFullName( "admin" );
+ query.setEmail( "admin@" );
+ users = um.findUsersByQuery( query );
+ assertEquals( 1, users.size() );
+ assertEquals( "admin@somedomain.com", ( (User) users.get( 0 ) ).getEmail() );
+ }
+
+ @Test
+ public void testUserExists()
+ throws Exception
+ {
+ assertCleanUserManager();
+ securityPolicy.setEnabled( false );
+
+ UserManager um = getUserManager();
+
+ // create and add a few users
+ User u1 = um.createUser( "admin", "Administrator", "admin@somedomain.com" );
+ u1.setPassword( "adminpass" );
+ um.addUser( u1 );
+
+ assertTrue( um.userExists( "admin" ) );
+ assertFalse( um.userExists( "voodoohatrack" ) );
+
+ /* Check into the event tracker. */
+ assertEquals( 1, getEventTracker().countInit );
+ assertNotNull( getEventTracker().lastDbFreshness );
+ assertTrue( getEventTracker().lastDbFreshness.booleanValue() );
+
+ assertEquals( 1, getEventTracker().addedUsernames.size() );
+ assertEquals( 0, getEventTracker().removedUsernames.size() );
+ assertEquals( 0, getEventTracker().updatedUsernames.size() );
+ }
+
+ @Test
+ public void testUpdateUser()
+ throws Exception
+ {
+ assertCleanUserManager();
+ securityPolicy.setEnabled( false );
+
+ UserManager um = getUserManager();
+
+ // create and add a user
+ User u1 = um.createUser( "root", "Root User", "root@somedomain.com" );
+ u1.setPassword( "rootpass" );
+ u1 = um.addUser( u1 );
+
+ // find user
+ User user = um.findUser( "root" );
+ assertNotNull( user );
+ assertEquals( u1, user );
+
+ user.setEmail( "superuser@somedomain.com" );
+ user.setPassword( "superpass" );
+ user.setFullName( "Super User" );
+
+ um.updateUser( user );
+
+ // find updated user
+ user = um.findUser( "root" );
+ assertNotNull( user );
+ assertEquals( "superuser@somedomain.com", user.getEmail() );
+ assertEquals( "Super User", user.getFullName() );
+ assertTrue( securityPolicy.getPasswordEncoder().isPasswordValid( user.getEncodedPassword(), "superpass" ) );
+
+ /* Check into the event tracker. */
+ assertEquals( 1, getEventTracker().countInit );
+ assertNotNull( getEventTracker().lastDbFreshness );
+ assertTrue( getEventTracker().lastDbFreshness.booleanValue() );
+
+ assertEquals( 1, getEventTracker().addedUsernames.size() );
+ assertEquals( 0, getEventTracker().removedUsernames.size() );
+ assertEquals( 1, getEventTracker().updatedUsernames.size() );
+ }
+
+ @Test
+ public void testDeletePermanentUser()
+ throws UserNotFoundException
+ {
+ assertCleanUserManager();
+ securityPolicy.setEnabled( false );
+
+ UserManager um = getUserManager();
+ User user = um.createUser( "admin", "Administrator", "admin@somedomain.com" );
+ user.setPassword( "adminpass" );
+ user.setPermanent( true );
+ user = um.addUser( user );
+
+ assertEquals( 1, um.getUsers().size() );
+
+ try
+ {
+ um.deleteUser( user.getPrincipal() );
+ fail("Deleting permanent user shold throw PermanentUserException.");
+ } catch( PermanentUserException e )
+ {
+ // do nothing, expected route.
+ }
+
+ assertEquals( 1, um.getUsers().size() );
+
+ // attempt to finding user
+ User admin = um.findUser( "admin" );
+ assertNotNull( admin );
+ assertEquals( user.getEmail(), admin.getEmail() );
+ assertEquals( user.getFullName(), admin.getFullName() );
+
+ /* Check into the event tracker. */
+ assertEquals( 1, getEventTracker().countInit );
+ assertNotNull( getEventTracker().lastDbFreshness );
+ assertTrue( getEventTracker().lastDbFreshness.booleanValue() );
+
+ assertEquals( 1, getEventTracker().addedUsernames.size() );
+ assertEquals( 0, getEventTracker().removedUsernames.size() );
+ assertEquals( 0, getEventTracker().updatedUsernames.size() ); }
+
+ public UserManagerEventTracker getEventTracker()
+ {
+ return eventTracker;
+ }
+
+ public void setEventTracker( UserManagerEventTracker eventTracker )
+ {
+ this.eventTracker = eventTracker;
+ }
+}
diff --git a/redback-users/redback-users-tests/src/main/java/org/codehaus/plexus/redback/users/provider/test/UserManagerEventTracker.java b/redback-users/redback-users-tests/src/main/java/org/codehaus/plexus/redback/users/provider/test/UserManagerEventTracker.java
new file mode 100644
index 000000000..66367c980
--- /dev/null
+++ b/redback-users/redback-users-tests/src/main/java/org/codehaus/plexus/redback/users/provider/test/UserManagerEventTracker.java
@@ -0,0 +1,72 @@
+package org.codehaus.plexus.redback.users.provider.test;
+
+/*
+ * Copyright 2001-2006 The Codehaus.
+ *
+ * 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.
+ */
+
+import org.codehaus.plexus.redback.users.User;
+import org.codehaus.plexus.redback.users.UserManagerListener;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * UserManagerEventTracker
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id$
+ */
+public class UserManagerEventTracker
+ implements UserManagerListener
+{
+ public long countInit = 0;
+
+ public Boolean lastDbFreshness;
+
+ public List<String> addedUsernames = new ArrayList<String>();
+
+ public List<String> removedUsernames = new ArrayList<String>();
+
+ public List<String> updatedUsernames = new ArrayList<String>();
+
+ public void userManagerInit( boolean freshDatabase )
+ {
+ countInit++;
+ lastDbFreshness = Boolean.valueOf( freshDatabase );
+ }
+
+ private void addUniqueUsername( List<String> list, User user )
+ {
+ if ( !list.contains( user.getUsername() ) )
+ {
+ list.add( user.getUsername() );
+ }
+ }
+
+ public void userManagerUserAdded( User user )
+ {
+ addUniqueUsername( addedUsernames, user );
+ }
+
+ public void userManagerUserRemoved( User user )
+ {
+ addUniqueUsername( removedUsernames, user );
+ }
+
+ public void userManagerUserUpdated( User user )
+ {
+ addUniqueUsername( updatedUsernames, user );
+ }
+}