Moved over some of the security stuff from Photos.
authorErik Brakkee <erik@brakkee.org>
Sat, 22 Mar 2008 16:32:13 +0000 (16:32 +0000)
committerErik Brakkee <erik@brakkee.org>
Sat, 22 Mar 2008 16:32:13 +0000 (16:32 +0000)
90 files changed:
.classpath
pom.xml
security/pom.xml [new file with mode: 0644]
security/src/main/java/org/wamblee/security/authorization/AllOperation.java [new file with mode: 0644]
security/src/main/java/org/wamblee/security/authorization/AnyUserCondition.java [new file with mode: 0644]
security/src/main/java/org/wamblee/security/authorization/AuthorizationException.java [new file with mode: 0644]
security/src/main/java/org/wamblee/security/authorization/AuthorizationInitializer.java [new file with mode: 0644]
security/src/main/java/org/wamblee/security/authorization/AuthorizationResult.java [new file with mode: 0644]
security/src/main/java/org/wamblee/security/authorization/AuthorizationRule.java [new file with mode: 0644]
security/src/main/java/org/wamblee/security/authorization/AuthorizationService.java [new file with mode: 0644]
security/src/main/java/org/wamblee/security/authorization/CreateOperation.java [new file with mode: 0644]
security/src/main/java/org/wamblee/security/authorization/DefaultAuthorizationService.java [new file with mode: 0644]
security/src/main/java/org/wamblee/security/authorization/DefaultOperationRegistry.java [new file with mode: 0644]
security/src/main/java/org/wamblee/security/authorization/DeleteOperation.java [new file with mode: 0644]
security/src/main/java/org/wamblee/security/authorization/GroupUserCondition.java [new file with mode: 0644]
security/src/main/java/org/wamblee/security/authorization/IsaOperationCondition.java [new file with mode: 0644]
security/src/main/java/org/wamblee/security/authorization/Operation.java [new file with mode: 0644]
security/src/main/java/org/wamblee/security/authorization/OperationCondition.java [new file with mode: 0644]
security/src/main/java/org/wamblee/security/authorization/OperationRegistry.java [new file with mode: 0644]
security/src/main/java/org/wamblee/security/authorization/PathCondition.java [new file with mode: 0644]
security/src/main/java/org/wamblee/security/authorization/ReadOperation.java [new file with mode: 0644]
security/src/main/java/org/wamblee/security/authorization/RegexpPathCondition.java [new file with mode: 0644]
security/src/main/java/org/wamblee/security/authorization/StartsWithPathCondition.java [new file with mode: 0644]
security/src/main/java/org/wamblee/security/authorization/UrlAuthorizationRule.java [new file with mode: 0644]
security/src/main/java/org/wamblee/security/authorization/UserCondition.java [new file with mode: 0644]
security/src/main/java/org/wamblee/security/authorization/WriteOperation.java [new file with mode: 0644]
security/src/main/java/org/wamblee/security/authorization/hibernate/PersistentAuthorizationService.java [new file with mode: 0644]
security/src/main/java/org/wamblee/security/encryption/Md5HexMessageDigester.java [new file with mode: 0644]
security/src/main/java/org/wamblee/security/encryption/MessageDigester.java [new file with mode: 0644]
security/src/main/java/org/wamblee/usermgt/AbstractUserSet.java [new file with mode: 0644]
security/src/main/java/org/wamblee/usermgt/Group.java [new file with mode: 0644]
security/src/main/java/org/wamblee/usermgt/GroupSet.java [new file with mode: 0644]
security/src/main/java/org/wamblee/usermgt/InMemoryGroupSet.java [new file with mode: 0644]
security/src/main/java/org/wamblee/usermgt/InMemoryUserSet.java [new file with mode: 0644]
security/src/main/java/org/wamblee/usermgt/JaasUserAccessor.java [new file with mode: 0644]
security/src/main/java/org/wamblee/usermgt/NameValidator.java [new file with mode: 0644]
security/src/main/java/org/wamblee/usermgt/RegexpNameValidator.java [new file with mode: 0644]
security/src/main/java/org/wamblee/usermgt/User.java [new file with mode: 0644]
security/src/main/java/org/wamblee/usermgt/UserAccessor.java [new file with mode: 0644]
security/src/main/java/org/wamblee/usermgt/UserAdminInitializer.java [new file with mode: 0644]
security/src/main/java/org/wamblee/usermgt/UserAdministration.java [new file with mode: 0644]
security/src/main/java/org/wamblee/usermgt/UserAdministrationImpl.java [new file with mode: 0644]
security/src/main/java/org/wamblee/usermgt/UserMgtException.java [new file with mode: 0644]
security/src/main/java/org/wamblee/usermgt/UserSet.java [new file with mode: 0644]
security/src/main/java/org/wamblee/usermgt/hibernate/HibernateGroupSet.java [new file with mode: 0644]
security/src/main/java/org/wamblee/usermgt/hibernate/HibernateUserSet.java [new file with mode: 0644]
security/src/test/java/org/wamblee/security/authorization/AuthorizationServiceTest.java [new file with mode: 0644]
security/src/test/java/org/wamblee/security/authorization/DefaultOperationRegistryTest.java [new file with mode: 0644]
security/src/test/java/org/wamblee/security/authorization/RegexpPathConditionTest.java [new file with mode: 0644]
security/src/test/java/org/wamblee/security/authorization/StartsWithPathConditionTest.java [new file with mode: 0644]
security/src/test/java/org/wamblee/security/authorization/TestAuthorizationRule.java [new file with mode: 0644]
security/src/test/java/org/wamblee/security/authorization/TestResource.java [new file with mode: 0644]
security/src/test/java/org/wamblee/security/authorization/TestUserAccessor.java [new file with mode: 0644]
security/src/test/java/org/wamblee/security/authorization/UrlAuthorizationRuleTest.java [new file with mode: 0644]
security/src/test/java/org/wamblee/security/authorization/hibernate/AuthorizationMappingFiles.java [new file with mode: 0644]
security/src/test/java/org/wamblee/security/authorization/hibernate/AuthorizationSpringConfigFiles.java [new file with mode: 0644]
security/src/test/java/org/wamblee/security/authorization/hibernate/PersistentAuthorizationServiceTest.java [new file with mode: 0644]
security/src/test/java/org/wamblee/security/encryption/MessageDigesterTest.java [new file with mode: 0644]
security/src/test/java/org/wamblee/usermgt/InMemoryGroupSetTest.java [new file with mode: 0644]
security/src/test/java/org/wamblee/usermgt/InMemoryUserSetTest.java [new file with mode: 0644]
security/src/test/java/org/wamblee/usermgt/UserAdministrationImplTest.java [new file with mode: 0644]
security/src/test/java/org/wamblee/usermgt/UsermgtHibernateMappingFiles.java [new file with mode: 0644]
security/src/test/java/org/wamblee/usermgt/UsermgtSpringConfigFiles.java [new file with mode: 0644]
security/src/test/java/org/wamblee/usermgt/UsermgtTestUtils.java [new file with mode: 0644]
security/src/test/java/org/wamblee/usermgt/hibernate/HibernateGroupSetTest.java [new file with mode: 0644]
security/src/test/java/org/wamblee/usermgt/hibernate/HibernateUserAdministrationTest.java [new file with mode: 0644]
security/src/test/java/org/wamblee/usermgt/hibernate/HibernateUserSetTest.java [new file with mode: 0644]
security/src/test/resources/hbm/AuthorizationRule.hbm.xml [new file with mode: 0644]
security/src/test/resources/hbm/AuthorizationService.hbm.xml [new file with mode: 0644]
security/src/test/resources/hbm/Group.hbm.xml [new file with mode: 0644]
security/src/test/resources/hbm/OperationCondition.hbm.xml [new file with mode: 0644]
security/src/test/resources/hbm/OperationCondition.hbm.xmlxx [new file with mode: 0644]
security/src/test/resources/hbm/PageAuthorizationRule.hbm.xml [new file with mode: 0644]
security/src/test/resources/hbm/PathCondition.hbm.xml [new file with mode: 0644]
security/src/test/resources/hbm/PhotoAuthorizationRule.hbm.xml [new file with mode: 0644]
security/src/test/resources/hbm/TestAuthorizationRule.hbm.xml [new file with mode: 0644]
security/src/test/resources/hbm/User.hbm.xml [new file with mode: 0644]
security/src/test/resources/hbm/UserCondition.hbm.xml [new file with mode: 0644]
security/src/test/resources/hbm/ehcache.xml [new file with mode: 0644]
security/src/test/resources/hbm/hibernate.properties [new file with mode: 0644]
security/src/test/resources/properties/test.org.wamblee.security.database.properties [new file with mode: 0644]
security/src/test/resources/properties/test.org.wamblee.security.ehcache.xml [new file with mode: 0644]
security/src/test/resources/properties/test.org.wamblee.security.hibernate.properties [new file with mode: 0644]
security/src/test/resources/properties/test.org.wamblee.security.usermgt.properties [new file with mode: 0644]
security/src/test/resources/spring/test.org.wamblee.security.authorization.xml [new file with mode: 0644]
security/src/test/resources/spring/test.org.wamblee.security.database.xml [new file with mode: 0644]
security/src/test/resources/spring/test.org.wamblee.security.datasource.xml [new file with mode: 0644]
security/src/test/resources/spring/test.org.wamblee.security.properties.xml [new file with mode: 0644]
security/src/test/resources/spring/test.org.wamblee.security.usermgt.xml [new file with mode: 0644]
support/src/test/java/org/wamblee/test/SpringTestCase.java

index 84317413b6fe9009f169b1969f2d8fe89e7fec54..40a31d71ec9b78115817385a51c8267571c76e24 100644 (file)
@@ -4,6 +4,9 @@
        <classpathentry kind="src" path="support/src/test/java"/>
        <classpathentry excluding="**" kind="src" output="support/src/main/resources" path="support/src/main/resources"/>
        <classpathentry excluding="**" kind="src" output="support/src/test/resources" path="support/src/test/resources"/>
+       <classpathentry kind="src" path="security/src/main/java"/>
+       <classpathentry kind="src" path="security/src/test/java"/>
+       <classpathentry excluding="**" kind="src" output="security/src/test/resources" path="security/src/test/resources"/>
        <classpathentry kind="src" path="socketproxy/src/main/java"/>
        <classpathentry kind="src" path="socketproxy/src/test/java"/>
        <classpathentry excluding="**" kind="src" output="socketproxy/src/test/resources" path="socketproxy/src/test/resources"/>
@@ -28,7 +31,7 @@
        <classpathentry excluding="**" kind="src" output="mythtv/war/src/main/resources" path="mythtv/war/src/main/resources"/>
        <classpathentry kind="src" path="mythtv/timer/src/main/java"/>
        <classpathentry excluding="**" kind="src" output="mythtv/timer/src/main/resources" path="mythtv/timer/src/main/resources"/>
-       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/jdk1.6.0"/>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
        <classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER/modules"/>
        <classpathentry kind="output" path="target/classes"/>
 </classpath>
diff --git a/pom.xml b/pom.xml
index 378e47912c631a7804a1dc6185ed04e2afc88e78..981a892b0c8fefe1b846c3bb95996c9858b0ed87 100644 (file)
--- a/pom.xml
+++ b/pom.xml
 <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>
-  <groupId>org.wamblee</groupId>
-  <artifactId>wamblee-utils</artifactId>
-  <packaging>pom</packaging>
-  <version>0.2-SNAPSHOT</version>
-  <name>wamblee.org utility libraries</name>
-  <url>http://wamblee.org</url>
-  <modules>
-    <module>support</module>
-    <module>hibernate-jpa</module>
-    <module>socketproxy</module>
-    <module>crawler</module>
-    <module>gps</module>
-    <module>mythtv</module>
-  </modules>
-  <dependencies>
-    <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
-      <version>4.4</version>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>dbunit</groupId>
-      <artifactId>dbunit</artifactId>
-      <version>2.1</version>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>jmock</groupId>
-      <artifactId>jmock-cglib</artifactId>
-      <version>1.1.0</version>
-      <scope>test</scope>
-      <exclusions>
-        <exclusion>
-          <groupId>cglib</groupId>
-          <artifactId>cglib-full</artifactId>
-        </exclusion>
-      </exclusions>
-    </dependency>
-  </dependencies>
-
-  <dependencyManagement>
+    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>
+    <groupId>org.wamblee</groupId>
+    <artifactId>wamblee-utils</artifactId>
+    <packaging>pom</packaging>
+    <version>0.2-SNAPSHOT</version>
+    <name>wamblee.org utility libraries</name>
+    <url>http://wamblee.org</url>
+    <modules>
+        <module>support</module>
+        <module>hibernate-jpa</module>
+        <module>security</module>
+        <module>socketproxy</module>
+        <module>crawler</module>
+        <module>gps</module>
+        <module>mythtv</module>
+    </modules>
     <dependencies>
-      <dependency>
-        <groupId>org.wamblee</groupId>
-        <artifactId>wamblee-support</artifactId>
-        <version>${project.version}</version>
-      </dependency>
-      <dependency>
-        <groupId>org.wamblee</groupId>
-        <artifactId>wamblee-support</artifactId>
-        <type>test-jar</type>
-        <version>${project.version}</version>
-      </dependency>
-      <dependency>
-        <groupId>org.wamblee</groupId>
-        <artifactId>wamblee-hibernate-jpa</artifactId>
-        <version>${project.version}</version>
-      </dependency>
-      <dependency>
-        <groupId>org.wamblee</groupId>
-        <artifactId>wamblee-hibernate-jpa</artifactId>
-        <version>${project.version}</version>
-        <type>test-jar</type>
-      </dependency>
-      <dependency>
-        <groupId>org.wamblee</groupId>
-        <artifactId>wamblee-socketproxy</artifactId>
-        <version>${project.version}</version>
-      </dependency>
-      <dependency>
-        <groupId>org.wamblee</groupId>
-        <artifactId>wamblee-socketproxy</artifactId>
-        <type>test-jar</type>
-        <version>${project.version}</version>
-      </dependency>
-      <dependency>
-        <groupId>org.wamblee</groupId>
-        <artifactId>wamblee-crawler</artifactId>
-        <version>${project.version}</version>
-      </dependency>
-      <dependency>
-        <groupId>org.wamblee</groupId>
-        <artifactId>wamblee-crawler</artifactId>
-        <type>test-jar</type>
-        <version>${project.version}</version>
-      </dependency>
-      <dependency>
-        <groupId>org.wamblee</groupId>
-        <artifactId>wamblee-crawler-basic</artifactId>
-        <version>${project.version}</version>
-      </dependency>
-      <dependency>
-        <groupId>org.wamblee</groupId>
-        <artifactId>wamblee-crawler-basic</artifactId>
-        <type>test-jar</type>
-        <version>${project.version}</version>
-      </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.4</version>
+            <scope>test</scope> 
+        </dependency>
+        <dependency>
+            <groupId>org.dbunit</groupId>
+            <artifactId>dbunit</artifactId>
+            <version>2.2</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>jmock</groupId>
+            <artifactId>jmock-cglib</artifactId>
+            <version>1.1.0</version>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>cglib</groupId>
+                    <artifactId>cglib-full</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+    </dependencies>
 
-      <dependency>
-        <groupId>javax.persistence</groupId>
-        <artifactId>persistence-api</artifactId>
-        <version>1.0</version>
-        <scope>provided</scope>
-      </dependency>
-      <dependency>
-        <groupId>javax.activation</groupId>
-        <artifactId>activation</artifactId>
-        <version>1.1</version>
-      </dependency>
-      <dependency>
-        <groupId>javax.mail</groupId>
-        <artifactId>mail</artifactId>
-        <version>1.4.1</version>
-      </dependency>
-      <dependency>
-        <groupId>javax.servlet</groupId>
-        <artifactId>servlet-api</artifactId>
-        <version>2.3</version>
-        <type>jar</type>
-        <scope>provided</scope>
-      </dependency>
-      <dependency>
-        <groupId>org.apache.derby</groupId>
-        <artifactId>derby</artifactId>
-        <version>10.3.2.1</version>
-      </dependency>
-      <dependency>
-        <groupId>org.apache.derby</groupId>
-        <artifactId>derbyclient</artifactId>
-        <version>10.3.2.1</version>
-      </dependency>
-      <dependency>
-        <groupId>org.apache.derby</groupId>
-        <artifactId>derbynet</artifactId>
-        <version>10.3.2.1</version>
-      </dependency>
-      <dependency>
-        <groupId>quartz</groupId>
-        <artifactId>quartz</artifactId>
-        <version>1.5.1</version>
-      </dependency>
-      <dependency>
-        <groupId>jtidy</groupId>
-        <artifactId>jtidy</artifactId>
-        <version>4aug2000r7-dev</version>
-      </dependency>
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.wamblee</groupId>
+                <artifactId>wamblee-support</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.wamblee</groupId>
+                <artifactId>wamblee-support</artifactId>
+                <type>test-jar</type>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.wamblee</groupId>
+                <artifactId>wamblee-hibernate-jpa</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.wamblee</groupId>
+                <artifactId>wamblee-hibernate-jpa</artifactId>
+                <version>${project.version}</version>
+                <type>test-jar</type>
+            </dependency>
+            <dependency>
+                <groupId>org.wamblee</groupId>
+                <artifactId>wamblee-socketproxy</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.wamblee</groupId>
+                <artifactId>wamblee-socketproxy</artifactId>
+                <type>test-jar</type>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.wamblee</groupId>
+                <artifactId>wamblee-crawler</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.wamblee</groupId>
+                <artifactId>wamblee-crawler</artifactId>
+                <type>test-jar</type>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.wamblee</groupId>
+                <artifactId>wamblee-crawler-basic</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.wamblee</groupId>
+                <artifactId>wamblee-crawler-basic</artifactId>
+                <type>test-jar</type>
+                <version>${project.version}</version>
+            </dependency>
 
-    
-      <dependency>
-        <groupId>commons-logging</groupId>
-        <artifactId>commons-logging</artifactId>
-        <version>1.0.2</version>
-      </dependency>
-      <dependency>
-        <groupId>commons-httpclient</groupId>
-        <artifactId>commons-httpclient</artifactId>
-        <version>3.0</version>
-      </dependency>
-      <dependency>
-        <groupId>commons-beanutils</groupId>
-        <artifactId>commons-beanutils</artifactId>
-        <version>1.7.0</version>
-      </dependency>
-      <dependency>
-        <groupId>org.springframework</groupId>
-        <artifactId>spring-beans</artifactId>
-        <version>${springversion}</version>
-      </dependency>
-      <dependency>
-        <groupId>org.springframework</groupId>
-        <artifactId>spring-web</artifactId>
-        <version>${springversion}</version>
-      </dependency>
-      <dependency>
-        <groupId>org.springframework</groupId>
-        <artifactId>spring-jms</artifactId>
-        <version>${springversion}</version>
-      </dependency>
-      <dependency>
-        <groupId>org.springframework</groupId>
-        <artifactId>spring-context</artifactId>
-        <version>${springversion}</version>
-      </dependency>
-      <dependency>
-        <groupId>org.springframework</groupId>
-        <artifactId>spring-hibernate3</artifactId>
-        <version>${springversion}</version>
-      </dependency>
-      <dependency>
-        <groupId>org.springframework</groupId>
-        <artifactId>spring-jpa</artifactId>
-        <version>${springversion}</version>
-      </dependency>
-      <dependency>
-        <groupId>org.springframework</groupId>
-        <artifactId>spring-aop</artifactId>
-        <version>${springversion}</version>
-      </dependency>
+            <dependency>
+                <groupId>javax.persistence</groupId>
+                <artifactId>persistence-api</artifactId>
+                <version>1.0</version>
+                <scope>provided</scope>
+            </dependency>
+            <dependency>
+                <groupId>javax.activation</groupId>
+                <artifactId>activation</artifactId>
+                <version>1.1</version>
+            </dependency>
+            <dependency>
+                <groupId>javax.mail</groupId>
+                <artifactId>mail</artifactId>
+                <version>1.4.1</version>
+            </dependency>
+            <dependency>
+                <groupId>javax.servlet</groupId>
+                <artifactId>servlet-api</artifactId>
+                <version>2.3</version>
+                <type>jar</type>
+                <scope>provided</scope>
+            </dependency>
+            <dependency>
+                <groupId>mysql</groupId>
+                <artifactId>mysql-connector-java</artifactId>
+                <version>3.1.14</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.derby</groupId>
+                <artifactId>derby</artifactId>
+                <version>10.3.2.1</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.derby</groupId>
+                <artifactId>derbyclient</artifactId>
+                <version>10.3.2.1</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.derby</groupId>
+                <artifactId>derbynet</artifactId>
+                <version>10.3.2.1</version>
+            </dependency>
+            <dependency>
+                <groupId>quartz</groupId>
+                <artifactId>quartz</artifactId>
+                <version>1.5.1</version>
+            </dependency>
+            <dependency>
+                <groupId>jtidy</groupId>
+                <artifactId>jtidy</artifactId>
+                <version>4aug2000r7-dev</version>
+            </dependency>
 
-      <!-- should be possible to remove the dependence on log4j -->
-      <dependency>
-        <groupId>log4j</groupId>
-        <artifactId>log4j</artifactId>
-        <version>1.2.8</version>
-      </dependency>
-      <dependency>
-        <groupId>dom4j</groupId>
-        <artifactId>dom4j</artifactId>
-        <version>1.6</version>
-        <exclusions>
-          <exclusion>
-            <groupId>xml-apis</groupId>
-            <artifactId>xml-apis</artifactId>
-          </exclusion>
-        </exclusions>
-      </dependency>
-      <dependency>
-        <groupId>net.sf.ehcache</groupId>
-        <artifactId>ehcache</artifactId>
-        <version>1.2.3</version>
-      </dependency>
-      <dependency>
-        <groupId>xerces</groupId>
-        <artifactId>xercesImpl</artifactId>
-        <version>2.8.1</version>
-      </dependency>
-     
-     
-      <dependency>
-        <groupId>commons-email</groupId>
-        <artifactId>commons-email</artifactId>
-        <version>1.0</version>
-      </dependency>
-      <dependency>
-        <groupId>jaxen</groupId>
-        <artifactId>jaxen</artifactId>
-        <version>1.1-beta-9</version>
-        <exclusions>
-          <exclusion>
-            <groupId>xom</groupId>
-            <artifactId>xom</artifactId>
-          </exclusion>
-          <exclusion>
-            <groupId>xerces</groupId>
-            <artifactId>xmlParserAPIs</artifactId>
-          </exclusion>
-        </exclusions>
-      </dependency>
-      <dependency>
-        <groupId>jstl</groupId>
-        <artifactId>jstl</artifactId>
-        <version>1.1.2</version>
-      </dependency>
-      <dependency>
-        <groupId>taglibs</groupId>
-        <artifactId>standard</artifactId>
-        <version>1.1.2</version>
-      </dependency>
-      <dependency>
-        <groupId>jfree</groupId>
-        <artifactId>jfreechart</artifactId>
-        <version>1.0.1</version>
-      </dependency>
-      <dependency>
-        <groupId>jfree</groupId>
-        <artifactId>jcommon</artifactId>
-        <version>1.0.2</version>
-      </dependency>
-      
-      <dependency>
-        <groupId>javaee</groupId>
-        <artifactId>javaee-api</artifactId>
-        <version>5</version>
-        <scope>provided</scope>
-      </dependency>
 
+            <dependency>
+                <groupId>commons-logging</groupId>
+                <artifactId>commons-logging</artifactId>
+                <version>1.0.2</version>
+            </dependency>
+            <dependency>
+                <groupId>commons-httpclient</groupId>
+                <artifactId>commons-httpclient</artifactId>
+                <version>3.0</version>
+            </dependency>
+            <dependency>
+                <groupId>commons-beanutils</groupId>
+                <artifactId>commons-beanutils</artifactId>
+                <version>1.7.0</version>
+            </dependency>
+            <dependency>
+                <groupId>org.springframework</groupId>
+                <artifactId>spring-beans</artifactId>
+                <version>${springversion}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.springframework</groupId>
+                <artifactId>spring-web</artifactId>
+                <version>${springversion}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.springframework</groupId>
+                <artifactId>spring-jms</artifactId>
+                <version>${springversion}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.springframework</groupId>
+                <artifactId>spring-context</artifactId>
+                <version>${springversion}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.springframework</groupId>
+                <artifactId>spring-hibernate3</artifactId>
+                <version>${springversion}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.springframework</groupId>
+                <artifactId>spring-jpa</artifactId>
+                <version>${springversion}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.springframework</groupId>
+                <artifactId>spring-aop</artifactId>
+                <version>${springversion}</version>
+            </dependency>
 
-    </dependencies>
-  </dependencyManagement>
+            <!-- should be possible to remove the dependence on log4j -->
+            <dependency>
+                <groupId>log4j</groupId>
+                <artifactId>log4j</artifactId>
+                <version>1.2.8</version>
+            </dependency>
+            <dependency>
+                <groupId>dom4j</groupId>
+                <artifactId>dom4j</artifactId>
+                <version>1.6</version>
+                <exclusions>
+                    <exclusion>
+                        <groupId>xml-apis</groupId>
+                        <artifactId>xml-apis</artifactId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+            <dependency>
+                <groupId>net.sf.ehcache</groupId>
+                <artifactId>ehcache</artifactId>
+                <version>1.2.3</version>
+            </dependency>
+            <dependency>
+                <groupId>xerces</groupId>
+                <artifactId>xercesImpl</artifactId>
+                <version>2.8.1</version>
+            </dependency>
+
+
+            <dependency>
+                <groupId>commons-email</groupId>
+                <artifactId>commons-email</artifactId>
+                <version>1.0</version>
+            </dependency>
+            <dependency>
+                <groupId>jaxen</groupId>
+                <artifactId>jaxen</artifactId>
+                <version>1.1-beta-9</version>
+                <exclusions>
+                    <exclusion>
+                        <groupId>xom</groupId>
+                        <artifactId>xom</artifactId>
+                    </exclusion>
+                    <exclusion>
+                        <groupId>xerces</groupId>
+                        <artifactId>xmlParserAPIs</artifactId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+            <dependency>
+                <groupId>jstl</groupId>
+                <artifactId>jstl</artifactId>
+                <version>1.1.2</version>
+            </dependency>
+            <dependency>
+                <groupId>taglibs</groupId>
+                <artifactId>standard</artifactId>
+                <version>1.1.2</version>
+            </dependency>
+            <dependency>
+                <groupId>jfree</groupId>
+                <artifactId>jfreechart</artifactId>
+                <version>1.0.1</version>
+            </dependency>
+            <dependency>
+                <groupId>jfree</groupId>
+                <artifactId>jcommon</artifactId>
+                <version>1.0.2</version>
+            </dependency>
 
-  <build>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-compiler-plugin</artifactId>
-        <configuration>
-          <source>1.6</source>
-          <target>1.6</target>
-        </configuration>
-      </plugin>
+            <dependency>
+                <groupId>javaee</groupId>
+                <artifactId>javaee-api</artifactId>
+                <version>5</version>
+                <scope>provided</scope>
+            </dependency>
 
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-surefire-plugin</artifactId>
-        <configuration>
-          <includes>
-            <include>**/*Test.java</include>
-          </includes>
-        </configuration>
-      </plugin>
+            <dependency>
+                <groupId>commons-codec</groupId>
+                <artifactId>commons-codec</artifactId>
+                <version>1.3</version>
+            </dependency>
 
-      <!-- Make sure other projects can use (or the test support
+
+        </dependencies>
+    </dependencyManagement>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>1.6</source>
+                    <target>1.6</target>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <includes>
+                        <include>**/*Test.java</include>
+                    </includes>
+                </configuration>
+            </plugin>
+
+            <!-- Make sure other projects can use (or the test support
            and test classes from the projects it uses. To use
            a dependence on a test library of a project, an additinoal
            dependence must be added with <type>test-jar</type>
         -->
 
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-jar-plugin</artifactId>
-        <executions>
-          <execution>
-            <goals>
-              <goal>test-jar</goal>
-            </goals>
-          </execution>
-        </executions>
-      </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>test-jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>cobertura-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>clean</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
 
-      <plugin>
-        <groupId>org.codehaus.mojo</groupId>
-        <artifactId>cobertura-maven-plugin</artifactId>
-        <executions>
-          <execution>
-            <goals>
-              <goal>clean</goal>
-            </goals>
-          </execution>
-        </executions>
-      </plugin>
+        </plugins>
 
-    </plugins>
+    </build>
 
-  </build>
+    <reporting>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-project-info-reports-plugin</artifactId>
+                <reportSets>
+                    <reportSet>
+                        <reports>
+                            <report>checkstyle</report>
+                            <report>javadoc</report>
+                            <report>dependencies</report>
+                            <report>project-team</report>
+                            <report>mailing-list</report>
+                            <report>issue-tracking</report>
+                            <report>license</report>
+                            <report>scm</report>
+                        </reports>
+                    </reportSet>
+                </reportSets>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>changes-maven-plugin</artifactId>
+                <version>2.0-beta-1</version>
+                <reportSets>
+                    <reportSet>
+                        <reports>
+                            <report>changes-report</report>
+                        </reports>
+                    </reportSet>
+                </reportSets>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-javadoc-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>surefire-report-maven-plugin</artifactId>
+            </plugin>
+            <!-- Test coverage reporting -->
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>cobertura-maven-plugin</artifactId>
+            </plugin>
 
-  <reporting>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-project-info-reports-plugin</artifactId>
-        <reportSets>
-          <reportSet>
-            <reports>
-              <report>checkstyle</report>
-              <report>javadoc</report>
-              <report>dependencies</report>
-              <report>project-team</report>
-              <report>mailing-list</report>
-              <report>issue-tracking</report>
-              <report>license</report>
-              <report>scm</report>
-            </reports>
-          </reportSet>
-        </reportSets>
-      </plugin>
-      <plugin>
-        <groupId>org.codehaus.mojo</groupId>
-        <artifactId>changes-maven-plugin</artifactId>
-        <version>2.0-beta-1</version>
-        <reportSets>
-          <reportSet>
-            <reports>
-              <report>changes-report</report>
-            </reports>
-          </reportSet>
-        </reportSets>
-      </plugin>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-javadoc-plugin</artifactId>
-      </plugin>
-      <plugin>
-        <groupId>org.codehaus.mojo</groupId>
-        <artifactId>surefire-report-maven-plugin</artifactId>
-      </plugin>
-      <!-- Test coverage reporting -->
-      <plugin>
-        <groupId>org.codehaus.mojo</groupId>
-        <artifactId>cobertura-maven-plugin</artifactId>
-      </plugin>
+            <!-- checkstyle -->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-checkstyle-plugin</artifactId>
+                <configuration>
+                    <configLocation>config/sun_checks.xml</configLocation>
+                </configuration>
+            </plugin>
 
-      <!-- checkstyle -->
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-checkstyle-plugin</artifactId>
-        <configuration>
-          <configLocation>config/sun_checks.xml</configLocation>
-        </configuration>
-      </plugin>
+            <!-- taglist -->
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>taglist-maven-plugin</artifactId>
+                <configuration>
+                    <tags>
+                        <tag>TODO</tag>
+                        <tag>@todo</tag>
+                        <tag>FIXME</tag>
+                    </tags>
+                </configuration>
+            </plugin>
 
-      <!-- taglist -->
-      <plugin>
-        <groupId>org.codehaus.mojo</groupId>
-        <artifactId>taglist-maven-plugin</artifactId>
-        <configuration>
-          <tags>
-            <tag>TODO</tag>
-            <tag>@todo</tag>
-            <tag>FIXME</tag>
-          </tags>
-        </configuration>
-      </plugin>
+        </plugins>
+    </reporting>
 
-    </plugins>
-  </reporting>
-  
-  <repositories>
-    <repository>
-      <id>javaee</id>
-      <name>Java EE repo at SUN</name>
-      <url>http://download.java.net/maven/1</url>
-      <layout>legacy</layout>
-    </repository>
-  </repositories>
+    <repositories>
+        <repository>
+            <id>javaee</id>
+            <name>Java EE repo at SUN</name>
+            <url>http://download.java.net/maven/1</url>
+            <layout>legacy</layout>
+        </repository>
+    </repositories>
 
-  <properties>
-    <springversion>2.0.8</springversion>
-  </properties>
+    <properties>
+        <springversion>2.0.8</springversion>
+    </properties>
 
 </project>
diff --git a/security/pom.xml b/security/pom.xml
new file mode 100644 (file)
index 0000000..9d6264b
--- /dev/null
@@ -0,0 +1,69 @@
+<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">
+
+  <parent>
+    <groupId>org.wamblee</groupId>
+    <artifactId>wamblee-utils</artifactId>
+    <version>0.2-SNAPSHOT</version>
+  </parent>
+
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.wamblee</groupId>
+  <artifactId>wamblee-security</artifactId>
+  <packaging>jar</packaging>
+  <name>wamblee.org security</name>
+  <url>http://wamblee.org</url>
+  <dependencies>
+    
+    <dependency>
+      <groupId>org.wamblee</groupId>
+      <artifactId>wamblee-support</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.wamblee</groupId>
+      <artifactId>wamblee-support</artifactId>
+      <type>test-jar</type>
+    </dependency>
+    <dependency>
+      <groupId>org.wamblee</groupId>
+      <artifactId>wamblee-hibernate-jpa</artifactId>
+    </dependency>
+    
+    <dependency>
+      <groupId>commons-codec</groupId>
+      <artifactId>commons-codec</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>mysql</groupId>
+      <artifactId>mysql-connector-java</artifactId>
+    </dependency>
+   
+    <dependency>
+      <groupId>org.springframework</groupId>
+      <artifactId>spring-beans</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework</groupId>
+      <artifactId>spring-hibernate3</artifactId>
+      <exclusions>
+        <exclusion>
+          <groupId>org.hibernate</groupId>
+          <artifactId>hibernate</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework</groupId>
+      <artifactId>spring-aop</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>javax.transaction</groupId>
+      <artifactId>transaction-api</artifactId>
+      <version>1.1</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+</project>
diff --git a/security/src/main/java/org/wamblee/security/authorization/AllOperation.java b/security/src/main/java/org/wamblee/security/authorization/AllOperation.java
new file mode 100644 (file)
index 0000000..c76d00c
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.security.authorization;
+
+/**
+ * An superclass of all other operations. 
+ */
+public class AllOperation implements Operation {
+    
+    private static final String OPERATION = "all";
+    
+    /**
+     * Operation name. 
+     */
+    private String _name;
+    
+    /**
+     * Constructs an all operation. 
+     *
+     */
+    public AllOperation() { 
+        _name = OPERATION; 
+    }
+    
+    /**
+     * Constructs the operation, this constructor is the one that must be used
+     * by subclasses. 
+     * @param aName Name of the operation. This name must be unique among all operations. 
+     */
+    protected AllOperation(String aName) { 
+        _name = aName; 
+    }
+
+    /* (non-Javadoc)
+     * @see org.wamblee.security.authorization.Operation#getName()
+     */
+    public String getName() {
+        return _name; 
+    }
+
+}
diff --git a/security/src/main/java/org/wamblee/security/authorization/AnyUserCondition.java b/security/src/main/java/org/wamblee/security/authorization/AnyUserCondition.java
new file mode 100644 (file)
index 0000000..c67855d
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.security.authorization;
+
+import org.wamblee.persistence.AbstractPersistent;
+import org.wamblee.usermgt.User;
+
+/**
+ * Matches any user. 
+ */
+public class AnyUserCondition extends AbstractPersistent implements UserCondition {
+    
+    /**
+     * Constructs the condition. 
+     *
+     */
+    public AnyUserCondition() { 
+        // Empty. 
+    }
+
+    /* (non-Javadoc)
+     * @see org.wamblee.security.authorization.UserCondition#matches(org.wamblee.usermgt.User)
+     */
+    public boolean matches(User aUser) {
+        return true; 
+    }
+    
+    /* (non-Javadoc)
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        return "AnyUserCondition()"; 
+    }
+
+}
diff --git a/security/src/main/java/org/wamblee/security/authorization/AuthorizationException.java b/security/src/main/java/org/wamblee/security/authorization/AuthorizationException.java
new file mode 100644 (file)
index 0000000..a44e2df
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.security.authorization;
+
+/**
+ * Authorization exception to be thrown when
+ * a resouce may not be accessed. 
+ */
+public class AuthorizationException extends RuntimeException {
+    
+    private Object _resource; 
+    private Operation _operation; 
+
+    public AuthorizationException(Object aResource, Operation aOperation) {
+        super("Operation '" + aOperation + "' not allowed on '" + aResource + "'");
+        _resource = aResource; 
+        _operation = aOperation; 
+    }
+    
+    public Object getResource() { 
+        return _resource; 
+    }
+    
+    public Operation getOperation() { 
+        return _operation;
+    }
+    
+}
diff --git a/security/src/main/java/org/wamblee/security/authorization/AuthorizationInitializer.java b/security/src/main/java/org/wamblee/security/authorization/AuthorizationInitializer.java
new file mode 100644 (file)
index 0000000..7423eab
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.security.authorization;
+
+import org.apache.log4j.Logger;
+
+
+/**
+ * Inititializer class for authorization rules. This class initializes the 
+ * authorization rules in case none are present. 
+ */
+public class AuthorizationInitializer {
+    
+    private static final Logger LOGGER = Logger.getLogger(AuthorizationInitializer.class);
+
+    /**
+     * Initializes authorization rules in case none are present. 
+     * @param aService Authorization service. 
+     * @param aRules Default rules for initialization. 
+     */
+    public AuthorizationInitializer(AuthorizationService aService, AuthorizationRule[] aRules) { 
+         if ( aService.getRules().length == 0 ) { 
+             for (AuthorizationRule rule: aRules) { 
+                 LOGGER.info("Appending authorization rule " + rule);
+                 aService.appendRule(rule); 
+             }
+         }
+    }
+}
diff --git a/security/src/main/java/org/wamblee/security/authorization/AuthorizationResult.java b/security/src/main/java/org/wamblee/security/authorization/AuthorizationResult.java
new file mode 100644 (file)
index 0000000..f36bf49
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.wamblee.security.authorization;
+
+/**
+ * Represents the result of an authorization decision.
+ */
+public enum AuthorizationResult {
+    /**
+     * Access is granted.
+     */
+    GRANTED,
+
+    /**
+     * Access is denied.
+     */
+    DENIED,
+
+    /**
+     * Access is undecided.
+     */
+    UNDECIDED,
+
+    /**
+     * Unsupported resource.
+     */
+    UNSUPPORTED_RESOURCE
+}
diff --git a/security/src/main/java/org/wamblee/security/authorization/AuthorizationRule.java b/security/src/main/java/org/wamblee/security/authorization/AuthorizationRule.java
new file mode 100644 (file)
index 0000000..8c17b7f
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.security.authorization;
+
+import org.wamblee.persistence.Persistent;
+import org.wamblee.usermgt.User;
+
+/**
+ * Represents an authorization rule to determine whether an operation is allowed on a resource. 
+ */
+public interface AuthorizationRule extends Persistent {
+    
+    /**
+     * Returns the supported object types for which this authorization rule applies.
+     * This can be used by the authorization service for optimization.  
+     * @return Array of supported types. 
+     */
+    Class[] getSupportedTypes();
+
+    /**
+     * Determines whether an operation is allowed on a certain resource. 
+     * The rule implementation must be prepared to deal with resources for which it does
+     * not apply. In those cases it should return {@link AuthorizationResult#UNSUPPORTED_RESOURCE}. 
+     * @param aResource Resource. 
+     * @param anOperation Operation. 
+     * @param aUser Current user.  
+     * @return Authorization result. 
+     */
+    AuthorizationResult isAllowed(Object aResource, Operation anOperation, User aUser);
+}
diff --git a/security/src/main/java/org/wamblee/security/authorization/AuthorizationService.java b/security/src/main/java/org/wamblee/security/authorization/AuthorizationService.java
new file mode 100644 (file)
index 0000000..867242a
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.security.authorization;
+
+import org.wamblee.persistence.Persistent;
+
+/**
+ * Service to determine if access to a certain resource is allowed. 
+ */
+public interface AuthorizationService extends Persistent {
+
+    /**
+     * Checks whether an operation is allowed on a resource. 
+     * @param aResource Resource. 
+     * @param aOperation Operation. 
+     * @return Checks whether the operation is allowed on a resource. 
+     */
+    boolean isAllowed(Object aResource, Operation aOperation);
+    
+    /**
+     * Same as {@link #isAllowed(Object, Operation)} but throws a 
+     * <code>RuntimeException</code> in case access is not allowed. 
+     * @param aResource Resource to check. 
+     * @param aOperation Operation to perform.
+     * @return Resource that was checked. 
+     */
+    <T> T check(T aResource, Operation aOperation); 
+    
+    /**
+     * Gets the authorization rules. 
+     * @return Rules. 
+     */
+    AuthorizationRule[] getRules(); 
+    
+    /**
+     * Appends a new authorization rule to the end. 
+     * @param aRule Rule to append. 
+     */
+    void appendRule(AuthorizationRule aRule); 
+    
+    /**
+     * Removes a rule. 
+     * @param aRule Index of the rule to remove.  
+     */
+    void removeRule(int aIndex); 
+        
+    /**
+     * Inserts a rule. 
+     * @param aIndex Index of the position of the rule after insertion. 
+     * @param aRule Rule to insert. 
+     */
+    void insertRuleAfter(int aIndex, AuthorizationRule aRule);
+}
diff --git a/security/src/main/java/org/wamblee/security/authorization/CreateOperation.java b/security/src/main/java/org/wamblee/security/authorization/CreateOperation.java
new file mode 100644 (file)
index 0000000..7020a68
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.security.authorization;
+
+/**
+ * Represents an operation to create something. 
+ */
+public class CreateOperation extends AllOperation {
+    
+    private static final String OPERATION = "create";
+    
+    /**
+     * Constructs the operation. 
+     *
+     */
+    public CreateOperation() { 
+        super(OPERATION);
+    }
+
+}
diff --git a/security/src/main/java/org/wamblee/security/authorization/DefaultAuthorizationService.java b/security/src/main/java/org/wamblee/security/authorization/DefaultAuthorizationService.java
new file mode 100644 (file)
index 0000000..d455ade
--- /dev/null
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.security.authorization;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.wamblee.persistence.AbstractPersistent;
+import org.wamblee.usermgt.User;
+import org.wamblee.usermgt.UserAccessor;
+
+/**
+ * Default implementation of an authorization service. 
+ * To determine whether access to a resource is allowed, the service consults a number
+ * of authorization rules in a fixed order. The first rule that gives a result GRANTED or 
+ * DENIED determines the result of the evaluation. Rules that return any other result are 
+ * ignoed. If none of the rules match, than access is denied. 
+ */
+public class DefaultAuthorizationService extends AbstractPersistent implements AuthorizationService {
+    
+    /**
+     * List of ordered authorization rules. 
+     */
+    private List<AuthorizationRule> _rules; 
+    
+    /**
+     * User accessor used to obtain the current user. 
+     */
+    private UserAccessor _userAccessor; 
+    
+    /**
+     * Name for this instance of the authorization service. 
+     */
+    private String _name;
+    
+    /**
+     * Constructs the service. 
+     * @param aAccessor User accessor. 
+     * @param aName Name of this instance of the service. 
+     */
+    public DefaultAuthorizationService(UserAccessor aAccessor, String aName) {
+        _rules = new ArrayList<AuthorizationRule>();
+        _userAccessor = aAccessor;
+        _name = aName; 
+    }
+    
+    /**
+     * Constructs the authorization service.  
+     */
+    public DefaultAuthorizationService() {
+        _rules = new ArrayList<AuthorizationRule>();
+        _userAccessor = null;
+        _name = null; 
+    }
+    
+    /**
+     * Sets the user accessor. 
+     * @param aUserAccessor User accessor. 
+     */
+    public void setUserAccessor(UserAccessor aUserAccessor) { 
+        _userAccessor = aUserAccessor; 
+    }
+
+    /* (non-Javadoc)
+     * @see org.wamblee.security.authorization.AuthorizationService#isAllowed(java.lang.Object, org.wamblee.security.authorization.Operation)
+     */
+    public boolean isAllowed(Object aResource, Operation aOperation) {
+        User user = _userAccessor.getCurrentUser(); 
+        for (AuthorizationRule rule: _rules) { 
+            switch ( rule.isAllowed(aResource, aOperation, user)) {
+            case DENIED: { return false; } 
+            case GRANTED: { return true; }
+            }
+        }
+        return false; 
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.security.authorization.AuthorizationService#check(T, org.wamblee.security.authorization.Operation)
+     */
+    public <T> T check(T aResource, Operation aOperation) {
+        if ( !isAllowed(aResource, aOperation)) { 
+            throw new AuthorizationException(aResource, aOperation);
+        }
+        return aResource;
+    }
+    
+    protected String getName() { 
+        return _name; 
+    }
+    
+    public void setName(String aName) { 
+        _name = aName; 
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.security.authorization.AuthorizationService#getRules()
+     */
+    public AuthorizationRule[] getRules() {
+        return _rules.toArray(new AuthorizationRule[0]); 
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.security.authorization.AuthorizationService#appendRule(org.wamblee.security.authorization.AuthorizationRule)
+     */
+    public void appendRule(AuthorizationRule aRule) {
+        _rules.add(aRule); 
+    }
+
+    /* (non-Javadoc)
+     * @see org.wamblee.security.authorization.AuthorizationService#insertRuleAfter(int, org.wamblee.security.authorization.AuthorizationRule)
+     */
+    public void insertRuleAfter(int aIndex, AuthorizationRule aRule) {
+       _rules.add(aIndex, aRule);
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.security.authorization.AuthorizationService#removeRule(int)
+     */
+    public void removeRule(int aIndex) {
+       _rules.remove(aIndex);
+    }
+    
+    /**
+     * For OR mapping. 
+     * @return The rules. 
+     */
+    protected List<AuthorizationRule> getMappedRules() {
+        return _rules; 
+    }
+    
+    /**
+     * For OR mapping.
+     * @param aRules The rules. 
+     */
+    protected void setMappedRules(List<AuthorizationRule> aRules) { 
+        _rules = aRules; 
+    }
+}
diff --git a/security/src/main/java/org/wamblee/security/authorization/DefaultOperationRegistry.java b/security/src/main/java/org/wamblee/security/authorization/DefaultOperationRegistry.java
new file mode 100644 (file)
index 0000000..7a26fb7
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.security.authorization;
+
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * Operation registry implementation. 
+ * This implementation ignores the distinction between different types of resources
+ * and simply assumes that every operation is applicable to every type of resource. 
+ */
+public class DefaultOperationRegistry implements OperationRegistry {
+
+    private Map<String,Operation> _operations; 
+    
+    public DefaultOperationRegistry(Operation[] aOperations) { 
+        _operations = new TreeMap<String, Operation>();
+        for (Operation operation: aOperations) { 
+            _operations.put(operation.getName(), operation);
+        }
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.security.authorization.OperationRegistry#getOperations(java.lang.Class)
+     */
+    public Operation[] getOperations(Class aResourceClass) {
+        return _operations.values().toArray(new Operation[0]); 
+    }
+
+    /* (non-Javadoc)
+     * @see org.wamblee.security.authorization.OperationRegistry#encode(org.wamblee.security.authorization.Operation[])
+     */
+    public String encode(Operation[] aOperations) {
+        StringBuffer buffer = new StringBuffer(); 
+        for (Operation operation: aOperations) {
+            if ( buffer.length() > 0 ) { 
+                buffer.append(',');
+            }
+            buffer.append(operation.getName());
+        }
+        return buffer.toString();
+    }
+
+    /* (non-Javadoc)
+     * @see org.wamblee.security.authorization.OperationRegistry#decode(java.lang.Class, java.lang.String)
+     */
+    public Operation[] decode(Class aResourceClass, String aOperationsString) {
+        return decode(aOperationsString); 
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.security.authorization.OperationRegistry#decode(java.lang.String)
+     */
+    public Operation[] decode(String aOperationsString) {
+        if ( aOperationsString.length() == 0 ) { 
+            return new Operation[0]; 
+        }
+        String[] names = aOperationsString.split(",");
+        ArrayList<Operation> result = new ArrayList<Operation>(); 
+        for (String name: names) { 
+            Operation operation = _operations.get(name);
+            if ( operation == null ) { 
+                throw new IllegalArgumentException("Unknown operation '" + name + "'");
+            }
+            result.add(operation);
+        }
+        return result.toArray(new Operation[0]); 
+    }
+
+}
diff --git a/security/src/main/java/org/wamblee/security/authorization/DeleteOperation.java b/security/src/main/java/org/wamblee/security/authorization/DeleteOperation.java
new file mode 100644 (file)
index 0000000..45a78aa
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.security.authorization;
+
+/**
+ * Deletes the operation. 
+ */
+public class DeleteOperation extends AllOperation {
+
+    private static final String OPERATION = "delete";
+    
+    /**
+     * Constructs the operation. 
+     *
+     */
+    public DeleteOperation() { 
+        super(OPERATION);
+    }
+}
diff --git a/security/src/main/java/org/wamblee/security/authorization/GroupUserCondition.java b/security/src/main/java/org/wamblee/security/authorization/GroupUserCondition.java
new file mode 100644 (file)
index 0000000..de85067
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.security.authorization;
+
+import org.wamblee.persistence.AbstractPersistent;
+import org.wamblee.usermgt.User;
+
+/**
+ * Checks if a user against a specific group. 
+ */
+public class GroupUserCondition extends AbstractPersistent implements UserCondition {
+    
+    /**
+     * Group the user must be in. 
+     */
+    private String _group; 
+    
+    /**
+     * Constructs the condition. 
+     * @param aGroup Group the user must be in. 
+     */
+    public GroupUserCondition(String aGroup) {
+        _group = aGroup; 
+    }
+    
+    /**
+     * For OR mapping. 
+     *
+     */
+    protected GroupUserCondition() { 
+        _group = null; 
+    }
+
+    /* (non-Javadoc)
+     * @see org.wamblee.security.authorization.UserCondition#matches(org.wamblee.usermgt.UserAccessor)
+     */
+    public boolean matches(User aUser) { 
+        return aUser.isInGroup(_group); 
+    }
+
+    /**
+     * @return Returns the _group.
+     */
+    protected String getGroup() {
+        return _group;
+    }
+
+    /**
+     * @param _group The _group to set.
+     */
+    protected void setGroup(String aGroup) {
+        _group = aGroup;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        return "GroupUserCondition(group=" + _group + ")";
+    }
+    
+}
diff --git a/security/src/main/java/org/wamblee/security/authorization/IsaOperationCondition.java b/security/src/main/java/org/wamblee/security/authorization/IsaOperationCondition.java
new file mode 100644 (file)
index 0000000..e5a4d68
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.wamblee.security.authorization;
+
+import org.wamblee.persistence.AbstractPersistent;
+
+/**
+ * Determiens if an operation is a subclass of a specified operation.
+ */
+public class IsaOperationCondition extends AbstractPersistent implements
+        OperationCondition {
+
+    /**
+     * Operation that the other operation must be a subclass of.
+     */
+    private Class<? extends Operation> _operation;
+
+    /**
+     * Constructs the condition.
+     * 
+     * @param aOperation
+     *            Operation that an operation must be an instance of.
+     */
+    public IsaOperationCondition(Class<? extends Operation> aOperation) {
+        _operation = aOperation;
+    }
+
+    /**
+     * For OR mapping.
+     * 
+     */
+    public IsaOperationCondition() {
+        _operation = null;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.security.authorization.OperationCondition#matches(org.wamblee.security.authorization.Operation)
+     */
+    public boolean matches(Operation aOperation) {
+        return _operation.isInstance(aOperation);
+    }
+
+    /**
+     * Gets the operation as a string. For OR mapping only.
+     * 
+     * @return Operation string.
+     */
+    protected String getOperationString() {
+        if (_operation == null) {
+            return null; 
+        }
+        return _operation.getName(); 
+    }
+
+    /**
+     * Sets the operation as a string. For OR mapping only.
+     * 
+     * @param aOperation
+     *            Operation string.
+     */
+    protected void setOperationString(String aOperation) {
+        if  (aOperation == null ) { 
+            return;
+        }
+        try { 
+            _operation = (Class<? extends Operation>)Class.forName(aOperation);
+        } catch (Exception e) { 
+            throw new IllegalArgumentException("Unknown class '" + aOperation + "'");
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        return "IsaOperationCondition(operation=" + _operation.getName() + ")";
+    }
+
+}
diff --git a/security/src/main/java/org/wamblee/security/authorization/Operation.java b/security/src/main/java/org/wamblee/security/authorization/Operation.java
new file mode 100644 (file)
index 0000000..0951446
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.security.authorization;
+
+/**
+ * Represents an operation on a resource. 
+ * An operation should contain no state to be persisted since only the name of the
+ * operation is persisted. 
+ */
+public interface Operation {
+    
+    /**
+     * Gets the name of the operation. 
+     * @return Operation. 
+     */
+    String getName(); 
+}
diff --git a/security/src/main/java/org/wamblee/security/authorization/OperationCondition.java b/security/src/main/java/org/wamblee/security/authorization/OperationCondition.java
new file mode 100644 (file)
index 0000000..64a8356
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.security.authorization;
+
+import org.wamblee.persistence.Persistent;
+
+/**
+ * Checks if an operation matches a condition. 
+ */
+public interface OperationCondition extends Persistent {
+
+    
+    /**
+     * Determines if the operation matches. 
+     * @param aOperation Operation. 
+     * @return True iff the operation matches. 
+     */
+    boolean matches(Operation aOperation); 
+}
diff --git a/security/src/main/java/org/wamblee/security/authorization/OperationRegistry.java b/security/src/main/java/org/wamblee/security/authorization/OperationRegistry.java
new file mode 100644 (file)
index 0000000..ac6772b
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.security.authorization;
+
+
+/**
+ * Utility to map between a list of operations and a string based
+ * on the names of the operations.   
+ */
+public interface OperationRegistry {
+  
+    /**
+     * Gets the supported operations for a given resource class. 
+     * @param aResourceClass Resource class. 
+     * @return Supported operations for that class. 
+     */
+    Operation[] getOperations(Class aResourceClass);
+    
+    /**
+     * Converts a number of operations to a string. 
+     * @param aOperations Operations to convert. 
+     * @return String representation of the allowed operations. 
+     */
+    String encode(Operation[] aOperations);
+    
+    /**
+     * Converts an operations string to an array of operations.
+     * @param aResourceClass Resource class.  
+     * @param aOperationsString Operations string as returned by {@link #encode(Operation[])}.
+     * @return Operations array. 
+     */
+    Operation[] decode(Class aResourceClass, String aOperationsString);
+    
+    /**
+     * Converts an operations string to an array of operations.
+     * @param aOperationsString Operations string as returned by {@link #encode(Operation[])}.
+     * @return Operations array. 
+     */
+    Operation[] decode(String aOperationsString);
+    
+}
diff --git a/security/src/main/java/org/wamblee/security/authorization/PathCondition.java b/security/src/main/java/org/wamblee/security/authorization/PathCondition.java
new file mode 100644 (file)
index 0000000..0d6e6ba
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.security.authorization;
+
+import org.wamblee.persistence.Persistent;
+
+/**
+ * Checks if a path satisfies a condition. 
+ */
+public interface PathCondition extends Persistent {
+
+    /**
+     * Checks if the path matches the condition. 
+     * @param aPath Path to match. 
+     * @return True iff the path matches. 
+     */
+    boolean matches(String aPath); 
+}
diff --git a/security/src/main/java/org/wamblee/security/authorization/ReadOperation.java b/security/src/main/java/org/wamblee/security/authorization/ReadOperation.java
new file mode 100644 (file)
index 0000000..3016f34
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.security.authorization;
+
+/**
+ * Represents a read operation on a resource. 
+ */
+public class ReadOperation extends AllOperation {
+    
+    private static final String OPERATION = "read";
+    
+    /**
+     * Constructs the operation. 
+     *
+     */
+    public ReadOperation() { 
+        super(OPERATION); 
+    }
+}
diff --git a/security/src/main/java/org/wamblee/security/authorization/RegexpPathCondition.java b/security/src/main/java/org/wamblee/security/authorization/RegexpPathCondition.java
new file mode 100644 (file)
index 0000000..4941f06
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.security.authorization;
+
+import org.wamblee.persistence.AbstractPersistent;
+
+/**
+ * Condition to check whether a path  matches a given regula expression.
+ */
+public class RegexpPathCondition extends AbstractPersistent implements PathCondition {
+    
+    /**
+     * String the path must start with. 
+     */
+    private String _pattern; 
+    
+    /**
+     * Constructs the condition. 
+     * @param aPattern String the path must start with. 
+     */
+    public RegexpPathCondition(String aPattern) { 
+        _pattern = aPattern; 
+    }
+    
+    /**
+     * For OR mapping. 
+     *
+     */
+    protected RegexpPathCondition() { 
+        _pattern = null; 
+    }
+
+    /* (non-Javadoc)
+     * @see org.wamblee.security.authorization.PathCondition#matches(java.lang.String)
+     */
+    public boolean matches(String aPath) {
+        return aPath.matches(_pattern);
+    }
+
+    /**
+     * @return Returns the _path.
+     */
+    protected String getPattern() {
+        return _pattern;
+    }
+
+    /**
+     * @param aPattern The _path to set.
+     */
+    protected void setPattern(String aPattern) {
+        _pattern = aPattern;
+    }
+    
+    /* (non-Javadoc)
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        return "RegexpCondition(pattern = '" + _pattern + "')";
+    }
+}
diff --git a/security/src/main/java/org/wamblee/security/authorization/StartsWithPathCondition.java b/security/src/main/java/org/wamblee/security/authorization/StartsWithPathCondition.java
new file mode 100644 (file)
index 0000000..3096430
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.security.authorization;
+
+
+/**
+ * Condition to check whether a path starts with a given string. 
+ */
+public class StartsWithPathCondition extends RegexpPathCondition {
+    
+    /**
+     * Constructs the condition. 
+     * @param aPath String the path must start with. 
+     */
+    public StartsWithPathCondition(String aPath) { 
+        super( aPath + ".*"); 
+    }
+    
+    /**
+     * For OR mapping. 
+     *
+     */
+    protected StartsWithPathCondition() { 
+        super(); 
+    }
+    
+    /* (non-Javadoc)
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        return "StartsWithPathCondition(pattern = '" + getPattern() + "')";
+    }
+}
diff --git a/security/src/main/java/org/wamblee/security/authorization/UrlAuthorizationRule.java b/security/src/main/java/org/wamblee/security/authorization/UrlAuthorizationRule.java
new file mode 100644 (file)
index 0000000..0d22d1c
--- /dev/null
@@ -0,0 +1,264 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.wamblee.security.authorization;
+
+import static org.wamblee.security.authorization.AuthorizationResult.DENIED;
+import static org.wamblee.security.authorization.AuthorizationResult.GRANTED;
+import static org.wamblee.security.authorization.AuthorizationResult.UNDECIDED;
+import static org.wamblee.security.authorization.AuthorizationResult.UNSUPPORTED_RESOURCE;
+
+import org.apache.log4j.Logger;
+import org.wamblee.persistence.AbstractPersistent;
+import org.wamblee.usermgt.User;
+
+/**
+ * Utility base class for implementation of authentication rules based on the 
+ * <ul>
+ * <li> The path of the resource. To obtain the path of a resource, subclasses
+ *      must implement {@link #getResourcePath(Object)}. 
+ *      Whether a path is appropriate is determined by a 
+ *      {@link org.wamblee.security.authorization.PathCondition}. 
+ * </li>
+ * <li> The user identity with which the resource is accessed. 
+ *      Whether a user is appropriate is determined by
+ *      a {@link org.wamblee.security.authorization.UserCondition}. 
+ * </li>
+ * <li> The operation that is requested. 
+ *      Whether the operation is appropriate is determined by a 
+ *      {@link org.wamblee.security.authorization.OperationCondition}. 
+ * </li>
+ * </ul>
+ * 
+ * In case all three conditions match, the condition returns the configured
+ * result passed at construction (GRANTED or DENIED). If the resource is not
+ * of the specified type, the result is UNSUPPORTED_RESOURCE, otherwise, the
+ * result is UNDECIDED. 
+ */
+public abstract class UrlAuthorizationRule extends AbstractPersistent implements AuthorizationRule {
+    
+    private static final Logger LOGGER = Logger.getLogger(UrlAuthorizationRule.class);
+
+    /**
+     * Result that the rule will return in case there is a match. 
+     */
+    private AuthorizationResult _result;
+
+    /**
+     * A condition which specifies which users the rule is for. 
+     */
+    private UserCondition _userCondition; 
+
+    /**
+     * Path the rule applies for. 
+     */
+    private PathCondition _pathCondition;
+
+    /**
+     * Resource class that the rule applies for. 
+     */
+    private Class _resourceClass;
+    
+    /**
+     * Operation that this rule is for. 
+     */
+    private OperationCondition _operationCondition; 
+
+    /**
+     * Constructs an authorization rule. 
+     * IF the group and path match, then the provided result will be returned. 
+     * @param aResult Result of the authorization when the path and group match. 
+     * @param aUserCondition Condition to match users.  
+     * @param aPathCondition Condition to match paths with.  
+     * @param aResourceClass Supported resource class this is for.
+     * @param aOperationCondition Condition to match the operation with.  
+     */
+    protected UrlAuthorizationRule(AuthorizationResult aResult, UserCondition aUserCondition,
+            PathCondition aPathCondition, Class aResourceClass, OperationCondition aOperationCondition) {
+        if ( !aResult.equals(GRANTED) && !aResult.equals(DENIED)) {
+            throw new IllegalArgumentException("Only GRANTED or DENIED may be used: " + aResult);
+        }
+        _result = aResult;
+        _userCondition = aUserCondition; 
+        _pathCondition = aPathCondition;
+        _resourceClass = aResourceClass;
+        _operationCondition = aOperationCondition; 
+    }
+    
+    /**
+     * For OR mapping. 
+     *
+     */
+    protected UrlAuthorizationRule(Class aResourceClass) { 
+        _result = null; 
+        _userCondition = null; 
+        _pathCondition = null; 
+        _resourceClass = aResourceClass; 
+        _operationCondition = null; 
+    }
+    
+    /**
+     * For OR mapping. 
+     *
+     */
+    protected UrlAuthorizationRule() { 
+        _result = null; 
+        _userCondition = null; 
+        _pathCondition = null; 
+        _resourceClass = null; 
+        _operationCondition = null; 
+    }
+
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.security.authorization.AuthorizationRule#getSupportedTypes()
+     */
+    public Class[] getSupportedTypes() {
+        return new Class[] { _resourceClass };
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.security.authorization.AuthorizationRule#isAllowed(java.lang.Object,
+     *      org.wamblee.security.authorization.Operation)
+     */
+    public AuthorizationResult isAllowed(Object aResource, Operation anOperation, User aUser) {
+        if ( ! _resourceClass.isInstance(aResource)) { 
+            return UNSUPPORTED_RESOURCE; 
+        }
+        String path = getResourcePath(aResource);
+        return isAllowed(path, anOperation, aUser); 
+    }
+    
+    /**
+     * Determines if the operation is allowed on the resource. 
+     * @param aPath Path of the resource. 
+     * @param aOperation Operation to be done. 
+     * @param aUser Currently logged in user or null if no user is logged in. 
+     * @return Authorization result, 
+     */
+    protected AuthorizationResult isAllowed(String aPath, Operation aOperation, User aUser) {
+        if ( ! _pathCondition.matches(aPath) ) {
+            return UNDECIDED;
+        }
+        if ( !_operationCondition.matches(aOperation) ) {
+            return UNDECIDED; 
+        }
+        if ( !_userCondition.matches(aUser)) {
+            return UNDECIDED;
+        }
+        return _result; 
+    }
+
+    /**
+     * Gets the path of the resource. 
+     * @param aResource Resource, guaranteed to be an instance of 
+     * {@link #_resourceClass}. 
+     * @return Path of the resource. 
+     */
+    protected abstract String getResourcePath(Object aResource);
+    
+    /* (non-Javadoc)
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        return "UrlAUthorizationRule(result = " + _result + 
+           ", pathCondition = " + _pathCondition + 
+           ", userCondition = " + _userCondition +   
+           ", resourceClass = " + _resourceClass + ")";  
+    }
+    
+    /**
+     * Gets the authorization result for OR mapping. 
+     * @return Result. 
+     */
+    protected String getAuthorizationResultString() {
+        if ( _result == null ) { 
+            return null; 
+        }
+        return _result.toString(); 
+    }
+    
+    /**
+     * Sets the authorization result, for OR mapping. 
+     * @param aResult Result. 
+     */
+    protected void setAuthorizationResultString(String aResult) {
+        _result = AuthorizationResult.valueOf(aResult);
+    }
+    
+    protected String getResourceClassName() {
+        if ( _resourceClass == null ) { 
+            return "";
+        }
+        return _resourceClass.getName(); 
+    }
+    
+    protected void setResourceClassName(String aResourceClass) { 
+        try {
+            _resourceClass = Class.forName(aResourceClass);
+        } catch (ClassNotFoundException e) {
+           LOGGER.error("Cannot find resource class '" + aResourceClass + "'", e); 
+           throw new IllegalArgumentException(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * @return Returns the _operationCondition.
+     */
+    public OperationCondition getOperationCondition() {
+        return _operationCondition;
+    }
+
+    /**
+     * @param aOperationCondition The _operationCondition to set.
+     */
+    protected void setOperationCondition(OperationCondition aOperationCondition) {
+        _operationCondition = aOperationCondition;
+    }
+
+    /**
+     * @return Returns the _pathCondition.
+     */
+    public PathCondition getPathCondition() {
+        return _pathCondition;
+    }
+
+    /**
+     * @param aPathCondition The _pathCondition to set.
+     */
+    protected void setPathCondition(PathCondition aPathCondition) {
+        _pathCondition = aPathCondition;
+    }
+
+    /**
+     * @return Returns the _userCondition.
+     */
+    public UserCondition getUserCondition() {
+        return _userCondition;
+    }
+
+    /**
+     * @param aUserCondition The _userCondition to set.
+     */
+    protected void setUserCondition(UserCondition aUserCondition) {
+        _userCondition = aUserCondition;
+    }
+}
diff --git a/security/src/main/java/org/wamblee/security/authorization/UserCondition.java b/security/src/main/java/org/wamblee/security/authorization/UserCondition.java
new file mode 100644 (file)
index 0000000..3a215ed
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.security.authorization;
+
+import org.wamblee.persistence.Persistent;
+import org.wamblee.usermgt.User;
+
+/**
+ * Condition used to match a user against a specified set of users. 
+ */
+public interface UserCondition extends Persistent {
+
+    /**
+     * Determines if the condition matches. 
+     * @param aUser user to check. 
+     * @return True if the condition matches, false otherwise. 
+     */
+    boolean matches(User aUser);
+}
diff --git a/security/src/main/java/org/wamblee/security/authorization/WriteOperation.java b/security/src/main/java/org/wamblee/security/authorization/WriteOperation.java
new file mode 100644 (file)
index 0000000..a56a059
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.security.authorization;
+
+/**
+ * Represents a write operation on a resource. 
+ */
+public class WriteOperation extends AllOperation {
+    
+    private static final String OPERATION = "write";
+    
+    /**
+     * Constructs the operation. 
+     *
+     */
+    public WriteOperation() { 
+        super(OPERATION); 
+    }
+
+}
diff --git a/security/src/main/java/org/wamblee/security/authorization/hibernate/PersistentAuthorizationService.java b/security/src/main/java/org/wamblee/security/authorization/hibernate/PersistentAuthorizationService.java
new file mode 100644 (file)
index 0000000..e54877a
--- /dev/null
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.wamblee.security.authorization.hibernate;
+
+import java.util.List;
+
+import org.springframework.orm.hibernate3.HibernateTemplate;
+import org.wamblee.persistence.AbstractPersistent;
+import org.wamblee.persistence.hibernate.HibernateSupport;
+import org.wamblee.security.authorization.AuthorizationRule;
+import org.wamblee.security.authorization.AuthorizationService;
+import org.wamblee.security.authorization.DefaultAuthorizationService;
+import org.wamblee.security.authorization.Operation;
+import org.wamblee.usermgt.UserAccessor;
+
+/**
+ * Authorization service with persistent storage.
+ * This is a wrapper for {@link org.wamblee.security.authorization.DefaultAuthorizationService}
+ * which refreshes the state of the service at certain time intervals. 
+ */
+public class PersistentAuthorizationService extends AbstractPersistent
+        implements AuthorizationService {
+
+    /**
+     * Name of query to find the service by name.
+     */
+    private static final String FIND_QUERY = "findAuthorizationServiceByName";
+
+    /**
+     * Name of the query parameter for the service name.
+     */
+    private static final String NAME_PARAM = "name";
+
+    /**
+     * Authorization service to use.
+     */
+    private DefaultAuthorizationService _service;
+
+    /**
+     * Hibernate template to use.
+     */
+    private HibernateTemplate _template;
+
+    /**
+     * User accessor.
+     */
+    private UserAccessor _userAccessor;
+    
+    /**
+     * Name of the service. 
+     */
+    private String _name; 
+
+    /**
+     * Refresh interval in milliseconds. 
+     */
+    private final long _refreshInterval;
+    
+    /**
+     * Last refresh time. 
+     */
+    private long _lastRefreshTime; 
+
+    /**
+     * Constructs the persistent service.
+     * 
+     * @param aName
+     *            Name of the service.
+     * @param aTemplate
+     *            Hibernate template for hibernate usage.
+     * @param aAccessor
+     *            User accessor.
+     * @param aRefresh
+     *            Whether or not to refresh the state of the service at the
+     *            start of every operation.
+     */
+    public PersistentAuthorizationService(String aName,
+            HibernateTemplate aTemplate, UserAccessor aAccessor,
+            long aRefreshInterval) {
+        _template = aTemplate;
+        _refreshInterval = aRefreshInterval;
+        _lastRefreshTime = System.currentTimeMillis(); 
+        _userAccessor = aAccessor;
+        _name = aName; 
+    }
+
+    /**
+     * Initialize service if needed. 
+     */
+    private void initialize() {
+        if (_service == null) {
+            List<DefaultAuthorizationService> result = _template
+                    .findByNamedQueryAndNamedParam(FIND_QUERY, NAME_PARAM,
+                            _name);
+
+            if (result.size() > 1) {
+                throw new IllegalArgumentException(
+                        "Returned more than one service for name '" + _name
+                                + "' (" + result.size() + ")");
+            }
+
+            if (result.size() == 0) {
+                _service = new DefaultAuthorizationService(_userAccessor, _name);
+                _template.persist(_service);
+            } else {
+                _service = result.get(0);
+                _service.setUserAccessor(_userAccessor);
+            }
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.security.authorization.AuthorizationService#isAllowed(java.lang.Object,
+     *      org.wamblee.security.authorization.Operation)
+     */
+    public boolean isAllowed(Object aResource, Operation aOperation) {
+        initialize();
+        refresh();
+        return _service.isAllowed(aResource, aOperation);
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.security.authorization.AuthorizationService#check(T, org.wamblee.security.authorization.Operation)
+     */
+    public <T> T check(T aResource, Operation aOperation) {
+        initialize();
+        refresh();
+        return _service.check(aResource, aOperation);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.security.authorization.AuthorizationService#getRules()
+     */
+    public AuthorizationRule[] getRules() {
+        initialize();
+        refresh();
+        return _service.getRules();
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.security.authorization.AuthorizationService#appendRule(org.wamblee.security.authorization.AuthorizationRule)
+     */
+    public void appendRule(AuthorizationRule aRule) {
+        initialize();
+        refresh();
+        _service.appendRule(aRule);
+        save();
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.security.authorization.AuthorizationService#removeRule(int)
+     */
+    public void removeRule(int aIndex) {
+        initialize();
+        refresh();
+        _service.removeRule(aIndex);
+        save();
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.security.authorization.AuthorizationService#insertRuleAfter(int,
+     *      org.wamblee.security.authorization.AuthorizationRule)
+     */
+    public void insertRuleAfter(int aIndex, AuthorizationRule aRule) {
+        initialize();
+        refresh();
+        _service.insertRuleAfter(aIndex, aRule);
+        save();
+    }
+
+    /**
+     * Refreshes the state of the service through hibernate.
+     * 
+     */
+    private synchronized void refresh() {
+        long time = System.currentTimeMillis(); 
+        if ( time - _lastRefreshTime > _refreshInterval ) { 
+            _template.refresh(_service);
+            _lastRefreshTime = time; 
+        }
+    }
+
+    /**
+     * Saves any changes to the service state if necessary.
+     */
+    private void save() {
+        HibernateSupport.merge(_template, _service);
+    }
+}
diff --git a/security/src/main/java/org/wamblee/security/encryption/Md5HexMessageDigester.java b/security/src/main/java/org/wamblee/security/encryption/Md5HexMessageDigester.java
new file mode 100644 (file)
index 0000000..f6d970e
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.wamblee.security.encryption;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import org.apache.commons.codec.binary.Hex;
+
+/**
+ * MD5 Hex encoder. 
+ */
+public class Md5HexMessageDigester implements MessageDigester {
+    
+    /**
+     * Constructs the message digester. 
+     *
+     */
+    public Md5HexMessageDigester() {
+        // Empty
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.security.MessageDigester#hash(java.lang.String)
+     */
+    public String hash(String aValue) {
+        try {
+            MessageDigest digest = MessageDigest.getInstance("MD5");
+            byte[] result = digest.digest(aValue.getBytes());
+            char[] charResult = new Hex().encodeHex(result);
+            return new String(charResult);
+        } catch (NoSuchAlgorithmException e) {
+            throw new IllegalArgumentException("MD5 not supported????");
+        }
+    }
+
+}
diff --git a/security/src/main/java/org/wamblee/security/encryption/MessageDigester.java b/security/src/main/java/org/wamblee/security/encryption/MessageDigester.java
new file mode 100644 (file)
index 0000000..685a4c7
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.security.encryption;
+
+/**
+ * Utility class that encapsulates a message digest method. 
+ */
+public interface MessageDigester {
+    
+    /**
+     * Computes a message digest for a value and encodes it in some way. 
+     * @param aValue Value to compute digest for. 
+     * @return Encoded digest. 
+     */
+    String hash(String aValue); 
+
+}
diff --git a/security/src/main/java/org/wamblee/usermgt/AbstractUserSet.java b/security/src/main/java/org/wamblee/usermgt/AbstractUserSet.java
new file mode 100644 (file)
index 0000000..1a3096f
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.usermgt;
+
+import static org.wamblee.usermgt.UserMgtException.Reason.DUPLICATE_USER;
+
+import org.wamblee.security.encryption.MessageDigester;
+
+/**
+ * User set base class. 
+ */
+public abstract class AbstractUserSet implements UserSet {
+    
+    /**
+     * Password validator. 
+     */
+    private NameValidator _passwordValidator; 
+    
+    /**
+     * Password encoder. 
+     */
+    private MessageDigester _passwordEncoder; 
+
+    
+    protected AbstractUserSet(NameValidator aPasswordValidator, 
+            MessageDigester aPasswordEncoder) { 
+        _passwordValidator = aPasswordValidator;
+        _passwordEncoder = aPasswordEncoder;
+    }
+    
+    /**
+     * Sets the password validtor and encoder in the user. 
+     * @param aUser User. 
+     */
+    protected void setPasswordInfo(User aUser) { 
+        aUser.setPasswordValidator(_passwordValidator);
+        aUser.setPasswordEncoder(_passwordEncoder);
+    }
+
+    /* (non-Javadoc)
+     * @see org.wamblee.usermgt.UserSet#createUser(java.lang.String, java.lang.String, org.wamblee.usermgt.Group)
+     */
+    public User createUser(String aUsername, String aPassword, Group aGroup) throws UserMgtException {
+        User user = new User(aUsername, aPassword, aGroup, _passwordValidator, _passwordEncoder);
+        if (contains(user)) {
+            throw new UserMgtException(DUPLICATE_USER, user);
+        }
+        add(user);
+        return user;
+    }
+}
diff --git a/security/src/main/java/org/wamblee/usermgt/Group.java b/security/src/main/java/org/wamblee/usermgt/Group.java
new file mode 100644 (file)
index 0000000..d8f9f83
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.usermgt;
+
+import java.io.Serializable;
+
+import org.wamblee.persistence.AbstractPersistent;
+
+/**
+ * Represents a group. 
+ */
+public class Group extends AbstractPersistent implements Serializable, Comparable {
+
+    /**
+     * Group name. 
+     */
+    private String _name;  
+
+    /**
+     * Constructs the group. 
+     * @param aName
+     */
+    Group(String aName) {
+        super();
+        _name = aName; 
+    }
+    
+    public Group(Group aGroup) { 
+        super(aGroup); 
+        _name = aGroup._name;
+    }
+    
+    protected Group() { 
+        super(); 
+        _name = null; 
+    }
+    
+    /**
+     * Gets the name of the group. 
+     * @return Group name. 
+     */
+    public String getName() { 
+        return _name; 
+    }
+    
+    /**
+     * Sets the group name. 
+     * @param aName Group name. 
+     */
+    void setName(String aName) {
+        _name = aName; 
+    }
+    
+    /* (non-Javadoc)
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(Object aGroup) {
+        if ( !( aGroup instanceof Group )) { 
+            return false; 
+        }
+        return _name.equals(((Group)aGroup)._name);
+    }
+    
+    /* (non-Javadoc)
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        return _name.hashCode(); 
+    }
+    
+    /* (non-Javadoc)
+     * @see java.lang.Comparable#compareTo(T)
+     */
+    public int compareTo(Object aGroup) {
+        return _name.compareTo(((Group)aGroup)._name);
+    }
+    
+    /* (non-Javadoc)
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        return "Group(pk = " + getPrimaryKey() + ", name=" + _name + ")";
+    }
+}
diff --git a/security/src/main/java/org/wamblee/usermgt/GroupSet.java b/security/src/main/java/org/wamblee/usermgt/GroupSet.java
new file mode 100644 (file)
index 0000000..e67d7bc
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.usermgt;
+
+import java.util.Set;
+
+/**
+ * Represents a set of groups. A typical implemnetation would be, a readonly implementation 
+ * defined in a configuration file or a list of groups defined in a database. 
+ */
+public interface GroupSet {
+    
+    /**
+     * Must be called when the group has been modified to notify the group set. 
+     * @param aGroup Group that was modified. 
+     */
+    void groupModified(Group aGroup); 
+    
+    /**
+     * Finds the group by name. 
+     * @param aName Group name. 
+     * @return Group or null if not found. 
+     */
+    Group find(String aName);
+    
+    /**
+     * Determines if the group exists. 
+     * @param aGroup Group. 
+     * @return True iff the group exists. 
+     */
+    boolean contains(Group aGroup);
+    /**
+     * Adds a group. If the group already exists, the existing group set
+     * is left unchanged.   
+     * @param aGroup Group.
+     */
+    boolean add(Group aGroup);
+    
+    /**
+     * Removes a group. If the group does not exist, this method is a no-op. 
+     * @param aGroup Group to remove.
+     * @return True if the group was removed, false otherwise.  
+     */
+    boolean remove(Group aGroup);
+   
+    /**
+     * Returns the current groups. 
+     * @return Groups. 
+     */
+    Set<Group> list();
+
+    /**
+     * @return The number of groups. 
+     */
+    int size(); 
+}
diff --git a/security/src/main/java/org/wamblee/usermgt/InMemoryGroupSet.java b/security/src/main/java/org/wamblee/usermgt/InMemoryGroupSet.java
new file mode 100644 (file)
index 0000000..04f8be2
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.usermgt;
+
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * In-memory group set implementation. 
+ */
+public class InMemoryGroupSet implements GroupSet {
+    
+    /**
+     * Groups. 
+     */
+    private Set<Group> _groups; 
+    
+    /**
+     * Constructs an empty group set. 
+     */
+    public InMemoryGroupSet() { 
+        _groups = new TreeSet<Group>();
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.usermgt.GroupSet#groupModified(org.wamblee.usermgt.Group)
+     */
+    public void groupModified(Group aGroup) {
+        _groups.remove(aGroup); 
+        _groups.add(aGroup);
+    }
+
+    /* (non-Javadoc)
+     * @see org.wamblee.usermgt.GroupSet#find(java.lang.String)
+     */
+    public Group find(String aName) {
+        for (Group group: _groups) { 
+            if ( group.getName().equals(aName)) { 
+                return new Group(group);
+            }
+        }
+        return null; 
+    }
+
+    /* (non-Javadoc)
+     * @see org.wamblee.usermgt.GroupSet#contains(org.wamblee.usermgt.Group)
+     */
+    public boolean contains(Group aGroup) {
+        return _groups.contains(aGroup);
+    }
+
+    /* (non-Javadoc)
+     * @see org.wamblee.usermgt.GroupSet#add(org.wamblee.usermgt.Group)
+     */
+    public boolean add(Group aGroup) {
+        return _groups.add(aGroup);
+    }
+
+    /* (non-Javadoc)
+     * @see org.wamblee.usermgt.GroupSet#remove(org.wamblee.usermgt.Group)
+     */
+    public boolean remove(Group aGroup) {
+        return _groups.remove(aGroup);
+    }
+
+    /* (non-Javadoc)
+     * @see org.wamblee.usermgt.GroupSet#list()
+     */
+    public Set<Group> list() {
+        Set<Group> list = new TreeSet<Group>(); 
+        for (Group group: _groups) { 
+            list.add(new Group(group));
+        }
+        return list;
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.usermgt.GroupSet#size()
+     */
+    public int size() {
+        return _groups.size(); 
+    }
+
+}
diff --git a/security/src/main/java/org/wamblee/usermgt/InMemoryUserSet.java b/security/src/main/java/org/wamblee/usermgt/InMemoryUserSet.java
new file mode 100644 (file)
index 0000000..0ccbba7
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.wamblee.usermgt;
+
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.wamblee.security.encryption.MessageDigester;
+
+/**
+ * In-memory user set.
+ */
+public class InMemoryUserSet extends AbstractUserSet {
+
+    /**
+     * Users. All users in this set have their password validator and encoder set. 
+     */
+    private Set<User> _users;
+
+    /**
+     * Constructs an empty user set.
+     */
+    public InMemoryUserSet(NameValidator aPasswordValidator, MessageDigester aPasswordEncoder) {
+        super(aPasswordValidator, aPasswordEncoder);
+        _users = new TreeSet<User>();
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.usermgt.UserSet#userModified(org.wamblee.usermgt.User)
+     */
+    public void userModified(User aUser) {
+        _users.remove(aUser);
+       setPasswordInfo(aUser); 
+        _users.add(aUser);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.usermgt.UserSet#find(java.lang.String)
+     */
+    public User find(String aName) {
+        for (User user : _users) {
+            if (user.getName().equals(aName)) {
+                return new User(user);
+            }
+        }
+        return null;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.usermgt.UserSet#add(org.wamblee.usermgt.User)
+     */
+    public boolean add(User aUser) {
+        setPasswordInfo(aUser);
+        return _users.add(aUser);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.usermgt.UserSet#contains(org.wamblee.usermgt.User)
+     */
+    public boolean contains(User aUser) {
+        return _users.contains(aUser);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.usermgt.UserSet#remove(org.wamblee.usermgt.User)
+     */
+    public boolean remove(User aUser) {
+        return _users.remove(aUser);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.usermgt.UserSet#list()
+     */
+    public Set<User> list() {
+        Set<User> list = new TreeSet<User>();
+        for (User user : _users) {
+            list.add(new User(user));
+        }
+        return list;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.usermgt.UserSet#list(org.wamblee.usermgt.Group)
+     */
+    public Set<User> list(Group aGroup) {
+        Set<User> result = new TreeSet<User>();
+        for (User user : _users) {
+            if (user.getGroups().contains(aGroup)) {
+                result.add(new User(user));
+            }
+        }
+        return result;
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.usermgt.UserSet#size()
+     */
+    public int size() {
+        return _users.size(); 
+    }
+
+}
diff --git a/security/src/main/java/org/wamblee/usermgt/JaasUserAccessor.java b/security/src/main/java/org/wamblee/usermgt/JaasUserAccessor.java
new file mode 100644 (file)
index 0000000..56ae836
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.wamblee.usermgt;
+
+import java.security.AccessController;
+import java.security.Principal;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+
+/**
+ * Implementation of the user accessor that retrieves user information
+ * from JAAS. 
+ */
+public class JaasUserAccessor implements UserAccessor {
+
+    /**
+     * User administration to use. 
+     */
+    private UserAdministration _admin;
+
+    /**
+     * Class of the JAAS user principal. 
+     */
+    private Class _userPrincipalClass;
+
+    /**
+     * Constructs user accessor. 
+     * @param aAdmin User administration. 
+     * @param aUserClassName Class name of the user principal. 
+     */
+    public JaasUserAccessor(UserAdministration aAdmin, String aUserClassName) {
+        _admin = aAdmin;
+        try {
+            _userPrincipalClass = Class.forName(aUserClassName);
+            if ( !Principal.class.isAssignableFrom(_userPrincipalClass)) {
+                throw new IllegalArgumentException("Specified class '" + aUserClassName + "' is not a subclass of '" +
+                        Principal.class.getName());
+            }
+        } catch (ClassNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.usermgt.UserAccessor#getCurrentUser()
+     */
+    public User getCurrentUser() {
+        Subject subject = Subject.getSubject(AccessController.getContext());
+        if (subject == null) {
+            return null;
+        }
+        Principal userPrincipal = getUserPrincipal(subject);
+      
+        return _admin.getUser(userPrincipal.getName());
+    }
+
+    /**
+     * Gets the user principal from the subject. 
+     * @param subject Subject. 
+     * @return User principal. 
+     * @throws IllegalArgumentException In case there is a duplicate principal or the principal was not found. 
+     */
+    private Principal getUserPrincipal(Subject subject) {
+        Set<Principal> principals = subject.getPrincipals();
+        Principal userPrincipal = null;  
+        for ( Principal principal: principals) { 
+            if ( principal.getClass().equals(_userPrincipalClass)) { 
+                if ( userPrincipal != null ) { 
+                    throw new IllegalArgumentException(
+                            "Multiple principals for class '" + _userPrincipalClass + "', subject: " + subject);                   
+                }
+                userPrincipal = principal; 
+            }
+        }
+        if ( userPrincipal == null ) { 
+            throw new IllegalArgumentException(
+                    "No user principal found for class '" + _userPrincipalClass + "', subject: " + subject);
+        }
+        return userPrincipal;
+    }
+
+}
diff --git a/security/src/main/java/org/wamblee/usermgt/NameValidator.java b/security/src/main/java/org/wamblee/usermgt/NameValidator.java
new file mode 100644 (file)
index 0000000..cd066a0
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.usermgt;
+
+/**
+ * Validator of names.
+ */
+public interface NameValidator {
+
+    /**
+     * Validates a name. 
+     * @param aName Name
+     * @throws UserMgtException In case the name is invalid. 
+     */
+    void validate(String aName) throws UserMgtException; 
+}
diff --git a/security/src/main/java/org/wamblee/usermgt/RegexpNameValidator.java b/security/src/main/java/org/wamblee/usermgt/RegexpNameValidator.java
new file mode 100644 (file)
index 0000000..fdc220f
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.usermgt;
+
+import org.wamblee.usermgt.UserMgtException.Reason;
+
+/**
+ * Validation of names based on a regular expression. 
+ */
+public class RegexpNameValidator implements NameValidator {
+    
+    /**
+     * Convenience pattern for an id. 
+     */
+    public static final String ID_PATTERN = "[a-zA-Z]+[a-zA-Z0-9]*";
+    
+    /**
+     * Convenience pattern for a password consisting of at least 6 characters. 
+     */
+    public static final String PASSWORD_PATTERN = ".{6}.*";
+    
+    /**
+     * Pattern to use. 
+     */
+    private String _pattern;
+    
+    /**
+     * Reason to use when validation fails. 
+     */
+    private Reason _reason; 
+    
+    /**
+     * Message to report. 
+     */
+    private String _message; 
+    
+    /**
+     * Validates a regular expression. 
+     * @param aPattern Pattern that names must comply to. 
+     * @param aReason Reason to report when validation fails. 
+     * @param aMessage Message to report. 
+     */
+    public RegexpNameValidator(String aPattern, Reason aReason, String aMessage) { 
+        _pattern = aPattern; 
+        _reason = aReason;
+        _message = aMessage; 
+    }
+    
+    /**
+     * Convenience constructor with all string parameters. Useful for configuration 
+     * in Spring. 
+     * @param aPattern Pattern to use.
+     * @param aReason Reason. 
+     * @param aMessage Message. 
+     */
+    public RegexpNameValidator(String aPattern, String aReason, String aMessage) { 
+        this(aPattern, Reason.valueOf(aReason), aMessage);
+    }
+
+    /* (non-Javadoc)
+     * @see org.wamblee.usermgt.NameValidator#validate(java.lang.String)
+     */
+    public void validate(String aName) throws UserMgtException {
+        if ( !aName.matches(_pattern)) { 
+            throw new UserMgtException(_reason, _message); 
+        }
+    }
+
+}
diff --git a/security/src/main/java/org/wamblee/usermgt/User.java b/security/src/main/java/org/wamblee/usermgt/User.java
new file mode 100644 (file)
index 0000000..f239c14
--- /dev/null
@@ -0,0 +1,295 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.wamblee.usermgt;
+
+import java.io.Serializable;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.wamblee.persistence.AbstractPersistent;
+import org.wamblee.security.encryption.MessageDigester;
+import org.wamblee.usermgt.UserMgtException.Reason;
+
+/**
+ * Represents a user.
+ * The methods for managing the groups of the user have package scope. 
+ * Managing the groups of the user should be done through the 
+ * {@link org.wamblee.usermgt.UserAdministration} interface. 
+ */
+public class User extends AbstractPersistent implements Serializable, Comparable {
+
+    /**
+     * User name. 
+     */
+    private String _name;
+
+    /**
+     * Password. 
+     */
+    private String _password;
+
+    /**
+     * Groups the user belongs to. 
+     */
+    private Set<Group> _groups;
+    
+    /**
+     * Password validator. 
+     */
+    private NameValidator _passwordValidator; 
+
+    /**
+     * Password encoder. 
+     */
+    private MessageDigester _passwordEncoder;
+    
+    /**
+     * Constructs the user. 
+     * @param aName User name. 
+     * @param aPassword Password. 
+     * @param aGroup Group the user belongs to.  
+     */
+    User(String aName, String aPassword, Group aGroup, NameValidator aPasswordValidator, 
+            MessageDigester aPasswordEncoder) throws UserMgtException {
+        super(); 
+        _name = aName;
+        aPasswordValidator.validate(aPassword);
+        _password = aPasswordEncoder.hash(aPassword);
+        _groups = new TreeSet<Group>(); 
+        _groups.add(aGroup);
+        _passwordValidator = aPasswordValidator;
+        _passwordEncoder = aPasswordEncoder;
+    }
+    
+    public User(User aUser) { 
+        super(aUser); 
+        _name = aUser._name;
+        _password = aUser._password;
+        _groups = new TreeSet<Group>(); 
+        for (Group group: aUser._groups) {
+            _groups.add(new Group(group)); 
+        }
+        _passwordValidator = aUser._passwordValidator; 
+        _passwordEncoder = aUser._passwordEncoder;
+    }
+    
+    User() { 
+        super(); 
+        _name = null; 
+        _password = null; 
+        _groups = null; 
+        _passwordValidator = null; 
+        _passwordEncoder = null;
+    }
+    
+    /**
+     * Sets the password validator. 
+     * @param aPasswordValidator Validator. 
+     */
+    public void setPasswordValidator(NameValidator aPasswordValidator) { 
+        _passwordValidator = aPasswordValidator;
+    }
+    
+    /**
+     * Sets the password encoder. 
+     * @param aPasswordEncoder Encoder. 
+     */
+    public void setPasswordEncoder(MessageDigester aPasswordEncoder) { 
+        _passwordEncoder = aPasswordEncoder; 
+    }
+
+    /**
+     * @return Returns the _password.
+     */
+    String getPassword() {
+        return _password;
+    }
+    
+    /**
+     * Checks the password. 
+     * @param aPassword Password to check. 
+     * @throws UserMgtException In case the password is incorrect. 
+     */
+    public void checkPassword(String aPassword) throws UserMgtException {
+        String encoded = _passwordEncoder.hash(aPassword);
+        if ( !_password.equals(encoded) ) { 
+            throw new UserMgtException(Reason.INVALID_PASSWORD, this);
+        }
+    }
+    
+    /**
+     * Changes the password. 
+     * @param aOldPassword Old password. 
+     * @param aNewPassword New password. 
+     * @throws UserMgtException In case the old password is incorrect. 
+     */
+    public void changePassword(String aOldPassword, String aNewPassword) throws UserMgtException {
+        checkPassword(aOldPassword);
+        _passwordValidator.validate(aNewPassword);
+        setPassword(aNewPassword);
+    }
+
+    /**
+     * @param aPassword
+     *            The password to set.
+     */
+    public void setPassword(String aPassword) throws UserMgtException {
+        _passwordValidator.validate(aPassword);
+        _password = _passwordEncoder.hash(aPassword);
+    }
+    
+    /**
+     * For OR mapping. 
+     * @return Password. 
+     */
+    protected String getPasswordString() { 
+        return _password;
+    }
+    
+    /**
+     * For OR mapping. 
+     * @param aPassword Password. 
+     */
+    protected void setPasswordString(String aPassword) { 
+        _password = aPassword; 
+    }
+
+    /**
+     * @return Returns the _user.
+     */
+    public String getName() {
+        return _name;
+    }
+
+    /**
+     * @param aName
+     *            The username to set.
+     */
+    void setName(String aName) {
+        _name = aName;
+    }
+
+    /**
+     * Gets the groups the user belongs to. 
+     * @return Groups.
+     */
+    public Set<Group> getGroups() {
+        Set<Group> result = new TreeSet<Group>(); 
+        result.addAll(_groups);
+        return result; 
+    }
+    
+    /**
+     * Checks whether the user belongs to the given group. 
+     * @param aGroup Group. 
+     * @return True if the user belongs to the group.
+     */
+    public boolean isInGroup(Group aGroup) {
+        return _groups.contains(aGroup);
+    }
+    
+    /**
+     * Checks whether the user belongs to the given group. 
+     * @param aGroup Group. 
+     * @return True if the user belongs to the group.
+     */
+    public boolean isInGroup(String aGroup) {
+        return _groups.contains(new Group(aGroup));
+    }
+    
+    /**
+     * Gets the group set. For OR mapping. 
+     * @return set of groups. 
+     */
+    Set<Group> getGroupSet() { 
+        return _groups;
+    }
+
+    /**
+     * Sets the groups the user belongs to, for OR mapping. 
+     * @param aGroups Groups.
+     */
+    void setGroupSet(Set<Group> aGroups) {
+        _groups = aGroups;
+    }
+
+    /**
+     * Adds the user to a group. 
+     * @param aGroup Group to add the user to.
+     * @throws UserMgtException In case the user already belongs to the group.  
+     */
+    void addGroup(Group aGroup) throws UserMgtException {
+        if (_groups.contains(aGroup)) {
+            throw new UserMgtException(Reason.USER_ALREADY_IN_GROUP, aGroup);
+        }
+        _groups.add(aGroup);
+    }
+
+    /**
+     * Removes the user from a group. 
+     * @param aGroup Group. 
+     * @throws UserMgtException In case the user does not belong to the group.
+     */
+    void removeGroup(Group aGroup) throws UserMgtException {
+        if (!_groups.contains(aGroup)) {
+            throw new UserMgtException(Reason.USER_NOT_IN_GROUP, this, aGroup);
+        }
+        if ( _groups.size() == 1 ) { 
+            throw new UserMgtException(Reason.USER_MUST_BE_IN_A_GROUP, this, aGroup); 
+        }
+        _groups.remove(aGroup);
+    }
+    
+    /* (non-Javadoc)
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(Object aUser) {
+        if ( !(aUser instanceof User)) {
+            return false; 
+        }
+        User user = (User)aUser; 
+        return _name.equals(user._name);
+    }
+    
+    /* (non-Javadoc)
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        return _name.hashCode(); 
+    }
+    
+    /* (non-Javadoc)
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        String result = "User(name=" + _name + ", password=" + _password;
+        for (Group group: _groups) { 
+            result += ", group=" + group; 
+        }
+        return result + ")"; 
+    }
+    
+    /* (non-Javadoc)
+     * @see java.lang.Comparable#compareTo(T)
+     */
+    public int compareTo(Object aUser) {
+        return _name.compareTo(((User)aUser)._name);
+    }
+}
diff --git a/security/src/main/java/org/wamblee/usermgt/UserAccessor.java b/security/src/main/java/org/wamblee/usermgt/UserAccessor.java
new file mode 100644 (file)
index 0000000..d36b6d3
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.usermgt;
+
+/**
+ * Interface for accessing the currently logged in user. 
+ */
+public interface UserAccessor {
+    /**
+     * Gets the current user. 
+     * @return Currently logged in user or null if no user is found. 
+     */
+    User getCurrentUser(); 
+}
diff --git a/security/src/main/java/org/wamblee/usermgt/UserAdminInitializer.java b/security/src/main/java/org/wamblee/usermgt/UserAdminInitializer.java
new file mode 100644 (file)
index 0000000..dee3454
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.wamblee.usermgt;
+
+import java.security.NoSuchAlgorithmException;
+
+import org.apache.log4j.Logger;
+
+/**
+ * User administration initializer. It populates the user administration with a
+ * number of groups and users but only in case no users exist.
+ */
+public class UserAdminInitializer {
+    
+    private static final Logger LOGGER = Logger.getLogger(UserAdminInitializer.class);
+    
+    /**
+     * Initializes the user administration in case no users are present.
+     * 
+     */
+    public UserAdminInitializer(UserAdministration aAdmin, String[] aUsers,
+            String[] aGroups, String[] aPasswords) throws UserMgtException, NoSuchAlgorithmException {
+        if (aUsers.length != aGroups.length
+                || aUsers.length != aPasswords.length) {
+            throw new IllegalArgumentException(
+                    "Array sizes for users, groups, and passwords differ: "
+                            + aUsers.length + "," + aGroups.length + ","
+                            + aPasswords.length);
+
+        }
+        if (aAdmin.getUserCount() == 0) {
+            initialize(aAdmin, aUsers, aGroups, aPasswords);
+        }
+       
+    }
+
+    /**
+     * Adds the specified users and groups to the user administration. 
+     * @param aAdmin User administration. 
+     * @param aUsers Users. 
+     * @param aGroups Groups. 
+     * @param aPasswords Passwords. 
+     * @throws UserMgtException In case of a problem creating users or groups. 
+     */
+    private void initialize(UserAdministration aAdmin, String[] aUsers,
+            String[] aGroups, String[] aPasswords) throws UserMgtException {
+        for (int i = 0; i < aUsers.length; i++) {
+            String user = aUsers[i];
+            String group = aGroups[i];
+            String password = aPasswords[i];
+            
+            if (aAdmin.getUser(user) == null) {
+                // must create user.
+                Group groupObj = aAdmin.getGroup(group);
+                if (groupObj == null) {
+                    // must create group
+                    LOGGER.info("Creating group: " + group);
+                    groupObj = aAdmin.createGroup(group);
+                }
+                assert groupObj != null; 
+                
+                LOGGER.info("Creating user: " + user + " password: " + password);
+                aAdmin.createUser(user, password, groupObj);
+            }
+        }
+    }
+}
diff --git a/security/src/main/java/org/wamblee/usermgt/UserAdministration.java b/security/src/main/java/org/wamblee/usermgt/UserAdministration.java
new file mode 100644 (file)
index 0000000..74d2dff
--- /dev/null
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.wamblee.usermgt;
+
+import java.util.Set;
+
+/**
+ * Interface for user administration. Manages the users and groups. 
+ */
+public interface UserAdministration {
+    
+    /**
+     * Creates a new user. 
+     * @param aUser Username. 
+     * @param aPassword Password. 
+     * @param aGroup Group. 
+     * @return User. 
+     * @throws UserMgtException In case there is a conflict with an existing user. 
+     */
+    User createUser(String aUser, String aPassword, Group aGroup) throws UserMgtException; 
+    
+    /**
+     * Creates a new group. 
+     * @param aName Group name. 
+     * @return Group
+     * @throws UserMgtException In case there is a conflict with an existing group. 
+     */
+    Group createGroup(String aName) throws UserMgtException;
+    
+    /** 
+     * @return Number of users. 
+     */
+    int getUserCount(); 
+    
+    /**
+     * @return Number of groups. 
+     */
+    int getGroupCount(); 
+    
+    /**
+     * Must be called when the user is modified. 
+     * @param aUser User.
+     */
+    void userModified(User aUser);
+    
+    /**
+     * Must be called when the group is modified. 
+     * @param aGroup Group. 
+     */
+    void groupModified(Group aGroup); 
+
+    /**
+     * Gets the user for a given name. 
+     * @param aName User name. 
+     * @return User or null if not found.   
+     */
+    User getUser(String aName); 
+    
+    /**
+     * Gets the group for a given group name. 
+     * @param aName Group name. 
+     * @return Group or null if not found. 
+     */
+    Group getGroup(String aName); 
+    
+    /**
+     * Get the users. 
+     * @return All known users. 
+     */
+    Set<User> getUsers(); 
+    
+    /**
+     * Gets the users for a given group. 
+     * @param aGroup Group. 
+     * @return Set of users (always non-null).
+     */
+    Set<User> getUsers(Group aGroup);
+    
+    /**
+     * Gets all known groups. 
+     * @return Groups. 
+     */
+    Set<Group> getGroups();
+    
+    /**
+     * Renames a user. 
+     * @param aUser User object for which user name must be changed. 
+     * @param aUserName New user name. 
+     * @throws UserMgtException In case the user is not known or the new user 
+     *    name is already in use by another user.  
+     */
+    void renameUser(User aUser, String aUserName) throws UserMgtException;
+    
+    /**
+     * Renames a group. 
+     * @param aGroup Group to rename. 
+     * @param aGroupName New name for the group. 
+     * @throws UserMgtException In case the new group name is already used by 
+     *    another group of if the existing group is unknown.  
+     */
+    void renameGroup(Group aGroup, String aGroupName) throws UserMgtException;
+    
+    /**
+     * Removes the user. 
+     * @param aUser User to remove. 
+     * @throws UserMgtException In case the user does not exist.  
+     */
+    void removeUser(User aUser) throws UserMgtException;
+    
+    /**
+     * Removes the group. 
+     * @param aGroup Group to remove. 
+     * @throws UserMgtException In case there are still users that are in the given group. 
+     */
+    void removeGroup(Group aGroup) throws UserMgtException;
+    
+    /**
+     * Adds a user to a group. 
+     * @param aUser User. 
+     * @param aGroup Group. 
+     * @throws UserMgtException In case the user or group or not known or if the user 
+     *   is already part of the group. 
+     */
+    void addUserToGroup(User aUser, Group aGroup) throws UserMgtException;
+    
+    /**
+     * Removes a user from a group. 
+     * @param aUser User
+     * @param aGroup Group
+     * @throws UserMgtException In case the user or group are unknown or if the user 
+     *    is not part of the group. 
+     */
+    void removeUserFromGroup(User aUser, Group aGroup) throws UserMgtException; 
+}
+
diff --git a/security/src/main/java/org/wamblee/usermgt/UserAdministrationImpl.java b/security/src/main/java/org/wamblee/usermgt/UserAdministrationImpl.java
new file mode 100644 (file)
index 0000000..a29d6d4
--- /dev/null
@@ -0,0 +1,287 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.wamblee.usermgt;
+
+import static org.wamblee.usermgt.UserMgtException.Reason.DUPLICATE_GROUP;
+import static org.wamblee.usermgt.UserMgtException.Reason.DUPLICATE_USER;
+import static org.wamblee.usermgt.UserMgtException.Reason.GROUP_STILL_OCCUPIED;
+import static org.wamblee.usermgt.UserMgtException.Reason.TRIVIAL_RENAME;
+import static org.wamblee.usermgt.UserMgtException.Reason.UNKNOWN_GROUP;
+import static org.wamblee.usermgt.UserMgtException.Reason.UNKNOWN_USER;
+
+import java.util.Set;
+
+/**
+ * Administration of users and groups.
+ */
+public class UserAdministrationImpl implements UserAdministration {
+   
+    /**
+     * All known users.
+     */
+    private UserSet _users;
+
+    /**
+     * All known groups.
+     */
+    private GroupSet _groups;
+    
+    /**
+     * Validator for user names. 
+     */
+    private NameValidator _userValidator;
+    
+    /**
+     * Validator for group names. 
+     */
+    private NameValidator _groupValidator;
+    
+    /**
+     * Constructs empty user administration.
+     * 
+     */
+    public UserAdministrationImpl(UserSet aUsers, GroupSet aGroups, NameValidator aUserValidator, 
+              NameValidator aGroupValidator) { 
+        _users = aUsers;
+        _groups = aGroups;
+        _userValidator = aUserValidator;
+        _groupValidator = aGroupValidator;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.usermgt.UserAdministration#createUser(java.lang.String,
+     *      java.lang.String)
+     */
+    public User createUser(String aUser, String aPassword, Group aGroup)
+            throws UserMgtException {
+        _userValidator.validate(aUser);
+        checkGroup(aGroup);
+        User user = _users.createUser(aUser, aPassword, aGroup);
+        return new User(user);
+    }
+    
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.usermgt.UserAdministration#createGroup(java.lang.String)
+     */
+    public Group createGroup(String aName) throws UserMgtException {
+        _groupValidator.validate(aName);
+        Group group = new Group(aName);
+        if (_groups.contains(group)) {
+            throw new UserMgtException(DUPLICATE_GROUP, group);
+        }
+        _groups.add(group);
+        return new Group(group);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.usermgt.UserAdministration#userModified(org.wamblee.usermgt.User)
+     */
+    public void userModified(User aUser) {
+        _users.userModified(aUser);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.usermgt.UserAdministration#groupModified(org.wamblee.usermgt.Group)
+     */
+    public void groupModified(Group aGroup) {
+        _groups.groupModified(aGroup);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.usermgt.UserAdministration#getUser(java.lang.String)
+     */
+    public User getUser(String aName) {
+        return _users.find(aName);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.usermgt.UserAdministration#getGroup(java.lang.String)
+     */
+    public Group getGroup(String aName) {
+        return _groups.find(aName);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.usermgt.UserAdministration#getUsers()
+     */
+    public Set<User> getUsers() {
+        return _users.list();
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.usermgt.UserAdministration#getUsers(org.wamblee.usermgt.Group)
+     */
+    public Set<User> getUsers(Group aGroup) {
+        return _users.list(aGroup);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.usermgt.UserAdministration#getGroups()
+     */
+    public Set<Group> getGroups() {
+        return _groups.list();
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.usermgt.UserAdministration#removeUser(org.wamblee.usermgt.User)
+     */
+    public void removeUser(User aUser) throws UserMgtException {
+        checkUser(aUser);
+        _users.remove(aUser);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.usermgt.UserAdministration#removeGroup(org.wamblee.usermgt.Group)
+     */
+    public void removeGroup(Group aGroup) throws UserMgtException {
+        checkGroup(aGroup);
+        if (getUsers(aGroup).size() > 0) {
+            throw new UserMgtException(GROUP_STILL_OCCUPIED, aGroup);
+        }
+        _groups.remove(aGroup);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.usermgt.UserAdministration#renameUser(org.wamblee.usermgt.User,
+     *      java.lang.String)
+     */
+    public void renameUser(User aUser, String aUserName)
+            throws UserMgtException {
+        checkUser(aUser);
+        if (aUser.getName().equals(aUserName)) {
+            throw new UserMgtException(TRIVIAL_RENAME, aUser);
+        }
+        if (_users.find(aUserName) != null) {
+            throw new UserMgtException(DUPLICATE_USER, aUser);
+        }
+        _userValidator.validate(aUserName);
+        // we are modifying the user so we should re-insert it into the set
+        // after renaming it.
+        _users.remove(aUser);
+        aUser.setName(aUserName);
+        _users.add(aUser);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.usermgt.UserAdministration#renameGroup(org.wamblee.usermgt.Group,
+     *      java.lang.String)
+     */
+    public void renameGroup(Group aGroup, String aGroupName)
+            throws UserMgtException {
+        checkGroup(aGroup);
+        if (aGroup.getName().equals(aGroupName)) {
+            throw new UserMgtException(TRIVIAL_RENAME, aGroup);
+        }
+        if (_groups.find(aGroupName) != null) {
+            throw new UserMgtException(DUPLICATE_GROUP, aGroup);
+        }
+        _groupValidator.validate(aGroupName);
+        // we are renaming the group so we should re-insert it into the set
+        // after renaming it.
+        _groups.remove(aGroup);
+        aGroup.setName(aGroupName);
+        _groups.add(aGroup);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.usermgt.UserAdministration#addUserToGroup(org.wamblee.usermgt.User,
+     *      org.wamblee.usermgt.Group)
+     */
+    public void addUserToGroup(User aUser, Group aGroup)
+            throws UserMgtException {
+        checkUser(aUser);
+        checkGroup(aGroup);
+        aUser.addGroup(aGroup);
+        _users.userModified(aUser);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.usermgt.UserAdministration#removeUserFromGroup(org.wamblee.usermgt.User,
+     *      org.wamblee.usermgt.Group)
+     */
+    public void removeUserFromGroup(User aUser, Group aGroup)
+            throws UserMgtException {
+        checkUser(aUser);
+        checkGroup(aGroup);
+        aUser.removeGroup(aGroup);
+        _users.userModified(aUser);
+    }
+
+    /**
+     * @param aUser
+     * @throws UserMgtException
+     */
+    private void checkUser(User aUser) throws UserMgtException {
+        if (!_users.contains(aUser)) {
+            throw new UserMgtException(UNKNOWN_USER, aUser);
+        }
+    }
+
+    /**
+     * @param aGroup
+     * @throws UserMgtException
+     */
+    private void checkGroup(Group aGroup) throws UserMgtException {
+        if (!_groups.contains(aGroup)) {
+            throw new UserMgtException(UNKNOWN_GROUP, aGroup);
+        }
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.usermgt.UserAdministration#getUserCount()
+     */
+    public int getUserCount() {
+        return _users.size(); 
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.usermgt.UserAdministration#getGroupCount()
+     */
+    public int getGroupCount() {
+        return _groups.size(); 
+    }
+}
diff --git a/security/src/main/java/org/wamblee/usermgt/UserMgtException.java b/security/src/main/java/org/wamblee/usermgt/UserMgtException.java
new file mode 100644 (file)
index 0000000..4a95caa
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.usermgt;
+
+import java.util.EnumMap;
+
+/**
+ * User management exception. 
+ */
+public class UserMgtException extends Exception {
+    
+    static final long serialVersionUID = 5585349754997507529L; 
+    
+    /**
+     * Possible causes for the exception. 
+     *
+     */
+    public enum Reason { 
+        UNKNOWN_USER, 
+        UNKNOWN_GROUP,
+        DUPLICATE_USER, 
+        DUPLICATE_GROUP, 
+        USER_ALREADY_IN_GROUP,
+        USER_NOT_IN_GROUP,
+        TRIVIAL_RENAME,
+        INVALID_PASSWORD, 
+        GROUP_STILL_OCCUPIED, 
+        USER_MUST_BE_IN_A_GROUP,
+        INVALID_USERNAME,
+        INVALID_GROUPNAME
+    }
+   
+    /**
+     * Mapping of enum to exception message text. 
+     */
+    private static final EnumMap<Reason,String> MESSAGES = new EnumMap<Reason,String>(Reason.class);
+    
+    static {
+        MESSAGES.put(Reason.UNKNOWN_USER, "Unknown user");
+        MESSAGES.put(Reason.UNKNOWN_GROUP, "Unknown group");
+        MESSAGES.put(Reason.DUPLICATE_USER, "Duplicate user");
+        MESSAGES.put(Reason.DUPLICATE_GROUP, "Duplicate group");
+        MESSAGES.put(Reason.USER_ALREADY_IN_GROUP, "User already in group");
+        MESSAGES.put(Reason.USER_NOT_IN_GROUP, "User not in group");
+        MESSAGES.put(Reason.TRIVIAL_RENAME, "Trivial rename");
+        MESSAGES.put(Reason.INVALID_PASSWORD, "Invalid password");
+        MESSAGES.put(Reason.GROUP_STILL_OCCUPIED, "Group still occupied");
+        MESSAGES.put(Reason.USER_MUST_BE_IN_A_GROUP, "User must be in at least one group");
+        MESSAGES.put(Reason.INVALID_USERNAME, "Invalid user name");
+        MESSAGES.put(Reason.INVALID_GROUPNAME, "Invalid group name");
+    }
+    
+    /**
+     * Cause of the exception. 
+     */
+    private Reason _cause; 
+    
+    /**
+     * User or null if no user is relevant for the problem. 
+     */
+    private User _user; 
+    
+    /**
+     * Group or null if no group is relevant for the problem. 
+     */
+    private Group _group; 
+
+    public UserMgtException(Reason aCause, String aMessage) {
+        super(MESSAGES.get(aCause) + ": " + aMessage);
+        _cause = aCause; 
+    }
+    
+    public UserMgtException(Reason aCause, User aUser) {
+        this(aCause, "for user '" + aUser.getName() + "'");
+        _user = aUser; 
+    }
+    
+    public UserMgtException(Reason aCause, Group aGroup) {
+        this(aCause, "for group '" + aGroup.getName() + "'");
+        _group = aGroup; 
+    }
+    
+    public UserMgtException(Reason aCause, User aUser, Group aGroup) {
+        this(aCause, "for user '" + aUser.getName() + "' and group '" + aGroup.getName() + "'");
+        _user = aUser; 
+        _group = aGroup; 
+    }
+
+    /**
+     * Gets the cause of the problem. 
+     * @return Cause. 
+     */
+    public Reason getReason() {
+        return _cause; 
+    }
+    
+    /**
+     * Gets the user for which the problem occurred. 
+     * @return User or null if not applicable.  
+     */
+    public User getUser() {
+        return _user; 
+    }
+    
+    /**
+     * Gets the group for which the problem occured. 
+     * @return Group or null if not applicable. 
+     */
+    public Group getGroup() {
+        return _group;
+    }   
+}
diff --git a/security/src/main/java/org/wamblee/usermgt/UserSet.java b/security/src/main/java/org/wamblee/usermgt/UserSet.java
new file mode 100644 (file)
index 0000000..ffff4ab
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.usermgt;
+
+import java.util.Set;
+
+/**
+ * Represents a set of users.
+ * Typical implementations would be an implementation based on a static configuration file or 
+ * an implementation backed by a database.  
+ */
+public interface UserSet {
+    
+    /**
+     * Creates a user. 
+     * @param aUsername User name. 
+     * @param aPassword Password. 
+     * @param aGroup Group.
+     * @return New user.  
+     * @throws UserMgtException In case the user cannot be created. 
+     */ 
+    User createUser(String aUsername, String aPassword, Group aGroup) throws UserMgtException; 
+    
+    /**
+     * Must be called whenever a user object has been modified to notify the
+     * user set.  
+     * @param aUser Modified user. 
+     */
+    void userModified(User aUser); 
+    
+    /**
+     * Finds  user. 
+     * @param aName Username. 
+     * @return User or null if not found. 
+     */
+    User find(String aName);
+    
+    /**
+     * Checks if a user exists. 
+     * @param aUser User. 
+     * @return True iff the user exists. 
+     */
+    boolean contains(User aUser); 
+    
+    /**
+     * Adds a user. If the user already exists, the user details are updated with that
+     * of the specified user object. 
+     * @param aUser User to add. 
+     */
+    boolean add(User aUser);
+    
+    /**
+     * Removes a user. If the user does not exist, nothing happens. 
+     * @param aUser
+     */
+    boolean remove(User aUser);
+    
+    /**
+     * Lists the current users. 
+     * @return Users. 
+     */
+    Set<User> list(); 
+    
+    /**
+     * Lists the users belonging to a particular group. 
+     * @param aGroup Group. 
+     * @return Groups. 
+     */
+    Set<User> list(Group aGroup);
+
+    /**
+     * 
+     * @return The number of users. 
+     */
+    int size(); 
+}
diff --git a/security/src/main/java/org/wamblee/usermgt/hibernate/HibernateGroupSet.java b/security/src/main/java/org/wamblee/usermgt/hibernate/HibernateGroupSet.java
new file mode 100644 (file)
index 0000000..5e3de65
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.usermgt.hibernate;
+
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.wamblee.persistence.hibernate.HibernateSupport;
+import org.wamblee.usermgt.Group;
+import org.wamblee.usermgt.GroupSet;
+
+/**
+ * Set of groups backed by the database. 
+ */
+public class HibernateGroupSet extends HibernateSupport implements GroupSet {
+    
+    
+    private static final String QUERY_FIND_BY_NAME = "findGroupByName";
+    
+    private static final String PARAM_NAME = "name";
+    
+    private static final String QUERY_COUNT_GROUPS = "countGroups";
+    
+    public HibernateGroupSet() {
+        // Empty
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.usermgt.GroupSet#groupModified(org.wamblee.usermgt.Group)
+     */
+    public void groupModified(Group aGroup) {
+        assert aGroup.getPrimaryKey() != null; 
+        super.merge(aGroup);        
+    }
+
+    /* (non-Javadoc)
+     * @see org.wamblee.usermgt.GroupSet#find(java.lang.String)
+     */
+    public Group find(String aName) {
+        List list = getHibernateTemplate().findByNamedQueryAndNamedParam(QUERY_FIND_BY_NAME, PARAM_NAME, aName);
+        if ( list.size() > 1 ) { 
+            throw new RuntimeException("More than one group with the same name '" + aName + "'");
+        }
+        if ( list.size() == 0 ) { 
+            return null; 
+        }
+        return new Group((Group)list.get(0));
+    }
+
+    /* (non-Javadoc)
+     * @see org.wamblee.usermgt.GroupSet#contains(org.wamblee.usermgt.Group)
+     */
+    public boolean contains(Group aGroup) {
+        return find(aGroup.getName()) != null; 
+    }
+
+    /* (non-Javadoc)
+     * @see org.wamblee.usermgt.GroupSet#add(org.wamblee.usermgt.Group)
+     */
+    public boolean add(Group aGroup) {
+        assert aGroup.getPrimaryKey() == null; 
+        if ( contains(aGroup) ) { 
+            return false; 
+        }
+        super.merge(aGroup);
+        return true; 
+    }
+
+    /* (non-Javadoc)
+     * @see org.wamblee.usermgt.GroupSet#remove(org.wamblee.usermgt.Group)
+     */
+    public boolean remove(Group aGroup) {
+        assert aGroup.getPrimaryKey() != null; 
+        if ( !contains(aGroup)) { 
+            return false; 
+        }
+        Group group = (Group) getHibernateTemplate().merge(aGroup); 
+        getHibernateTemplate().delete(group);
+        aGroup.setPrimaryKey(null); 
+        aGroup.setPersistedVersion(-1);
+        return true; 
+    }
+
+    /* (non-Javadoc)
+     * @see org.wamblee.usermgt.GroupSet#list()
+     */
+    public Set<Group> list() {
+        Set<Group> groups = new TreeSet<Group>(); 
+        List<Group> list = getHibernateTemplate().loadAll(Group.class);
+        for (Group group: list) { 
+            groups.add(new Group(group));
+        }
+        return groups; 
+    }
+
+    /* (non-Javadoc)
+     * @see org.wamblee.usermgt.GroupSet#size()
+     */
+    public int size() {
+        Long result = (Long) getHibernateTemplate().findByNamedQuery(QUERY_COUNT_GROUPS).get(0);
+        return result.intValue(); 
+    }
+}
diff --git a/security/src/main/java/org/wamblee/usermgt/hibernate/HibernateUserSet.java b/security/src/main/java/org/wamblee/usermgt/hibernate/HibernateUserSet.java
new file mode 100644 (file)
index 0000000..1c9237d
--- /dev/null
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.wamblee.usermgt.hibernate;
+
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.hibernate.SessionFactory;
+import org.springframework.orm.hibernate3.HibernateTemplate;
+import org.wamblee.cache.Cache;
+import org.wamblee.persistence.hibernate.HibernateSupport;
+import org.wamblee.security.encryption.MessageDigester;
+import org.wamblee.usermgt.AbstractUserSet;
+import org.wamblee.usermgt.Group;
+import org.wamblee.usermgt.NameValidator;
+import org.wamblee.usermgt.User;
+
+/**
+ * User set backed by the database.
+ */
+public class HibernateUserSet extends AbstractUserSet {
+
+    private static final String QUERY_FIND_BY_NAME = "findUserByName";
+
+    private static final String QUERY_FIND_BY_GROUP_NAME = "findUserByGroupName";
+
+    private static final String PARAM_NAME = "name";
+    
+    private static final String QUERY_COUNT_USERS = "countUsers"; 
+
+    /**
+     * Cache of users. Every user in the cache has its password validator and encoder set.  
+     */
+    private Cache<String, User> _cache;
+    
+    /** 
+     * Spring hibernate support. 
+     */
+    private HibernateSupport _hibernateSupport; 
+
+    /**
+     * Constructs a user set backed by the database.
+     * @param aCache User cache to use. 
+     */
+    public HibernateUserSet(Cache<String,User> aCache, 
+            NameValidator aPasswordValidator, MessageDigester aPasswordEncoder) {
+        super(aPasswordValidator, aPasswordEncoder);
+        _cache = aCache;
+        _hibernateSupport = new HibernateSupport();
+    }
+    
+    /**
+     * Sets the session factory. 
+     * @param aFactory Session factory. 
+     */
+    public void setSessionFactory(SessionFactory aFactory) { 
+        _hibernateSupport.setSessionFactory(aFactory);
+    }
+    
+    /**
+     * Gets the hibernate template. 
+     * @return Hibernate template. 
+     */
+    private HibernateTemplate getHibernateTemplate() { 
+        return _hibernateSupport.getHibernateTemplate();
+    }
+   
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.usermgt.UserSet#userModified(org.wamblee.usermgt.User)
+     */
+    public void userModified(User aUser) {
+        assert aUser.getPrimaryKey() != null;
+        _hibernateSupport.merge(aUser);
+        _cache.remove(aUser.getName());
+        setPasswordInfo(aUser);
+        _cache.put(aUser.getName(), new User(aUser));
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.usermgt.UserSet#find(java.lang.String)
+     */
+    public User find(String aName) {
+        User user = _cache.get(aName);
+        if (user != null) {
+            return user;
+        }
+        List result = getHibernateTemplate().findByNamedQueryAndNamedParam(
+                QUERY_FIND_BY_NAME, PARAM_NAME, aName);
+        if (result.size() > 1) {
+            throw new RuntimeException(
+                    "Implementation problem, more than one user with the same name!");
+        }
+        if (result.size() == 0) {
+            return null;
+        }
+        user = (User) result.get(0);
+        setPasswordInfo(user);
+        _cache.put(aName, user);
+        return new User(user);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.usermgt.UserSet#contains(org.wamblee.usermgt.User)
+     */
+    public boolean contains(User aUser) {
+        return find(aUser.getName()) != null;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.usermgt.UserSet#add(org.wamblee.usermgt.User)
+     */
+    public boolean add(User aUser) {
+        assert aUser.getPrimaryKey() == null;
+        if (contains(aUser)) {
+            return false;
+        }
+        getHibernateTemplate().saveOrUpdate(aUser);
+        setPasswordInfo(aUser);
+        _cache.put(aUser.getName(), aUser);
+        return true;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.usermgt.UserSet#remove(org.wamblee.usermgt.User)
+     */
+    public boolean remove(User aUser) {
+        assert aUser.getPrimaryKey() != null;
+        if (!contains(aUser)) {
+            return false;
+        }
+        User user = (User) getHibernateTemplate().merge(aUser);
+        getHibernateTemplate().delete(user);
+        aUser.setPersistedVersion(-1);
+        aUser.setPrimaryKey(null);
+        _cache.remove(aUser.getName());
+        return true;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.usermgt.UserSet#list()
+     */
+    public Set<User> list() {
+        Set<User> users = new TreeSet<User>();
+        List<User> list = getHibernateTemplate().loadAll(User.class);
+        for (User user : list) {
+            setPasswordInfo(user);
+            users.add(new User(user));
+        }
+        return users;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.usermgt.UserSet#list(org.wamblee.usermgt.Group)
+     */
+    public Set<User> list(Group aGroup) {
+        Set<User> users = new TreeSet<User>();
+        List<User> list = getHibernateTemplate().findByNamedQueryAndNamedParam(
+                QUERY_FIND_BY_GROUP_NAME, PARAM_NAME, aGroup.getName());
+        for (User user : list) {
+            setPasswordInfo(user);
+            users.add(new User(user));
+        }
+        return users;
+    }
+
+    /* (non-Javadoc)
+     * @see org.wamblee.usermgt.UserSet#size()
+     */
+    public int size() {
+        Long result = (Long)getHibernateTemplate().findByNamedQuery(QUERY_COUNT_USERS).get(0);
+        return result.intValue(); 
+    }
+}
diff --git a/security/src/test/java/org/wamblee/security/authorization/AuthorizationServiceTest.java b/security/src/test/java/org/wamblee/security/authorization/AuthorizationServiceTest.java
new file mode 100644 (file)
index 0000000..f3791a3
--- /dev/null
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.security.authorization;
+
+import static org.wamblee.security.authorization.AuthorizationResult.DENIED;
+import static org.wamblee.security.authorization.AuthorizationResult.GRANTED;
+
+import org.wamblee.persistence.hibernate.HibernateMappingFiles;
+import org.wamblee.test.SpringConfigFiles;
+import org.wamblee.test.SpringTestCase;
+import org.wamblee.usermgt.UserAccessor;
+
+/**
+ * Tests the authorization service. 
+ */
+public class AuthorizationServiceTest extends SpringTestCase {
+    
+    private AuthorizationRule _rule1; 
+    private AuthorizationRule _rule2;  
+    private AuthorizationRule _rule3; 
+    private AuthorizationService _service; 
+    
+    
+    public AuthorizationServiceTest() { 
+        super(SpringConfigFiles.class, HibernateMappingFiles.class);
+    }
+    
+    public AuthorizationServiceTest(Class<? extends SpringConfigFiles>aSpringFiles, 
+            Class<? extends HibernateMappingFiles> aMappings) {
+        super(aSpringFiles, aMappings);
+    }
+    
+    protected AuthorizationService getService() { 
+        return _service; 
+    }
+    
+    /* (non-Javadoc)
+     * @see junit.framework.TestCase#setUp()
+     */
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        
+        _rule1 = createRule(GRANTED, "users", "/oni/", AllOperation.class); 
+        _rule2 = createRule(DENIED, "users", "/abc/", ReadOperation.class); 
+        _rule3 = createRule(GRANTED, "users", "/abc/", AllOperation.class);
+        
+        _service = createService(); 
+        _service.appendRule(_rule1); 
+        _service.appendRule(_rule2);
+        _service.appendRule(_rule3);
+    }
+    
+    protected void resetTestRules() { 
+        ((TestAuthorizationRule)_rule1).reset(); 
+        ((TestAuthorizationRule)_rule2).reset(); 
+        ((TestAuthorizationRule)_rule3).reset(); 
+    }
+    
+    protected UserAccessor createUserAccessor() { 
+        return new TestUserAccessor(); 
+    }
+
+    /**
+     * Creates an authorization service with some rules for testing. . 
+     * @return Authorization service. 
+     */
+    protected AuthorizationService createService() {
+        DefaultAuthorizationService service = new DefaultAuthorizationService() ;
+        service.setUserAccessor(createUserAccessor());
+        return service;
+    }
+    
+    protected AuthorizationRule createRule(AuthorizationResult aResult, String aGroup, String aPath, Class<? extends Operation> aOperation) { 
+        return new TestAuthorizationRule(aResult, aGroup, aPath, aOperation);
+    }
+    
+    protected void checkMatchCount(int aCount, AuthorizationRule aRule) { 
+        assertEquals( aCount,  ((TestAuthorizationRule)aRule).getMatchCount()); 
+    }
+    
+    protected Object createResource(String aPath) { 
+        return new TestResource(aPath);
+    }
+    
+    protected void checkRuleCount(int aCount) { 
+        // Empty
+    }
+    
+    /**
+     * Several checks to verify the outcome of matching against the first rule. 
+     *
+     */
+    public void testFirstRuleGrants() { 
+        assertTrue( _service.isAllowed(createResource("/oni/xyz.jpg"), new ReadOperation())); 
+        checkMatchCount(1, _rule1);
+        assertTrue(_service.isAllowed(createResource("/oni/xyz.jpg"), new WriteOperation())); 
+        checkMatchCount(2, _rule1);
+        assertTrue(_service.isAllowed(createResource("/oni/xyz.jpg"), new DeleteOperation())); 
+        checkMatchCount(3, _rule1);
+        assertTrue(_service.isAllowed(createResource("/oni/xyz.jpg"), new CreateOperation())); 
+        checkMatchCount(4, _rule1);
+        checkMatchCount(0, _rule2);
+        checkMatchCount(0, _rule3);
+    }
+    
+    /**
+     * Verify that a match with the second rule leads to a denial of authorization. 
+     *
+     */
+    public void testSecondRuleDenies() {
+        assertFalse(_service.isAllowed(createResource("/abc/xyz.jpg"), new ReadOperation())); 
+        checkMatchCount(0, _rule1);
+        checkMatchCount(1, _rule2);
+        checkMatchCount(0, _rule3);
+    }
+    
+    /**
+     * Verifies that the third rule is used when appropriate and that it grants access. 
+     *
+     */
+    public void testThirdRuleGrants() { 
+        assertTrue(_service.isAllowed(createResource("/abc/xyz.jpg"), new WriteOperation())); 
+        checkMatchCount(0, _rule1); 
+        checkMatchCount(0, _rule2);
+        checkMatchCount(1, _rule3); 
+    }
+    
+    /**
+     * Removes a rule and checks it is removed. 
+     *
+     */
+    public void testRemoveRule() { 
+        checkRuleCount(3);
+        assertTrue(_service.isAllowed(createResource("/abc/xyz.jpg"), new WriteOperation())); 
+        _service.removeRule(2); 
+        assertFalse(_service.isAllowed(createResource("/abc/xyz.jpg"), new WriteOperation()));
+        checkRuleCount(2);
+    }
+    
+    /**
+     * Inserts a rule and checks it is inserted. 
+     *
+     */
+    public void testInsertRule() {
+        checkRuleCount(3);
+        assertFalse(_service.isAllowed(createResource("/janse/xyz.jpg"), new WriteOperation()));     
+        _service.appendRule(createRule(GRANTED, "users", "/janse/", WriteOperation.class));
+        assertTrue(_service.isAllowed(createResource("/janse/xyz.jpg"), new WriteOperation()));
+        checkRuleCount(4);
+            
+    }
+   
+    /**
+     * Gets the rules. Verifies that all rules are obtained. 
+     *
+     */
+    public void testGetRules() { 
+        AuthorizationRule[] rules = _service.getRules();
+        assertEquals(3, rules.length); 
+    }
+    
+    /**
+     * Verifies that when no rules match, access is denied. 
+     *
+     */
+    public void testNoRulesSupportResource() { 
+        assertFalse(_service.isAllowed(createResource("/xyxyxyxy"), new ReadOperation()));
+        checkMatchCount(0, _rule1); 
+        checkMatchCount(0, _rule2);
+        checkMatchCount(0, _rule3); 
+    }
+}
diff --git a/security/src/test/java/org/wamblee/security/authorization/DefaultOperationRegistryTest.java b/security/src/test/java/org/wamblee/security/authorization/DefaultOperationRegistryTest.java
new file mode 100644 (file)
index 0000000..07b147b
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.security.authorization;
+
+import junit.framework.TestCase;
+
+/**
+ * Test of the operation registry. 
+ */
+public class DefaultOperationRegistryTest extends TestCase {
+    
+    private OperationRegistry _registry; 
+    
+    /* (non-Javadoc)
+     * @see junit.framework.TestCase#setUp()
+     */
+    @Override
+    protected void setUp() throws Exception {
+        
+        _registry = new DefaultOperationRegistry(new Operation[] {
+                new AllOperation(), 
+                new ReadOperation(), 
+                new WriteOperation(), 
+                new DeleteOperation(), 
+                new CreateOperation()
+        });     
+    }
+    
+    /**
+     * Tests encoding and decoding of no operations. 
+     *
+     */
+    public void testEncodeDecodeNooperations() { 
+        assertEquals("", _registry.encode(new Operation[0]));
+        assertEquals(0, _registry.decode(Object.class, "").length);
+    }
+    
+    /**
+     * Verifies that encoding of operations into a string works. 
+     *
+     */
+    public void testEncode() { 
+        assertEquals("read,write", _registry.encode(new Operation[] { new ReadOperation(), new WriteOperation() })); 
+    }
+    
+    /**
+     * Verifies that decoding of operation from a string works. 
+     *
+     */
+    public void testDecode() { 
+        Operation[] operations = _registry.decode(Object.class, "read,write");
+        assertTrue( operations[0] instanceof ReadOperation); 
+        assertTrue( operations[1] instanceof WriteOperation); 
+    }
+    
+    /**
+     * Verifies that an IllegalArgumentException occurs when attempting to decode
+     * an operation that is not known. 
+     *
+     */
+    public void testDecodeUnknownOperation() { 
+        try {  
+            _registry.decode(Object.class, "bla"); 
+            fail(); 
+        } catch (IllegalArgumentException e) { 
+            // ok
+        }
+    }
+}
diff --git a/security/src/test/java/org/wamblee/security/authorization/RegexpPathConditionTest.java b/security/src/test/java/org/wamblee/security/authorization/RegexpPathConditionTest.java
new file mode 100644 (file)
index 0000000..92a2f08
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.security.authorization;
+
+import junit.framework.TestCase;
+
+/**
+ * Test for regular expression matching. 
+ */
+public class RegexpPathConditionTest extends TestCase {
+
+    /**
+     * Various tests. 
+     *
+     */
+    public void testMatch() { 
+       PathCondition cond = new RegexpPathCondition("abc"); 
+       assertTrue(cond.matches("abc"));
+       assertFalse(cond.matches("xabcx"));
+       cond = new RegexpPathCondition("/[a-z]*/.*"); 
+       assertTrue(cond.matches("/hallo/xyz")); 
+    }
+}
diff --git a/security/src/test/java/org/wamblee/security/authorization/StartsWithPathConditionTest.java b/security/src/test/java/org/wamblee/security/authorization/StartsWithPathConditionTest.java
new file mode 100644 (file)
index 0000000..3754773
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.security.authorization;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for StartsWithPathCondition. 
+ */
+public class StartsWithPathConditionTest extends TestCase {
+
+    /**
+     * Various tests. 
+     */
+    public void testMatches() {
+        PathCondition cond = new StartsWithPathCondition("/hallo");
+        assertTrue(cond.matches("/hallo"));
+        assertTrue(cond.matches("/hallox"));
+        assertTrue(cond.matches("/hallo/abc"));
+    }
+}
diff --git a/security/src/test/java/org/wamblee/security/authorization/TestAuthorizationRule.java b/security/src/test/java/org/wamblee/security/authorization/TestAuthorizationRule.java
new file mode 100644 (file)
index 0000000..fe02560
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.security.authorization;
+
+import static org.wamblee.security.authorization.AuthorizationResult.DENIED;
+import static org.wamblee.security.authorization.AuthorizationResult.GRANTED;
+
+import org.wamblee.usermgt.User;
+
+/**
+ * Test authorization rule that also counts the number of times the rule matches. 
+ */
+public class TestAuthorizationRule extends UrlAuthorizationRule {
+    
+    /**
+     * Counts the number of matches. 
+     */
+    private int _matches = 0; 
+
+    public TestAuthorizationRule( AuthorizationResult aResult, String aGroup,
+            String aPath, Class<? extends Operation> aOperation) {
+       super(aResult, new GroupUserCondition(aGroup), 
+               new StartsWithPathCondition(aPath), TestResource.class, new IsaOperationCondition(aOperation));
+    }
+    
+    protected TestAuthorizationRule() { 
+        super();
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.security.authorization.UrlAuthorizationRule#getPath(java.lang.Object)
+     */
+    @Override
+    protected String getResourcePath(Object aResource) {
+        return ((TestResource)aResource).getPath();
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.security.authorization.UrlAuthorizationRule#isAllowed(java.lang.Object, org.wamblee.security.authorization.Operation, org.wamblee.usermgt.UserAccessor)
+     */
+    @Override
+    public AuthorizationResult isAllowed(Object aResource, Operation anOperation, User aUser) {
+        
+        AuthorizationResult result = super.isAllowed(aResource, anOperation, aUser);
+        if ( result.equals(GRANTED) || result.equals(DENIED)) { 
+            _matches++; 
+        }
+        return result; 
+    }
+    
+    public int getMatchCount() { 
+        return _matches; 
+    }
+    
+    public void reset() { 
+        _matches = 0; 
+    }
+   
+}
diff --git a/security/src/test/java/org/wamblee/security/authorization/TestResource.java b/security/src/test/java/org/wamblee/security/authorization/TestResource.java
new file mode 100644 (file)
index 0000000..4708a69
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.security.authorization;
+
+/**
+ * A test resource for authorization. 
+ */
+public class TestResource {
+
+    private String _path; 
+    
+    public TestResource(String aPath) { 
+        _path = aPath;     
+    }
+    
+    public String getPath() { 
+        return _path; 
+    }
+}
diff --git a/security/src/test/java/org/wamblee/security/authorization/TestUserAccessor.java b/security/src/test/java/org/wamblee/security/authorization/TestUserAccessor.java
new file mode 100644 (file)
index 0000000..d6b6f87
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.wamblee.security.authorization;
+
+import junit.framework.TestCase;
+
+import org.wamblee.security.encryption.Md5HexMessageDigester;
+import org.wamblee.usermgt.Group;
+import org.wamblee.usermgt.InMemoryGroupSet;
+import org.wamblee.usermgt.InMemoryUserSet;
+import org.wamblee.usermgt.RegexpNameValidator;
+import org.wamblee.usermgt.User;
+import org.wamblee.usermgt.UserAccessor;
+import org.wamblee.usermgt.UserAdministration;
+import org.wamblee.usermgt.UserAdministrationImpl;
+import org.wamblee.usermgt.UserMgtException;
+import org.wamblee.usermgt.UserMgtException.Reason;
+
+/**
+ * User access that always returns a user that belongs to
+ * a fixed group. 
+ */
+public class TestUserAccessor implements UserAccessor {
+    
+    private static final String USER = "erik"; 
+    private static final String PASSWORD = "abc123";
+    private static final String GROUP = "users";
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.usermgt.UserAccessor#getCurrentUser()
+     */
+    public User getCurrentUser() {
+        UserAdministration admin = new UserAdministrationImpl(
+                new InMemoryUserSet(    new RegexpNameValidator(RegexpNameValidator.PASSWORD_PATTERN, Reason.INVALID_PASSWORD, "Password must contain at least 6 characters"),
+                        new Md5HexMessageDigester()), new InMemoryGroupSet(),
+                new RegexpNameValidator(RegexpNameValidator.ID_PATTERN, Reason.INVALID_USERNAME, "Invalid user"), 
+                new RegexpNameValidator(RegexpNameValidator.ID_PATTERN, Reason.INVALID_GROUPNAME, "Invalid group")
+        );
+        try {
+            Group group = admin.createGroup(GROUP);
+            return admin.createUser(USER, PASSWORD, group);
+        } catch (UserMgtException e) {
+            TestCase.fail(e.getMessage());
+            throw new RuntimeException(e);
+        }
+    }
+
+}
diff --git a/security/src/test/java/org/wamblee/security/authorization/UrlAuthorizationRuleTest.java b/security/src/test/java/org/wamblee/security/authorization/UrlAuthorizationRuleTest.java
new file mode 100644 (file)
index 0000000..9cc9287
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.security.authorization;
+
+import static org.wamblee.security.authorization.AuthorizationResult.GRANTED;
+import static org.wamblee.security.authorization.AuthorizationResult.UNDECIDED;
+import static org.wamblee.security.authorization.AuthorizationResult.UNSUPPORTED_RESOURCE;
+import junit.framework.TestCase;
+
+import org.wamblee.usermgt.User;
+
+
+/**
+ * Tests for the {@link org.wamblee.security.authorization.UrlAuthorizationRule}. 
+ */
+public class UrlAuthorizationRuleTest extends TestCase {
+
+    /**
+     * Constructs the rule with a result of UNDECIDED. Verifies that an IllegalArgumentException 
+     * is thrown. 
+     *
+     */
+    public void testConstructWithUndecidedResult() { 
+        try { 
+            new TestAuthorizationRule(UNDECIDED, "users", "/path", ReadOperation.class);
+            fail();
+        } catch (IllegalArgumentException e) { 
+            // ok 
+        }
+    }
+    
+    /**
+     * Constructs the rule with a result of UNSUPPORTED_RESOURCE. Verifies that an IllegalArgumentException 
+     * is thrown. 
+     *
+     */
+    public void testConstructWithUnsupportedResult() { 
+        try { 
+            new TestAuthorizationRule(UNSUPPORTED_RESOURCE, "users", "/path", ReadOperation.class);
+            fail();
+        } catch (IllegalArgumentException e) { 
+            // ok 
+        }
+    }
+    
+    /**
+     * Constructs the authorization rule and applies it to an unsupported object type. 
+     * Verifies that the result is UNSUPPORTED_RESOURCE. 
+     *
+     */
+    public void testUnsupportedObject() { 
+        AuthorizationRule rule = new TestAuthorizationRule(GRANTED, "users", "/path", ReadOperation.class);
+        assertEquals(UNSUPPORTED_RESOURCE, rule.isAllowed("hello", new ReadOperation(), new TestUserAccessor().getCurrentUser()));
+    }
+    
+    public void testMatchingScenarios() { 
+        AuthorizationRule rule = new TestAuthorizationRule(GRANTED, "users", "/path/", ReadOperation.class);
+        User user = new TestUserAccessor().getCurrentUser();
+        
+        // everything matches
+        assertEquals(GRANTED, rule.isAllowed(new TestResource("/path/a"), new ReadOperation(), user));
+        assertEquals(GRANTED, rule.isAllowed(new TestResource("/path/"), new ReadOperation(), user));
+        
+        // path does not match. 
+        assertEquals(UNDECIDED, rule.isAllowed(new TestResource("/path"), new ReadOperation(), user));
+        
+        // operation does not match. 
+        assertEquals(UNDECIDED, rule.isAllowed(new TestResource("/path/"), new WriteOperation(), user));
+        
+        // group does not match. 
+        AuthorizationRule rule2 = new TestAuthorizationRule(GRANTED, "users2", "/path/", ReadOperation.class);
+        assertEquals(UNDECIDED, rule2.isAllowed(new TestResource("/path/a"), new ReadOperation(), user));
+    }
+    
+}
diff --git a/security/src/test/java/org/wamblee/security/authorization/hibernate/AuthorizationMappingFiles.java b/security/src/test/java/org/wamblee/security/authorization/hibernate/AuthorizationMappingFiles.java
new file mode 100644 (file)
index 0000000..cecce73
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.security.authorization.hibernate;
+
+import org.wamblee.usermgt.UsermgtHibernateMappingFiles;
+
+/**
+ * Mapping files for authorization. 
+ */
+public class AuthorizationMappingFiles extends UsermgtHibernateMappingFiles {
+
+    public AuthorizationMappingFiles() { 
+        super(new String[]{ "hbm/AuthorizationRule.hbm.xml", "hbm/UserCondition.hbm.xml", 
+                "hbm/AuthorizationService.hbm.xml", "hbm/OperationCondition.hbm.xml", "hbm/PathCondition.hbm.xml", 
+                "hbm/TestAuthorizationRule.hbm.xml" }); 
+    }
+}
diff --git a/security/src/test/java/org/wamblee/security/authorization/hibernate/AuthorizationSpringConfigFiles.java b/security/src/test/java/org/wamblee/security/authorization/hibernate/AuthorizationSpringConfigFiles.java
new file mode 100644 (file)
index 0000000..6ecbff8
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.security.authorization.hibernate;
+
+import org.wamblee.usermgt.UsermgtSpringConfigFiles;
+
+/**
+ * 
+ */
+public class AuthorizationSpringConfigFiles extends UsermgtSpringConfigFiles {
+    
+    public AuthorizationSpringConfigFiles() { 
+        super(new String[] { "spring/test.org.wamblee.security.authorization.xml" });
+    }
+
+}
diff --git a/security/src/test/java/org/wamblee/security/authorization/hibernate/PersistentAuthorizationServiceTest.java b/security/src/test/java/org/wamblee/security/authorization/hibernate/PersistentAuthorizationServiceTest.java
new file mode 100644 (file)
index 0000000..93758b1
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.security.authorization.hibernate;
+
+import java.sql.SQLException;
+
+import org.apache.log4j.Logger;
+import org.springframework.orm.hibernate3.HibernateTemplate;
+import org.wamblee.general.BeanKernel;
+import org.wamblee.security.authorization.AuthorizationService;
+import org.wamblee.security.authorization.AuthorizationServiceTest;
+
+/**
+ * Unit test for the persistent authorization service. 
+ */
+public class PersistentAuthorizationServiceTest extends AuthorizationServiceTest {
+    
+    private static final Logger LOGGER = Logger.getLogger(PersistentAuthorizationServiceTest.class);
+   
+    private static final String SERVICE_TABLE = "AUTHORIZATION_SERVICE";
+    private static final String RULES_TABLE = "AUTHORIZATION_RULES"; 
+    private static final String SERVICE_RULES_TABLE = "AUTHORIZATION_SERVICE_RULES";
+    private static final String OPERATIONCOND_TABLE = "OPERATION_CONDITIONS";
+    private static final String PATHCOND_TABLE = "PATH_CONDITIONS";
+    private static final String USERCOND_TABLE = "USER_CONDITIONS";
+   
+
+    public PersistentAuthorizationServiceTest() { 
+        super(AuthorizationSpringConfigFiles.class, AuthorizationMappingFiles.class);
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.security.authorization.AuthorizationServiceTest#createService()
+     */
+    @Override
+    protected AuthorizationService createService() {
+        PersistentAuthorizationService service = new PersistentAuthorizationService("DEFAULT", 
+                BeanKernel.getBeanFactory().find(HibernateTemplate.class), createUserAccessor(), 10000);
+        return service; 
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.security.authorization.AuthorizationServiceTest#checkRuleCount(int)
+     */
+    @Override
+    protected void checkRuleCount(int aCount) {
+        try { 
+            assertEquals(1, getTableSize(SERVICE_TABLE)); 
+            assertEquals(aCount, getTableSize(RULES_TABLE));
+            assertEquals(aCount, getTableSize(SERVICE_RULES_TABLE));
+            assertEquals(aCount, getTableSize(USERCOND_TABLE));
+            assertEquals(aCount, getTableSize(PATHCOND_TABLE));
+            assertEquals(aCount, getTableSize(OPERATIONCOND_TABLE));
+        } catch (SQLException e) {  
+            throw new RuntimeException(e);
+        }
+       
+    }
+    
+    public void testPerformance() { 
+       
+        PersistentAuthorizationService service = (PersistentAuthorizationService)getService(); 
+        
+        int n = 1000; 
+        long time = System.currentTimeMillis();
+        for (int i = 0; i < n; i++) { 
+            testFirstRuleGrants(); 
+            resetTestRules();
+            testSecondRuleDenies();
+            resetTestRules();
+            testThirdRuleGrants();
+            resetTestRules(); 
+            testNoRulesSupportResource();
+        }
+        LOGGER.info("Executed " + 4*n + " authorization checks in " + (float)(System.currentTimeMillis()-time)/(float)1000 + " seconds.");
+    }
+}
diff --git a/security/src/test/java/org/wamblee/security/encryption/MessageDigesterTest.java b/security/src/test/java/org/wamblee/security/encryption/MessageDigesterTest.java
new file mode 100644 (file)
index 0000000..78abdde
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.security.encryption;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for the message digester. 
+ */
+public class MessageDigesterTest extends TestCase {
+
+    public void testMd5HexEncoding() { 
+        assertEquals("e99a18c428cb38d5f260853678922e03", new Md5HexMessageDigester().hash("abc123"));
+    }
+}
diff --git a/security/src/test/java/org/wamblee/usermgt/InMemoryGroupSetTest.java b/security/src/test/java/org/wamblee/usermgt/InMemoryGroupSetTest.java
new file mode 100644 (file)
index 0000000..a751a10
--- /dev/null
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.usermgt;
+
+import java.sql.SQLException;
+import java.util.Set;
+
+import org.wamblee.persistence.hibernate.HibernateMappingFiles;
+import org.wamblee.test.SpringConfigFiles;
+import org.wamblee.test.SpringTestCase;
+
+/**
+ * Tests the inmemory group set. Intended to be subclassed for other
+ * implementations of group set. 
+ */
+public class InMemoryGroupSetTest extends SpringTestCase {
+    
+    protected GroupSet _groups; 
+    
+    public InMemoryGroupSetTest() { 
+        super(SpringConfigFiles.class, HibernateMappingFiles.class);
+    }
+    
+    protected InMemoryGroupSetTest(Class<? extends SpringConfigFiles> aSprings, 
+            Class<? extends HibernateMappingFiles> aMappings) { 
+        super(aSprings, aMappings); 
+    }
+   
+    /**
+     * This method must be overriden in subclasses. 
+     * @return New group set object. 
+     */
+    protected GroupSet createGroupSet() { 
+        return new InMemoryGroupSet(); 
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.test.SpringTestCase#setUp()
+     */
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        _groups = createGroupSet(); 
+        checkGroupCount(0); 
+    }
+    
+    
+    
+    /**
+     * Additional check to be implemented by a subclass. 
+     * @param aGroup Group to check for existence. 
+     */
+    protected void checkGroupExists(String aGroup) throws SQLException { 
+        // Empty
+    }
+    
+    /**
+     * Additional check to be implemented by a subclass. 
+     * @param aGroup Group to check for non-existence. 
+     */
+    protected void checkGroupNotExists(String aGroup) throws SQLException { 
+        // Empty
+    }
+    
+    /**
+     * Additional check to be implemented by a subclass. 
+     * @param aSize Expected number of groups. 
+     */
+    protected void checkGroupCount(int aSize) throws SQLException { 
+        assertEquals(aSize, _groups.size());
+    }
+    
+    /**
+     * Adds a group and verifies that the group is added using 
+     * find(), list(),  and contains().  
+     *
+     */
+    public void testAdd() throws SQLException { 
+       Group group = new Group("group1"); 
+       assertTrue(  _groups.add(group) );
+       checkGroupExists(group.getName());
+       checkGroupCount(1);
+       Group group2 = _groups.find("group1"); 
+       assertNotNull(group2);
+       assertEquals(group.getName(), group2.getName()); 
+       Set<Group> set = _groups.list(); 
+       assertEquals(1, set.size()); 
+       assertTrue(set.contains(group));
+    }
+    
+    /**
+     * Tries to find a non-existing group. Verifies that null is
+     * returned. 
+     *
+     */
+    public void testFindUnknownGroup() throws SQLException { 
+       Group group1 = new Group("group1"); 
+       Group group2 = new Group("group2");
+       _groups.add(group1); 
+       _groups.add(group2);
+       checkGroupExists(group1.getName()); 
+       checkGroupExists(group2.getName()); 
+    
+       assertNull( _groups.find("group3") );
+       checkGroupNotExists("group3");
+    }
+    
+    /**
+     * Adds duplicate group. Verifies that the existing group is left untouched.  
+     */
+    public void testAddDuplicateGroup() throws SQLException { 
+       Group group1 = new Group("group1"); 
+       _groups.add(group1); 
+       
+       assertEquals(1, _groups.list().size()); 
+       assertTrue(_groups.contains(group1));
+       group1 = new Group("group1");
+       assertFalse(_groups.add(group1));
+       assertEquals(1, _groups.list().size());
+       
+       checkGroupExists(group1.getName()); 
+       checkGroupCount(1); 
+    }
+    
+    /**
+     * Removes a group. Verifies that the group is 
+     * removed and the return value is true. 
+     *
+     */
+    public void testRemoveGroup() throws SQLException { 
+        Group group1 = new Group("group1"); 
+        _groups.add(group1); 
+        assertTrue(_groups.contains(group1));
+        checkGroupCount(1);
+        
+        assertTrue(_groups.remove(group1)); 
+        assertFalse(_groups.contains(group1)); 
+        assertNull(_groups.find(group1.getName()));
+        assertEquals(0, _groups.list().size());
+        checkGroupCount(0);
+    }
+  
+    /**
+     * Removes a non-existing group. Verifies that no groups are
+     * removed an that the return value is true. 
+     *
+     */
+    public void testRemoveNonExistingGroup() throws SQLException { 
+        Group group1 = new Group("group1"); 
+        _groups.add(group1); 
+        checkGroupCount(1);
+        Group nonExistingGroup = new Group("group2"); 
+        nonExistingGroup.setPrimaryKey(new Long(1000));
+        nonExistingGroup.setPersistedVersion(1000);
+        assertFalse(_groups.remove(nonExistingGroup));
+        assertTrue(_groups.contains(group1)); 
+        assertEquals(1, _groups.list().size());
+        checkGroupCount(1); 
+    }
+    
+    /**
+     * Adds a number of groups to the set and verifies that list() 
+     * returns them all. 
+     *
+     */
+    public void testList() throws SQLException { 
+        Group group1 = new Group("group1"); 
+        Group group2 = new Group("group2"); 
+        Group group3 = new Group("group3"); 
+        assertTrue(_groups.add(group1)); 
+        assertTrue(_groups.add(group2)); 
+        assertTrue(_groups.add(group3));
+        
+        checkGroupExists(group1.getName()); 
+        checkGroupExists(group2.getName()); 
+        checkGroupExists(group3.getName()); 
+        
+        Set<Group> set = _groups.list(); 
+        assertTrue(set.contains(group1)); 
+        assertTrue(set.contains(group2)); 
+        assertTrue(set.contains(group3));
+        
+        checkGroupCount(3); 
+    }
+}
diff --git a/security/src/test/java/org/wamblee/usermgt/InMemoryUserSetTest.java b/security/src/test/java/org/wamblee/usermgt/InMemoryUserSetTest.java
new file mode 100644 (file)
index 0000000..0e3c759
--- /dev/null
@@ -0,0 +1,322 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.usermgt;
+
+import java.sql.SQLException;
+import java.util.Set;
+
+import org.wamblee.persistence.hibernate.HibernateMappingFiles;
+import org.wamblee.security.encryption.Md5HexMessageDigester;
+import org.wamblee.test.SpringConfigFiles;
+import org.wamblee.test.SpringTestCase;
+import org.wamblee.usermgt.UserMgtException.Reason;
+
+/**
+ * Tests the inmemory user set. Intended to be subclassed for other
+ * implementations of user set. 
+ */
+public class InMemoryUserSetTest extends SpringTestCase {
+    
+    protected static final String PASSWORD = "abc123";
+    
+    private UserSet _users;
+    private GroupSet _groups;
+    
+    private Group _group; 
+    
+    public InMemoryUserSetTest() { 
+        super(SpringConfigFiles.class, HibernateMappingFiles.class);
+    }
+    
+    protected InMemoryUserSetTest(Class<? extends SpringConfigFiles> aSprings, 
+            Class<? extends HibernateMappingFiles> aMappings) { 
+        super(aSprings, aMappings); 
+    }
+   
+    /**
+     * This method must be overriden in subclasses. 
+     * @return New user set object. 
+     */
+    protected UserSet createUserSet() { 
+        return new InMemoryUserSet(   new RegexpNameValidator(RegexpNameValidator.PASSWORD_PATTERN, Reason.INVALID_PASSWORD, "Password must contain at least 6 characters"),
+                new Md5HexMessageDigester()); 
+    }
+    
+    /**
+     * This method must be overriden in subclasses. 
+     * @return New group set object. 
+     */
+    protected GroupSet createGroupSet() { 
+        return new InMemoryGroupSet(); 
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.test.SpringTestCase#setUp()
+     */
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        _users = createUserSet();
+        _groups = createGroupSet(); 
+        _group = new Group("group0");
+        _groups.add(_group);
+        checkUserCount(0);
+        
+    }
+    
+    protected UserSet getUsers() { 
+        return _users; 
+    }
+    
+    protected GroupSet getGroups() { 
+        return _groups; 
+    }
+    
+    protected Group createGroup(String aName) { 
+        return new Group(aName);
+    }
+    
+    protected User createUser(String aName, String aPassword, Group aGroup) throws UserMgtException { 
+        return UsermgtTestUtils.createUser(aName, aPassword, aGroup);
+    }
+    
+    protected void addUserToGroup(User aUser, Group aGroup) throws UserMgtException { 
+        aUser.addGroup(aGroup); 
+    }
+    
+    protected void removeUserFromGroup(User aUser, Group aGroup ) throws UserMgtException { 
+        aUser.removeGroup(aGroup); 
+    }
+    
+    /**
+     * Additional check to be implemented by a subclass. 
+     * @param aUser User to check for existence. 
+     */
+    protected void checkUserExists(String aUser) throws SQLException { 
+        // Empty
+    }
+    
+    /**
+     * Additional check to be implemented by a subclass. 
+     * @param aUser User to check for non-existence. 
+     */
+    protected void checkUserNotExists(String aUser) throws SQLException { 
+        // Empty
+    }
+    
+    /**
+     * Additional check to be implemented by a subclass. 
+     * @param aSize Expected number of users. 
+     */
+    protected void checkUserCount(int aSize) throws SQLException { 
+        assertEquals(aSize, _users.size()); 
+    }
+    
+    /**
+     * Additional check to be implemented by a subclass. 
+     * @param aUser User to check for existence. 
+     */
+    protected void checkGroupExists(String aUser) throws SQLException { 
+        // Empty
+    }
+    
+    /**
+     * Additional check to be implemented by a subclass. 
+     * @param aUser User to check for non-existence. 
+     */
+    protected void checkGroupNotExists(String aUser) throws SQLException { 
+        // Empty
+    }
+    
+    /**
+     * Additional check to be implemented by a subclass. 
+     * @param aSize Expected number of users. 
+     */
+    protected void checkGroupCount(int aSize) throws SQLException { 
+        // Empty
+    }
+   
+    
+    /**
+     * Adds a user and verifies that the user is added using 
+     * find(), list(),  and contains().  
+     *
+     */
+    public void testAdd() throws SQLException, UserMgtException { 
+       User user = createUser("user1", PASSWORD, _group); 
+       assertTrue(  _users.add(user) );
+       checkUserExists(user.getName());
+       checkUserCount(1);
+       User user2 = _users.find("user1"); 
+       assertNotNull(user2);
+       assertEquals(user.getName(), user2.getName()); 
+       Set<User> set = _users.list(); 
+       assertEquals(1, set.size()); 
+       assertTrue(set.contains(user));
+    }
+    
+    /**
+     * Tries to find a non-existing user. Verifies that null is
+     * returned. 
+     *
+     */
+    public void testFindUnknownUser() throws SQLException, UserMgtException { 
+       User user1 = createUser("user1", PASSWORD, _group); 
+       User user2 = createUser("user2", PASSWORD, _group);
+       _users.add(user1); 
+       _users.add(user2);
+       checkUserExists(user1.getName()); 
+       checkUserExists(user2.getName()); 
+    
+       assertNull( _users.find("user3") );
+       checkUserNotExists("user3");
+    }
+    
+    /**
+     * Adds duplicate user. Verifies that the existing user is left untouched.  
+     */
+    public void testAddDuplicateUser() throws SQLException, UserMgtException { 
+       User user1 = createUser("user1", PASSWORD, _group); 
+       _users.add(user1); 
+       
+       assertEquals(1, _users.list().size()); 
+       assertTrue(_users.contains(user1)); 
+       user1 = createUser("user1", PASSWORD, _group); 
+       assertFalse(_users.add(user1));
+       assertEquals(1, _users.list().size());
+       
+       checkUserExists(user1.getName()); 
+       checkUserCount(1); 
+    }
+    
+    /**
+     * Removes a user. Verifies that the user is 
+     * removed and the return value is true. 
+     *
+     */
+    public void testRemoveUser() throws SQLException, UserMgtException { 
+        User user1 = createUser("user1", PASSWORD, _group); 
+        _users.add(user1); 
+        assertTrue(_users.contains(user1));
+        checkUserCount(1);
+        
+        assertTrue(_users.remove(user1)); 
+        assertFalse(_users.contains(user1)); 
+        assertNull(_users.find(user1.getName()));
+        assertEquals(0, _users.list().size());
+        checkUserCount(0);
+    }
+  
+    /**
+     * Removes a non-existing user. Verifies that no users are
+     * removed an that the return value is true. 
+     *
+     */
+    public void testRemoveNonExistingUser() throws SQLException, UserMgtException { 
+        User user1 = createUser("user1", PASSWORD, _group); 
+        _users.add(user1); 
+        checkUserCount(1);
+        User nonExistingUser = createUser("user2", PASSWORD, _group);
+        nonExistingUser.setPrimaryKey(new Long(1000));
+        nonExistingUser.setPersistedVersion(10);
+        assertFalse(_users.remove(nonExistingUser));
+        assertTrue(_users.contains(user1)); 
+        assertEquals(1, _users.list().size());
+        checkUserCount(1); 
+    }
+    
+    /**
+     * Adds a number of users to the set and verifies that list() 
+     * returns them all. 
+     *
+     */
+    public void testList() throws SQLException, UserMgtException { 
+        User user1 = createUser("user1", PASSWORD, _group); 
+        User user2 = createUser("user2", PASSWORD, _group); 
+        User user3 = createUser("user3", PASSWORD, _group); 
+        assertTrue(_users.add(user1)); 
+        assertTrue(_users.add(user2)); 
+        assertTrue(_users.add(user3));
+        
+        checkUserExists(user1.getName()); 
+        checkUserExists(user2.getName()); 
+        checkUserExists(user3.getName()); 
+        
+        Set<User> set = _users.list(); 
+        assertTrue(set.contains(user1)); 
+        assertTrue(set.contains(user2)); 
+        assertTrue(set.contains(user3));
+        
+        checkUserCount(3); 
+    }
+    
+    /**
+     * Adds several users to different groups and verifies that 
+     * the correct users are returned when looking for users in 
+     * different groups. 
+     * @throws SQLException
+     */
+    public void testListByGroup() throws SQLException, UserMgtException {
+        Group group1 = new Group("group1"); 
+        Group group2 = new Group("group2");
+        Group group3 = new Group("group3");
+        _groups.add(group1); 
+        _groups.add(group2); 
+        _groups.add(group3);
+        
+        //           user1    user2    user3
+        // group1     y         
+        // group2     y       y           
+        // group3     y       y           y
+        
+        User user1 = createUser("user1", PASSWORD, group1);
+        user1.addGroup(group2); 
+        user1.addGroup(group3);
+        User user2 = createUser("user2", PASSWORD, group2); 
+        user2.addGroup(group3); 
+        User user3 = createUser("user3", PASSWORD, group3);
+        _users.add(user1); 
+        _users.add(user2); 
+        _users.add(user3);
+        
+        checkUserExists(user1.getName());
+        checkUserExists(user2.getName()); 
+        checkUserExists(user3.getName()); 
+        
+        checkGroupExists(group1.getName());
+        checkGroupExists(group2.getName()); 
+        checkGroupExists(group3.getName()); 
+        
+        checkUserCount(3); 
+        checkGroupCount(3+1); // also count the group that was created in the setUp().
+        
+        Set<User> list = _users.list(group1);
+        assertTrue(list.contains(user1)); 
+        assertEquals(1, list.size());
+        
+        list = _users.list(group2); 
+        assertTrue(list.contains(user1));
+        assertTrue(list.contains(user2));
+        assertEquals(2, list.size());
+        
+        list = _users.list(group3); 
+        assertTrue(list.contains(user1));
+        assertTrue(list.contains(user2));
+        assertTrue(list.contains(user3));
+        assertEquals(3, list.size());
+    }
+}
diff --git a/security/src/test/java/org/wamblee/usermgt/UserAdministrationImplTest.java b/security/src/test/java/org/wamblee/usermgt/UserAdministrationImplTest.java
new file mode 100644 (file)
index 0000000..12834c6
--- /dev/null
@@ -0,0 +1,611 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.wamblee.usermgt;
+
+import java.util.Set;
+
+import org.apache.log4j.Logger;
+import org.wamblee.persistence.hibernate.HibernateMappingFiles;
+import org.wamblee.security.encryption.Md5HexMessageDigester;
+import org.wamblee.test.SpringConfigFiles;
+import org.wamblee.test.SpringTestCase;
+import org.wamblee.usermgt.UserMgtException.Reason;
+
+/**
+ * Test of user administration implementation.
+ */
+public class UserAdministrationImplTest extends SpringTestCase {
+
+    private static final Logger LOGGER = Logger
+            .getLogger(UserAdministrationImplTest.class);
+
+    private static final String USER1 = "piet";
+
+    private static final String PASS1 = "passpiet";
+
+    private static final String USER2 = "kees";
+
+    private static final String PASS2 = "passkees";
+
+    private static final String GROUP1 = "cyclists";
+
+    private static final String GROUP2 = "runners";
+
+    private UserAdministration _admin;
+
+    public UserAdministrationImplTest() {
+        super(SpringConfigFiles.class, HibernateMappingFiles.class);
+    }
+
+    public UserAdministrationImplTest(
+            Class<? extends SpringConfigFiles> aSprings,
+            Class<? extends HibernateMappingFiles> aMappings) {
+        super(aSprings, aMappings);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see junit.framework.TestCase#setUp()
+     */
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        _admin = createAdmin();
+    }
+
+    protected UserAdministration createAdmin() {
+        UserSet users = new InMemoryUserSet(   new RegexpNameValidator(RegexpNameValidator.PASSWORD_PATTERN, Reason.INVALID_PASSWORD, "Password must contain at least 6 characters"),
+                new Md5HexMessageDigester());
+        GroupSet groups = new InMemoryGroupSet();
+        return new UserAdministrationImpl(users, groups,
+                new RegexpNameValidator(RegexpNameValidator.ID_PATTERN,
+                        Reason.INVALID_USERNAME, "Invalid user"),
+                new RegexpNameValidator(RegexpNameValidator.ID_PATTERN,
+                        Reason.INVALID_GROUPNAME, "Invalid group"));
+    }
+    
+    protected User createUser(String aName, String aPassword, Group aGroup) throws UserMgtException { 
+        return UsermgtTestUtils.createUser(aName, aPassword, aGroup);
+    }
+
+    /**
+     * Constructs the admin, verify it contains no users and no groups.
+     */
+    public void testConstruct() {
+        assertEquals(0, _admin.getUsers().size());
+        assertEquals(0, _admin.getGroups().size());
+        assertEquals(0, _admin.getUserCount());
+        assertEquals(0, _admin.getGroupCount());
+    }
+
+    /**
+     * Creates a new group. Verifies the group is created correctly and that the
+     * user is added.
+     * 
+     */
+    public void testCreateGroup() throws UserMgtException {
+        Group group = _admin.createGroup(GROUP1);
+        assertNotNull(group);
+        assertEquals(GROUP1, group.getName());
+
+        Set<Group> groups = _admin.getGroups();
+        assertEquals(1, groups.size());
+        assertEquals(1, _admin.getGroupCount());
+        assertTrue(groups.contains(group));
+    }
+
+    private void createInvalidGroup(String aUsername) {
+        try {
+            _admin.createGroup(aUsername);
+            fail();
+        } catch (UserMgtException e) {
+            assertEquals(UserMgtException.Reason.INVALID_GROUPNAME, e
+                    .getReason());
+            assertEquals(0, _admin.getGroupCount());
+        }
+    }
+
+    /**
+     * Creates a new group with an invalid name. Verifies that the appropriate
+     * exception is thrown.
+     * 
+     * @throws UserMgtException
+     */
+    public void testCreateInvalidGroupName() throws UserMgtException {
+        createInvalidGroup("");
+        createInvalidGroup("0abc"); // should not start with digits
+        createInvalidGroup("a b"); // should not contain spaces
+        createInvalidGroup(" aa");
+        createInvalidGroup("aa ");
+    }
+
+    /**
+     * Creates a new group which conflicts with an existing one. Verifies that
+     * the UserMgtException is thrown and that no group is added.
+     * 
+     */
+    public void testCreateDuplicateGroup() throws UserMgtException {
+        _admin.createGroup(GROUP1);
+        try {
+            _admin.createGroup(GROUP1);
+        } catch (UserMgtException e) {
+            assertEquals(UserMgtException.Reason.DUPLICATE_GROUP, e.getReason());
+            assertEquals(1, _admin.getGroupCount());
+            return;
+        }
+        fail();
+    }
+
+    /**
+     * Creates a new user. Verifies the user is created correctly and that the
+     * user is added.
+     * 
+     */
+    public void testCreateUser() throws UserMgtException {
+        Group group = _admin.createGroup(GROUP1);
+        User user = _admin.createUser(USER1, PASS1, group);
+        assertNotNull(user);
+        assertEquals(USER1, user.getName());
+        user.checkPassword(PASS1);
+
+        Set<User> users = _admin.getUsers();
+        assertEquals(1, users.size());
+        assertEquals(1, _admin.getUserCount());
+        assertTrue(users.contains(user));
+    }
+
+    private void createInvalidUser(String aUsername, Group aGroup) {
+        try {
+            _admin.createUser(aUsername, "pass", aGroup);
+            fail();
+        } catch (UserMgtException e) {
+            assertEquals(UserMgtException.Reason.INVALID_USERNAME, e
+                    .getReason());
+            assertEquals(0, _admin.getUserCount());
+        }
+    }
+
+    /**
+     * Constructs users with invalid names. Verifies that the appropriate
+     * exception is thrown.
+     * 
+     */
+    public void testCreateInvalidUserName() throws UserMgtException {
+        Group group = _admin.createGroup(GROUP1);
+        createInvalidUser("", group);
+        createInvalidUser("0abc", group); // should not start with digits
+        createInvalidUser("a b", group); // should not contain spaces
+        createInvalidUser(" aa", group);
+        createInvalidUser("aa ", group);
+    }
+
+    /**
+     * Creates a new user which conflicts with an existing one. Verifies that
+     * the UserMgtException is thrown and that no user is added.
+     * 
+     */
+    public void testCreateDuplicateUser() throws UserMgtException {
+        Group group = _admin.createGroup(GROUP1);
+        _admin.createUser(USER1, PASS1, group);
+        try {
+            _admin.createUser(USER1, PASS2, group);
+            fail();
+        } catch (UserMgtException e) {
+            assertEquals(UserMgtException.Reason.DUPLICATE_USER, e.getReason());
+            assertEquals(1, _admin.getUserCount());
+        }
+    }
+
+    /**
+     * Gets a known user by name. Verifies the correct user is obtained.
+     * Verifies that null is returned when trying to obtain an unknown user.
+     * 
+     */
+    public void testGetUser() throws UserMgtException {
+        Group group = _admin.createGroup(GROUP1);
+        User user = _admin.createUser(USER1, PASS1, group);
+        User user2 = _admin.getUser(USER1);
+        assertTrue(user.equals(user2));
+        assertNull(_admin.getUser(USER2));
+    }
+
+    /**
+     * Gets a known group by name. Verifies the correct group is obtained.
+     * Verifies that null is returned when the group is not known.
+     * 
+     */
+    public void testGetGroup() throws UserMgtException {
+        Group group = _admin.createGroup(GROUP1);
+        Group group2 = _admin.getGroup(GROUP1);
+        assertTrue(group.equals(group2));
+        assertNull(_admin.getGroup(GROUP2));
+    }
+
+    /**
+     * Adds a user to a group. Verifies that the user is added using several API
+     * calls. Verifies that an exception occurs if the user is not already part
+     * of the group.
+     * 
+     */
+    public void testAddUserToGroup() throws UserMgtException {
+
+        Group group = _admin.createGroup(GROUP1);
+        User user = _admin.createUser(USER1, PASS1, group);
+        Group group2 = _admin.createGroup(GROUP2);
+        assertTrue(user.isInGroup(group));
+        assertFalse(user.isInGroup(group2));
+        _admin.addUserToGroup(user, group2);
+        assertTrue(user.isInGroup(group));
+        assertTrue(user.isInGroup(group2));
+        Set<User> users = _admin.getUsers(group2);
+        assertNotNull(users);
+        assertEquals(1, users.size());
+        assertTrue(users.contains(user));
+
+        try {
+            _admin.addUserToGroup(user, group);
+        } catch (UserMgtException e) {
+            assertEquals(UserMgtException.Reason.USER_ALREADY_IN_GROUP, e
+                    .getReason());
+            return;
+        }
+        fail();
+    }
+
+    /**
+     * Adds a user to a group where the user does not exist. Verifies that an
+     * exception occurs.
+     * 
+     */
+    public void testAddUserToGroupUnknownUser() throws UserMgtException {
+        Group group = _admin.createGroup(GROUP1);
+        User user = createUser(USER1, PASS1, group);
+        try {
+            _admin.addUserToGroup(user, group);
+        } catch (UserMgtException e) {
+            assertEquals(UserMgtException.Reason.UNKNOWN_USER, e.getReason());
+            return;
+        }
+        fail();
+    }
+
+    /**
+     * Adds a user to a group where the user does not exist. Verifies that an
+     * exception occurs.
+     * 
+     */
+    public void testAddUserToGroupUnknownGroup() throws UserMgtException {
+        Group group = _admin.createGroup(GROUP1);
+        User user = _admin.createUser(USER1, PASS1, group);
+        Group group2 = new Group(GROUP2);
+        try {
+            _admin.addUserToGroup(user, group2);
+        } catch (UserMgtException e) {
+            assertEquals(UserMgtException.Reason.UNKNOWN_GROUP, e.getReason());
+            return;
+        }
+        fail();
+    }
+
+    /**
+     * Removes a user from a group. Verifies that the user is removed from the
+     * group using several API calls. Verifies that an exception occurs if the
+     * user not part of the group or if the user is only part of one group.
+     */
+    public void testRemoveUserFromGroup() throws UserMgtException {
+        Group group = _admin.createGroup(GROUP1);
+
+        User user = _admin.createUser(USER1, PASS1, group);
+        Group group2 = _admin.createGroup(GROUP2);
+        _admin.addUserToGroup(user, group2);
+        Set<Group> groups = user.getGroups();
+        assertEquals(2, groups.size());
+        assertTrue(groups.contains(group));
+        assertTrue(groups.contains(group2));
+
+        _admin.removeUserFromGroup(user, group);
+        groups = user.getGroups();
+        assertEquals(1, groups.size());
+        assertTrue(groups.contains(group2));
+        assertFalse(groups.contains(group));
+    }
+
+    /**
+     * Removes a user from a group where the user is not known. Verifies that an
+     * exception is thrown.
+     * 
+     */
+    public void testRemoveUserFromGroupUnknownUser() throws UserMgtException {
+        Group group = _admin.createGroup(GROUP1);
+        User user = createUser(USER1, GROUP1, group);
+        try {
+            _admin.removeUserFromGroup(user, group);
+        } catch (UserMgtException e) {
+            assertEquals(UserMgtException.Reason.UNKNOWN_USER, e.getReason());
+        }
+    }
+
+    /**
+     * Removes a user from a group where the group is not known. Verifies that
+     * an exception is thrown.
+     * 
+     */
+    public void testRemoveUserFromGroupUnknownGroup() throws UserMgtException {
+        Group group = _admin.createGroup(GROUP1);
+        User user = _admin.createUser(USER1, PASS1, group);
+        Group group2 = new Group(GROUP2);
+        try {
+            _admin.removeUserFromGroup(user, group2);
+        } catch (UserMgtException e) {
+            assertEquals(UserMgtException.Reason.UNKNOWN_GROUP, e.getReason());
+        }
+    }
+
+    /**
+     * Removes a user from a group where the user is only part of one group.
+     * Verifies that an exception is thrown.
+     */
+    public void testRemoveUserFromGroupOnlyGroup() throws UserMgtException {
+        Group group = _admin.createGroup(GROUP1);
+        User user = _admin.createUser(USER1, PASS1, group);
+        try {
+            _admin.removeUserFromGroup(user, group);
+        } catch (UserMgtException e) {
+            assertEquals(UserMgtException.Reason.USER_MUST_BE_IN_A_GROUP, e
+                    .getReason());
+        }
+    }
+
+    /**
+     * Gets the list of users and groups. Verifies that the correct suers and
+     * groups are returned. Verifies also that the relations from user to group
+     * are correct.
+     * 
+     */
+    public void testGetUsersAndGroups() throws UserMgtException {
+        Group group1 = _admin.createGroup(GROUP1);
+        Group group2 = _admin.createGroup(GROUP2);
+
+        User user1 = _admin.createUser(USER1, PASS1, group1);
+        _admin.addUserToGroup(user1, group2);
+        User user2 = _admin.createUser(USER2, PASS2, group2);
+
+        Set<User> users = _admin.getUsers();
+        assertEquals(2, users.size());
+        assertTrue(users.contains(user1));
+        assertTrue(users.contains(user2));
+
+        Set<Group> groups = _admin.getGroups();
+        assertEquals(2, groups.size());
+        assertTrue(groups.contains(group1));
+        assertTrue(groups.contains(group2));
+
+        assertTrue(user1.isInGroup(group1));
+        assertTrue(user1.isInGroup(group2));
+        assertFalse(user2.isInGroup(group1));
+        assertTrue(user2.isInGroup(group2));
+
+        Set<Group> groups1 = user1.getGroups();
+        assertEquals(2, groups1.size());
+
+        Set<Group> groups2 = user2.getGroups();
+        assertEquals(1, groups2.size());
+    }
+
+    /**
+     * Renames a user. Verifies that the user is renamed. Verifies that
+     * exceptions are thrown when an attempt is made to rename the user to
+     * itself or to another existing user, or when the group does not exist.
+     * 
+     */
+    public void testRenameUser() throws UserMgtException {
+        Group group = _admin.createGroup(GROUP1);
+        User user1 = _admin.createUser(USER1, PASS1, group);
+        _admin.renameUser(user1, USER2);
+        assertEquals(USER2, user1.getName());
+        assertEquals(user1, _admin.getUser(USER2));
+
+        _admin.createUser(USER1, PASS1, group);
+
+        try {
+            _admin.renameUser(user1, USER1);
+        } catch (UserMgtException e) {
+            assertEquals(UserMgtException.Reason.DUPLICATE_USER, e.getReason());
+
+            // do a trivial reanem
+            try {
+                _admin.renameUser(user1, user1.getName());
+            } catch (UserMgtException e2) {
+                assertEquals(UserMgtException.Reason.TRIVIAL_RENAME, e2
+                        .getReason());
+                return;
+            }
+            fail();
+        }
+        fail();
+    }
+
+    /**
+     * Renames a user to a user with an invalid username. Verifies that the
+     * appropriate exception is thrown.
+     * 
+     */
+    public void testRenameUserInvalidUsername() throws UserMgtException {
+        Group group = _admin.createGroup(GROUP1);
+        User user1 = _admin.createUser(USER1, PASS1, group);
+        try {
+            _admin.renameUser(user1, USER2);
+        } catch (UserMgtException e) {
+            assertEquals(e.getReason(), Reason.INVALID_USERNAME);
+        }
+    }
+
+    /**
+     * Renames a group. Verifies that the group is renamed. Verifies that
+     * exceptions are thrown when an attempt is made to rename the group to
+     * itself or to another existing group or when the group does not exist.
+     * 
+     */
+    public void testRenameGroup() throws UserMgtException {
+        Group group = _admin.createGroup(GROUP1);
+        _admin.renameGroup(group, GROUP2);
+        assertEquals(GROUP2, group.getName());
+        assertEquals(group, _admin.getGroup(GROUP2));
+
+        _admin.createGroup(GROUP1);
+        try {
+            _admin.renameGroup(group, GROUP1);
+        } catch (UserMgtException e) {
+            assertEquals(UserMgtException.Reason.DUPLICATE_GROUP, e.getReason());
+
+            // do a trivial reanem
+            try {
+                _admin.renameGroup(group, group.getName());
+            } catch (UserMgtException e2) {
+                assertEquals(UserMgtException.Reason.TRIVIAL_RENAME, e2
+                        .getReason());
+                return;
+            }
+            fail();
+            return;
+        }
+        fail();
+    }
+
+    /**
+     * Renames a group to a group with an invalid name. Verifies that the
+     * appropriate exception is thrown.
+     * 
+     */
+    public void testRenameGroupInvalidGroupname() throws UserMgtException {
+        Group group = _admin.createGroup(GROUP1);
+        try {
+            _admin.renameGroup(group, "a b");
+        } catch (UserMgtException e) {
+            assertEquals(e.getReason(), Reason.INVALID_GROUPNAME);
+        }
+    }
+
+    /**
+     * Removes a user. Verifies that the user is removed. Verifies that the an
+     * exception is thrown when the user does not exist.
+     * 
+     */
+    public void testRemoveUser() throws UserMgtException {
+        Group group = _admin.createGroup(GROUP1);
+        User user = _admin.createUser(USER1, PASS1, group);
+
+        assertEquals(1, _admin.getUserCount());
+        _admin.removeUser(user);
+        assertEquals(0, _admin.getUserCount());
+
+        _admin.createUser(USER1, PASS1, group);
+        assertEquals(1, _admin.getUserCount());
+
+        User user2 = createUser(USER2, PASS2, group);
+
+        try {
+            _admin.removeUser(user2);
+        } catch (UserMgtException e) {
+            assertEquals(UserMgtException.Reason.UNKNOWN_USER, e.getReason());
+        }
+    }
+
+    /**
+     * Removes a group. Verifies that the group is removed. Verifies that the an
+     * exception is thrown when the group does not exist or if there are still
+     * users in the group.
+     * 
+     */
+    public void testRemoveGroup() throws UserMgtException {
+        Group group1 = _admin.createGroup(GROUP1);
+        assertEquals(1, _admin.getGroupCount());
+        _admin.removeGroup(group1);
+        assertEquals(0, _admin.getGroupCount());
+        group1 = _admin.createGroup(GROUP1);
+
+        _admin.createUser(USER1, PASS1, group1);
+        try {
+            _admin.removeGroup(group1);
+        } catch (UserMgtException e) {
+            assertEquals(UserMgtException.Reason.GROUP_STILL_OCCUPIED, e
+                    .getReason());
+            return;
+        }
+        fail();
+    }
+
+    /**
+     * Tries to remove an unknown group. Verifies that an exception is thrown.
+     * 
+     */
+    public void testRemoveGroupUnknownGroup() throws UserMgtException {
+        Group group = _admin.createGroup(GROUP1);
+        Group group2 = new Group(GROUP2);
+        try {
+            _admin.removeGroup(group2);
+        } catch (UserMgtException e) {
+            assertEquals(UserMgtException.Reason.UNKNOWN_GROUP, e.getReason());
+        }
+    }
+
+    /**
+     * Changes the password, verifies that this succeeds.
+     * 
+     * @throws UserMgtException
+     */
+    public void testChangePassword() throws UserMgtException {
+        Group group = _admin.createGroup(GROUP1);
+        User user = _admin.createUser(USER1, PASS1, group);
+        user.changePassword(PASS1, PASS2);
+
+        // retrieve the user and verifies the password hasn't changed.
+        User user2 = _admin.getUser(USER1);
+        try {
+            user2.checkPassword(PASS2);
+            fail(); // password should not have changed already.
+        } catch (UserMgtException e) {
+            // ok.
+        }
+
+        // now notify the admin of the change in the user
+        _admin.userModified(user);
+
+        user2 = _admin.getUser(USER1);
+        user2.checkPassword(PASS2); // this time it should succeed.
+
+    }
+
+    /**
+     * Performance test. Finds a user by name.
+     * 
+     */
+    public void testPerformanceFindUserByName() throws UserMgtException {
+        Group group = _admin.createGroup(GROUP1);
+        _admin.createUser(USER1, PASS1, group);
+
+        int n = 1000;
+        long time = System.currentTimeMillis();
+        for (int i = 0; i < n; i++) {
+            _admin.getUser(USER1);
+        }
+        LOGGER.info("Looked up a user " + n + " times in "
+                + (float) (System.currentTimeMillis() - time) / 1000.0);
+    }
+
+}
diff --git a/security/src/test/java/org/wamblee/usermgt/UsermgtHibernateMappingFiles.java b/security/src/test/java/org/wamblee/usermgt/UsermgtHibernateMappingFiles.java
new file mode 100644 (file)
index 0000000..611afd3
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.usermgt;
+
+import java.util.Collections;
+
+import org.wamblee.persistence.hibernate.HibernateMappingFiles;
+
+/**
+ * Hibernate mapping files for user management. 
+ */
+public class UsermgtHibernateMappingFiles extends HibernateMappingFiles {
+
+    public UsermgtHibernateMappingFiles() { 
+       super(new String[] {
+          "hbm/Group.hbm.xml", "hbm/User.hbm.xml"     
+       });
+    }
+    
+    public UsermgtHibernateMappingFiles(String[] aFiles) { 
+        this(); 
+        Collections.addAll(this, aFiles);
+    }
+}
diff --git a/security/src/test/java/org/wamblee/usermgt/UsermgtSpringConfigFiles.java b/security/src/test/java/org/wamblee/usermgt/UsermgtSpringConfigFiles.java
new file mode 100644 (file)
index 0000000..8ef20ce
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.wamblee.usermgt;
+
+import java.util.Collections;
+
+import org.wamblee.test.SpringConfigFiles;
+
+/**
+ * Spring config files for user management.
+ */
+public class UsermgtSpringConfigFiles extends SpringConfigFiles {
+
+    public UsermgtSpringConfigFiles() {
+        super(new String[] { "spring/test.org.wamblee.security.properties.xml",
+                "spring/test.org.wamblee.security.datasource.xml",
+                "spring/test.org.wamblee.security.database.xml",
+                "spring/test.org.wamblee.security.usermgt.xml" });
+    }
+    
+    public UsermgtSpringConfigFiles(String[] aFiles) { 
+        this(); 
+        Collections.addAll(this, aFiles);
+    }
+}
diff --git a/security/src/test/java/org/wamblee/usermgt/UsermgtTestUtils.java b/security/src/test/java/org/wamblee/usermgt/UsermgtTestUtils.java
new file mode 100644 (file)
index 0000000..3075034
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.usermgt;
+
+import org.wamblee.security.encryption.Md5HexMessageDigester;
+import org.wamblee.usermgt.UserMgtException.Reason;
+
+/**
+ * User management test utilities. 
+ */
+public class UsermgtTestUtils {
+    
+    private static final String DUMMY_GROUP = "dummygroup";
+    private static final String DUMMY_PASSWD = "dummypasswd";
+
+    public static Group createGroup(String aName) { 
+        return new Group(aName);
+    }
+    
+    public static User createUser(String aUsername) throws UserMgtException { 
+        return createUser(aUsername, DUMMY_GROUP);
+    }
+    
+    public static User createUser(String aUsername, String aGroup) throws UserMgtException { 
+        return createUser(aUsername, createGroup(aGroup)); 
+    }
+    
+    public static User createUser(String aUsername, Group aGroup) throws UserMgtException { 
+        return createUser(aUsername, DUMMY_PASSWD, aGroup);
+    }
+    
+    public static User createUser(String aName, String aPassword, Group aGroup) throws UserMgtException { 
+        return new User(aName, aPassword, aGroup, 
+                new RegexpNameValidator(RegexpNameValidator.PASSWORD_PATTERN, 
+                        Reason.INVALID_PASSWORD, "Password must be at least 6 chars"), 
+                        new Md5HexMessageDigester()); 
+    }
+    
+    public static void addUserToGroup(User aUser, Group aGroup) throws UserMgtException { 
+        aUser.addGroup(aGroup);
+    }
+    
+    public static void removeUserFromGroup(User aUser, Group aGroup) throws UserMgtException { 
+        aUser.removeGroup(aGroup);
+    }
+}
diff --git a/security/src/test/java/org/wamblee/usermgt/hibernate/HibernateGroupSetTest.java b/security/src/test/java/org/wamblee/usermgt/hibernate/HibernateGroupSetTest.java
new file mode 100644 (file)
index 0000000..bc1e67d
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.usermgt.hibernate;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.wamblee.general.BeanKernel;
+import org.wamblee.test.TestTransactionCallback;
+import org.wamblee.usermgt.GroupSet;
+import org.wamblee.usermgt.InMemoryGroupSetTest;
+import org.wamblee.usermgt.UsermgtHibernateMappingFiles;
+import org.wamblee.usermgt.UsermgtSpringConfigFiles;
+
+/**
+ * Tests for {@link org.wamblee.usermgt.hibernate.HibernateGroupSet} 
+ */
+public class HibernateGroupSetTest extends InMemoryGroupSetTest {
+    
+    private static final String GROUP_TABLE = "GROUPS"; 
+    
+    private static final String GROUP_QUERY = "select * from " + GROUP_TABLE + " where name = ?";
+    
+    public HibernateGroupSetTest() { 
+        super(UsermgtSpringConfigFiles.class, UsermgtHibernateMappingFiles.class);
+    }
+
+    /* (non-Javadoc)
+     * @see org.wamblee.usermgt.InMemoryGroupSetTest#checkGroupCount(int)
+     */
+    @Override
+    protected void checkGroupCount(int aSize) throws SQLException {
+        super.flush(); 
+        super.checkGroupCount(aSize);
+        assertEquals(aSize, getTableSize(GROUP_TABLE));
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.usermgt.InMemoryGroupSetTest#checkGroupExists(java.lang.String)
+     */
+    @Override
+    protected void checkGroupExists(final String aGroup) throws SQLException {
+        flush();
+        Map<String,Integer> result = 
+        executeTransaction(new TestTransactionCallback()  { 
+            /* (non-Javadoc)
+             * @see org.wamblee.test.TestTransactionCallback#execute()
+             */
+            @Override
+            public Map execute() throws Exception {
+                ResultSet result = executeQuery(GROUP_QUERY, aGroup);
+                Map<String,Integer> res = new HashMap<String,Integer>(); 
+                res.put("result", countResultSet(result));
+                return res; 
+            }
+        }); 
+      
+        int count = result.get("result");
+        assertEquals(1, count);
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.usermgt.InMemoryGroupSetTest#checkGroupNotExists(java.lang.String)
+     */
+    @Override
+    protected void checkGroupNotExists(String aGroup) throws SQLException {
+       flush(); 
+       ResultSet result = executeQuery(GROUP_QUERY, aGroup); 
+       assertEquals(0, countResultSet(result));
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.usermgt.InMemoryGroupSetTest#createGroupSet()
+     */
+    @Override
+    protected GroupSet createGroupSet() {
+        return BeanKernel.getBeanFactory().find(GroupSet.class); 
+    }
+    
+}
diff --git a/security/src/test/java/org/wamblee/usermgt/hibernate/HibernateUserAdministrationTest.java b/security/src/test/java/org/wamblee/usermgt/hibernate/HibernateUserAdministrationTest.java
new file mode 100644 (file)
index 0000000..07a770c
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.wamblee.usermgt.hibernate;
+
+import java.lang.reflect.Method;
+import java.sql.SQLException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.wamblee.cache.Cache;
+import org.wamblee.general.BeanKernel;
+import org.wamblee.test.TestTransactionCallbackWithoutResult;
+import org.wamblee.usermgt.UserAdministration;
+import org.wamblee.usermgt.UserAdministrationImplTest;
+import org.wamblee.usermgt.UsermgtHibernateMappingFiles;
+import org.wamblee.usermgt.UsermgtSpringConfigFiles;
+
+/**
+ * User administration tests with persistence based on Hibernate. This executes
+ * the same test cases as {@link org.wamblee.usermgt.UserAdministrationImplTest}
+ * with in addition, one test case that executes all Hibernate test cases
+ * separately with each test case in its own transaction.
+ */
+public class HibernateUserAdministrationTest extends UserAdministrationImplTest {
+    
+    private static final Log LOG = LogFactory.getLog(HibernateUserAdministrationTest.class);
+
+    public HibernateUserAdministrationTest() {
+        super(UsermgtSpringConfigFiles.class,
+                UsermgtHibernateMappingFiles.class);
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.usermgt.UserAdministrationImplTest#setUp()
+     */
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        clearUserCache();
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.usermgt.UserAdministrationImplTest#createAdmin()
+     */
+    @Override
+    protected UserAdministration createAdmin() {
+        return BeanKernel.getBeanFactory().find(UserAdministration.class);
+    }
+
+    public void testAllTestsInASeparateTransaction() throws SQLException {
+
+        Method[] methods = UserAdministrationImplTest.class.getMethods();
+        for (final Method method : methods) {
+            if (method.getName().startsWith("test")) {
+                cleanDatabase();
+                clearUserCache();
+                executeTransaction(new TestTransactionCallbackWithoutResult() {
+                    public void execute() throws Exception {
+                        LOG.info("Running test " + method.getName());
+                        try {
+                            method.invoke(HibernateUserAdministrationTest.this);
+                        } catch (Throwable t) {
+                            LOG.error("Test " + method.getName() + " failed");
+                            throw new RuntimeException(t.getMessage(), t); 
+                        }
+                        finally {
+                            LOG.info("Test " + method.getName() + " finished");
+                        }
+
+                    }
+                });
+            }
+        }
+    }
+
+    /**
+     * 
+     */
+    private void clearUserCache() {
+        BeanKernel.getBeanFactory().find("userCache", Cache.class).clear();
+    }
+}
diff --git a/security/src/test/java/org/wamblee/usermgt/hibernate/HibernateUserSetTest.java b/security/src/test/java/org/wamblee/usermgt/hibernate/HibernateUserSetTest.java
new file mode 100644 (file)
index 0000000..0061a43
--- /dev/null
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.wamblee.usermgt.hibernate;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Set;
+
+import org.wamblee.cache.Cache;
+import org.wamblee.general.BeanKernel;
+import org.wamblee.usermgt.Group;
+import org.wamblee.usermgt.GroupSet;
+import org.wamblee.usermgt.InMemoryUserSetTest;
+import org.wamblee.usermgt.User;
+import org.wamblee.usermgt.UserMgtException;
+import org.wamblee.usermgt.UserSet;
+import org.wamblee.usermgt.UsermgtHibernateMappingFiles;
+import org.wamblee.usermgt.UsermgtSpringConfigFiles;
+
+/**
+ * Tests for {@link org.wamblee.usermgt.hibernate.HibernateGroupSet} 
+ */
+public class HibernateUserSetTest extends InMemoryUserSetTest {
+    
+    private static final String USER_TABLE = "USERS";
+    private static final String GROUP_TABLE = "GROUPS"; 
+    
+    private static final String USER_QUERY = "select * from " + USER_TABLE + " where name = ?";
+    private static final String GROUP_QUERY = "select * from " + GROUP_TABLE + " where name = ?"; 
+
+    public HibernateUserSetTest() { 
+        super(UsermgtSpringConfigFiles.class, UsermgtHibernateMappingFiles.class);
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.usermgt.InMemoryUserSetTest#setUp()
+     */
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        clearUserCache();
+    }
+
+    /**
+     * Clears the user cache.  
+     */
+    private void clearUserCache() {
+        BeanKernel.getBeanFactory().find("userCache", Cache.class).clear();
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.usermgt.InMemoryGroupSetTest#checkGroupCount(int)
+     */
+    @Override
+    protected void checkUserCount(int aSize) throws SQLException {
+        super.flush();
+        super.checkUserCount(aSize);
+        assertEquals(aSize, getTableSize(USER_TABLE));
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.usermgt.InMemoryGroupSetTest#checkGroupExists(java.lang.String)
+     */
+    @Override
+    protected void checkUserExists(String aUser) throws SQLException {
+        flush(); 
+        ResultSet result = executeQuery(USER_QUERY, aUser);
+        assertEquals(1, countResultSet(result));
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.usermgt.InMemoryGroupSetTest#checkGroupNotExists(java.lang.String)
+     */
+    @Override
+    protected void checkUserNotExists(String aUser) throws SQLException {
+       flush(); 
+       ResultSet result = executeQuery(USER_QUERY, aUser); 
+       assertEquals(0, countResultSet(result));
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.usermgt.InMemoryGroupSetTest#checkGroupCount(int)
+     */
+    @Override
+    protected void checkGroupCount(int aSize) throws SQLException {
+        super.flush(); 
+        assertEquals(aSize, getTableSize(GROUP_TABLE));
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.usermgt.InMemoryGroupSetTest#checkGroupExists(java.lang.String)
+     */
+    @Override
+    protected void checkGroupExists(String aGroup) throws SQLException {
+        flush(); 
+       
+        ResultSet result = executeQuery(GROUP_QUERY, aGroup);
+        assertEquals(1, countResultSet(result));
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.usermgt.InMemoryGroupSetTest#checkGroupNotExists(java.lang.String)
+     */
+    @Override
+    protected void checkGroupNotExists(String aGroup) throws SQLException {
+       flush(); 
+       ResultSet result = executeQuery(GROUP_QUERY, aGroup); 
+       assertEquals(0, countResultSet(result));
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.usermgt.InMemoryGroupSetTest#createGroupSet()
+     */
+    @Override
+    protected UserSet createUserSet() {
+        return BeanKernel.getBeanFactory().find(UserSet.class); 
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.usermgt.InMemoryUserSetTest#createGroupSet()
+     */
+    @Override
+    protected GroupSet createGroupSet() {
+        return BeanKernel.getBeanFactory().find(GroupSet.class);
+    }
+
+    /**
+     * Reproduction of a bug. 
+     * Create a user which is in group1
+     * Add it to a second group group2.
+     * Remove the user from group1.
+     * Verify the user is in group2. 
+     */
+    public void testVerifyAddRemove() throws SQLException, UserMgtException {
+        cleanDatabase(); // just to be sure.
+        GroupSet groups = getGroups(); 
+        assertEquals(0, groups.size()); 
+        Group group1 = createGroup("group1");
+        Group group2 = createGroup("group2");
+        groups.add(group1); 
+        groups.add(group2); 
+        checkGroupExists("group1"); 
+        checkGroupExists("group2");
+        
+        User user = createUser("user", PASSWORD, group1);
+        getUsers().add(user);    
+        checkUserExists("user");
+     
+        addUserToGroup(user, group2);
+        getUsers().userModified(user);
+        clearUserCache(); 
+        User user2 = getUsers().find("user");
+        Set<Group> userGroups = user2.getGroups();
+        assertTrue(user2.isInGroup("group1")); 
+        assertTrue(user2.isInGroup("group2"));
+        assertEquals(2, userGroups.size());
+        
+        removeUserFromGroup(user, group1); 
+        getUsers().userModified(user); 
+        clearUserCache(); 
+        user2 = getUsers().find("user"); 
+        userGroups = user2.getGroups();
+        assertFalse(user2.isInGroup("group1")); 
+        assertTrue(user2.isInGroup("group2"));
+        assertEquals(1, userGroups.size());
+    }
+    
+}
diff --git a/security/src/test/resources/hbm/AuthorizationRule.hbm.xml b/security/src/test/resources/hbm/AuthorizationRule.hbm.xml
new file mode 100644 (file)
index 0000000..33f5d94
--- /dev/null
@@ -0,0 +1,43 @@
+<?xml version="1.0"?>
+<!DOCTYPE hibernate-mapping PUBLIC
+    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
+    "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
+    
+    <hibernate-mapping>
+        
+        <class name="org.wamblee.security.authorization.AuthorizationRule" 
+               table="AUTHORIZATION_RULES" 
+               select-before-update="true"
+               lazy="false">
+            <id name="primaryKey" column="ID" type="long">
+                <generator class="native"/>
+            </id>
+            <discriminator column="TYPE" type="string"/>
+            <version name="persistedVersion" column="VERSION"/>
+            
+            <subclass name="org.wamblee.security.authorization.UrlAuthorizationRule"
+                discriminator-value="ISINGROUP"
+                lazy="false">
+                <property name="authorizationResultString" column="RESULT"/>
+                <property name="resourceClassName" column="RESOURCE_CLASSNAME"/>
+                <many-to-one name="userCondition" 
+                             class="org.wamblee.security.authorization.UserCondition"
+                             column="USERCONDITION_ID"
+                             cascade="all"
+                             lazy="false"/>
+                <many-to-one name="pathCondition" 
+                    class="org.wamblee.security.authorization.PathCondition"
+                    column="PATHCONDITION_ID"
+                    cascade="all"
+                    lazy="false"/>
+                <many-to-one name="operationCondition" 
+                    class="org.wamblee.security.authorization.OperationCondition"
+                    column="OPERATIONCONDITION_ID"
+                    cascade="all"
+                    lazy="false"/> 
+            </subclass>
+            
+        </class>
+        
+        
+    </hibernate-mapping>
\ No newline at end of file
diff --git a/security/src/test/resources/hbm/AuthorizationService.hbm.xml b/security/src/test/resources/hbm/AuthorizationService.hbm.xml
new file mode 100644 (file)
index 0000000..f8e036c
--- /dev/null
@@ -0,0 +1,37 @@
+<?xml version="1.0"?>
+<!DOCTYPE hibernate-mapping PUBLIC
+    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
+    "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
+    
+    <hibernate-mapping>
+        
+        <class name="org.wamblee.security.authorization.AuthorizationService" table="AUTHORIZATION_SERVICE" 
+               select-before-update="true">
+            <id name="primaryKey" column="ID" type="long">
+                <generator class="native"/>
+            </id>
+            <discriminator column="TYPE" type="string"/>
+            <version name="persistedVersion" column="VERSION"/>
+            
+            <subclass name="org.wamblee.security.authorization.DefaultAuthorizationService"
+                discriminator-value="DEFAULT">
+                
+                <property name="name" column="NAME"/>
+               
+                <list name="mappedRules" table="AUTHORIZATION_SERVICE_RULES" lazy="false" cascade="all-delete-orphan">
+                    <key column="ID"/>
+                    <index column="POSITION"/>
+                    <many-to-many class="org.wamblee.security.authorization.AuthorizationRule"
+                       column="RULE_ID"/>
+                </list>
+            </subclass>
+            
+        </class>
+        
+        <query name="findAuthorizationServiceByName">
+            select service
+            from org.wamblee.security.authorization.AuthorizationService service
+            where service.name = :name 
+        </query>
+        
+    </hibernate-mapping>
\ No newline at end of file
diff --git a/security/src/test/resources/hbm/Group.hbm.xml b/security/src/test/resources/hbm/Group.hbm.xml
new file mode 100644 (file)
index 0000000..41e5e6b
--- /dev/null
@@ -0,0 +1,25 @@
+<?xml version="1.0"?>
+<!DOCTYPE hibernate-mapping PUBLIC
+    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
+    "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
+    
+<hibernate-mapping>
+    
+    <class name="org.wamblee.usermgt.Group" table="GROUPS" select-before-update="true">
+        <id name="primaryKey" column="ID" type="long">
+            <generator class="native"/>
+        </id>
+        <version name="persistedVersion" column="VERSION"/>
+        <property name="name" column="NAME" unique="true"/>
+    </class>
+    
+    <query name="findGroupByName">
+        from org.wamblee.usermgt.Group grp where grp.name = :name
+    </query>
+    
+    <query name="countGroups">
+        select count(*)
+        from org.wamblee.usermgt.Group group 
+    </query>
+    
+</hibernate-mapping>
\ No newline at end of file
diff --git a/security/src/test/resources/hbm/OperationCondition.hbm.xml b/security/src/test/resources/hbm/OperationCondition.hbm.xml
new file mode 100644 (file)
index 0000000..5ab4b0d
--- /dev/null
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<!DOCTYPE hibernate-mapping PUBLIC
+    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
+    "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
+    
+    <hibernate-mapping>
+        
+        <class name="org.wamblee.security.authorization.OperationCondition" table="OPERATION_CONDITIONS" 
+               select-before-update="true"
+               lazy="false">
+            <id name="primaryKey" column="ID" type="long">
+                <generator class="native"/>
+            </id>
+            <discriminator column="TYPE" type="string"/>
+            <version name="persistedVersion" column="VERSION"/>
+            
+            <subclass name="org.wamblee.security.authorization.IsaOperationCondition"
+                discriminator-value="ISA"
+                lazy="false">
+                <property name="operationString" column="OPERATION"/>
+            </subclass>
+            
+        </class>
+        
+        
+    </hibernate-mapping>
\ No newline at end of file
diff --git a/security/src/test/resources/hbm/OperationCondition.hbm.xmlxx b/security/src/test/resources/hbm/OperationCondition.hbm.xmlxx
new file mode 100644 (file)
index 0000000..250030e
--- /dev/null
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<!DOCTYPE hibernate-mapping PUBLIC
+    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
+    "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
+    
+    <hibernate-mapping>
+        
+        <class name="org.wamblee.security.authorization.PathCondition" table="PATH_CONDITIONS" 
+               select-before-update="true"
+               lazy="false">
+            <id name="primaryKey" column="ID" type="long">
+                <generator class="native"/>
+            </id>
+            <discriminator column="TYPE" type="string"/>
+            <version name="persistedVersion" column="VERSION"/>
+            
+            <subclass name="org.wamblee.security.authorization.StartsWithPathCondition"
+                discriminator-value="STARTS_WITH"
+                lazy="false">
+                <property name="path" column="PATH"/>
+            </subclass>
+            
+        </class>
+        
+        
+    </hibernate-mapping>
\ No newline at end of file
diff --git a/security/src/test/resources/hbm/PageAuthorizationRule.hbm.xml b/security/src/test/resources/hbm/PageAuthorizationRule.hbm.xml
new file mode 100644 (file)
index 0000000..332e5d8
--- /dev/null
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<!DOCTYPE hibernate-mapping PUBLIC
+    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
+    "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
+    
+    <hibernate-mapping>
+        <subclass name="org.wamblee.photos.authorizationrules.PageAuthorizationRule"
+            extends="org.wamblee.security.authorization.UrlAuthorizationRule"
+            discriminator-value="PAGE"
+            lazy="false"/>
+      
+    </hibernate-mapping>
\ No newline at end of file
diff --git a/security/src/test/resources/hbm/PathCondition.hbm.xml b/security/src/test/resources/hbm/PathCondition.hbm.xml
new file mode 100644 (file)
index 0000000..c43c0a8
--- /dev/null
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+<!DOCTYPE hibernate-mapping PUBLIC
+    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
+    "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
+    
+    <hibernate-mapping>
+        
+        <class name="org.wamblee.security.authorization.PathCondition" table="PATH_CONDITIONS" 
+               select-before-update="true"
+               lazy="false">
+            <id name="primaryKey" column="ID" type="long">
+                <generator class="native"/>
+            </id>
+            <discriminator column="TYPE" type="string"/>
+            <version name="persistedVersion" column="VERSION"/>
+            
+            <subclass name="org.wamblee.security.authorization.RegexpPathCondition"
+                discriminator-value="REGEXP"
+                lazy="false">
+                <property name="pattern" column="PATTERN"/>
+                
+                <subclass name="org.wamblee.security.authorization.StartsWithPathCondition"
+                    discriminator-value="STARTS_WITH"
+                    lazy="false">
+                </subclass>
+                
+            </subclass>
+            
+        </class>
+        
+        
+    </hibernate-mapping>
\ No newline at end of file
diff --git a/security/src/test/resources/hbm/PhotoAuthorizationRule.hbm.xml b/security/src/test/resources/hbm/PhotoAuthorizationRule.hbm.xml
new file mode 100644 (file)
index 0000000..fc00dda
--- /dev/null
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<!DOCTYPE hibernate-mapping PUBLIC
+    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
+    "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
+    
+    <hibernate-mapping>
+        <subclass name="org.wamblee.photos.authorizationrules.PhotoAuthorizationRule"
+            extends="org.wamblee.security.authorization.AuthorizationRule"
+            discriminator-value="PHOTOS"
+            lazy="false"/>
+      
+    </hibernate-mapping>
\ No newline at end of file
diff --git a/security/src/test/resources/hbm/TestAuthorizationRule.hbm.xml b/security/src/test/resources/hbm/TestAuthorizationRule.hbm.xml
new file mode 100644 (file)
index 0000000..7f796f5
--- /dev/null
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<!DOCTYPE hibernate-mapping PUBLIC
+    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
+    "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
+    
+    <hibernate-mapping>
+        <subclass name="org.wamblee.security.authorization.TestAuthorizationRule"
+            extends="org.wamblee.security.authorization.UrlAuthorizationRule"
+            discriminator-value="TEST"
+            lazy="false"/>
+      
+    </hibernate-mapping>
\ No newline at end of file
diff --git a/security/src/test/resources/hbm/User.hbm.xml b/security/src/test/resources/hbm/User.hbm.xml
new file mode 100644 (file)
index 0000000..eefbfa2
--- /dev/null
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<!DOCTYPE hibernate-mapping PUBLIC
+    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
+    "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
+    
+<hibernate-mapping>
+    
+    <class name="org.wamblee.usermgt.User" table="USERS" select-before-update="true">
+        <id name="primaryKey" column="ID" type="long">
+            <generator class="native"/>
+        </id>
+        <version name="persistedVersion" column="VERSION"/>
+        <property name="name" column="NAME" unique="true"/>
+        <property name="passwordString" column="PASSWORD"/>
+        <set name="groupSet" table="USER_GROUPS" lazy="false"> 
+            <key column="USER_ID"/>
+            <many-to-many class="org.wamblee.usermgt.Group" column="GROUP_ID"/>
+        </set>
+    </class>
+    
+    <query name="findUserByName">
+        from org.wamblee.usermgt.User user where user.name = :name
+    </query>
+    
+    <query name="findUserByGroupName">
+      select user
+          from org.wamblee.usermgt.User user
+            join user.groupSet grp
+            where grp.name = :name 
+    </query>
+    
+    <query name="countUsers">
+        select count(*)
+        from org.wamblee.usermgt.User user 
+    </query>
+    
+    
+</hibernate-mapping>
\ No newline at end of file
diff --git a/security/src/test/resources/hbm/UserCondition.hbm.xml b/security/src/test/resources/hbm/UserCondition.hbm.xml
new file mode 100644 (file)
index 0000000..fc83423
--- /dev/null
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+<!DOCTYPE hibernate-mapping PUBLIC
+    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
+    "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
+    
+    <hibernate-mapping>
+        
+        <class name="org.wamblee.security.authorization.UserCondition" table="USER_CONDITIONS" 
+               select-before-update="true"
+               lazy="false">
+            <id name="primaryKey" column="ID" type="long">
+                <generator class="native"/>
+            </id>
+            <discriminator column="TYPE" type="string"/>
+            <version name="persistedVersion" column="VERSION"/>
+            
+            <subclass name="org.wamblee.security.authorization.GroupUserCondition"
+                discriminator-value="URL"
+                lazy="false">
+                <property name="group" column="GROUPNAME"/>
+            </subclass>
+            
+            <subclass name="org.wamblee.security.authorization.AnyUserCondition"
+                discriminator-value="ANY"
+                lazy="false">
+            </subclass>
+            
+        </class>
+        
+        
+    </hibernate-mapping>
\ No newline at end of file
diff --git a/security/src/test/resources/hbm/ehcache.xml b/security/src/test/resources/hbm/ehcache.xml
new file mode 100644 (file)
index 0000000..ee79950
--- /dev/null
@@ -0,0 +1,75 @@
+<ehcache>
+
+    <!-- Sets the path to the directory where cache .data files are created.
+
+         If the path is a Java System Property it is replaced by
+         its value in the running VM.
+
+         The following properties are translated:
+         user.home - User's home directory
+         user.dir - User's current working directory
+         java.io.tmpdir - Default temp file path -->
+    <diskStore path="java.io.tmpdir"/>
+
+
+    <!--Default Cache configuration. These will applied to caches programmatically created through
+        the CacheManager.
+
+        The following attributes are required:
+
+        maxElementsInMemory            - Sets the maximum number of objects that will be created in memory
+        eternal                        - Sets whether elements are eternal. If eternal,  timeouts are ignored and the
+                                         element is never expired.
+        overflowToDisk                 - Sets whether elements can overflow to disk when the in-memory cache
+                                         has reached the maxInMemory limit.
+
+        The following attributes are optional:
+        timeToIdleSeconds              - Sets the time to idle for an element before it expires.
+                                         i.e. The maximum amount of time between accesses before an element expires
+                                         Is only used if the element is not eternal.
+                                         Optional attribute. A value of 0 means that an Element can idle for infinity.
+                                         The default value is 0.
+        timeToLiveSeconds              - Sets the time to live for an element before it expires.
+                                         i.e. The maximum time between creation time and when an element expires.
+                                         Is only used if the element is not eternal.
+                                         Optional attribute. A value of 0 means that and Element can live for infinity.
+                                         The default value is 0.
+        diskPersistent                 - Whether the disk store persists between restarts of the Virtual Machine.
+                                         The default value is false.
+        diskExpiryThreadIntervalSeconds- The number of seconds between runs of the disk expiry thread. The default value
+                                         is 120 seconds.
+        -->
+
+    <defaultCache
+        maxElementsInMemory="10000"
+        eternal="false"
+        overflowToDisk="false"
+        timeToIdleSeconds="120"
+        timeToLiveSeconds="120"
+        diskPersistent="false"
+        diskExpiryThreadIntervalSeconds="120"
+        />
+        
+    <cache
+        name="users"
+        maxElementsInMemory="10000"
+        eternal="false"
+        overflowToDisk="false"
+        timeToIdleSeconds="120"
+        timeToLiveSeconds="120"
+        diskPersistent="false"
+        diskExpiryThreadIntervalSeconds="120"
+    />
+    
+    <cache
+        name="photos"
+        maxElementsInMemory="1000"
+        eternal="false"
+        overflowToDisk="false"
+        timeToIdleSeconds="120"
+        timeToLiveSeconds="120"
+        diskPersistent="false"
+        diskExpiryThreadIntervalSeconds="120"
+    />
+    
+</ehcache>
diff --git a/security/src/test/resources/hbm/hibernate.properties b/security/src/test/resources/hbm/hibernate.properties
new file mode 100644 (file)
index 0000000..42491b2
--- /dev/null
@@ -0,0 +1,20 @@
+
+###################################################################################
+# dialect
+###################################################################################
+hibernate.dialect=org.hibernate.dialect.MySQLInnoDBDialect
+
+###################################################################################
+# debugging settings: Log4j configuration can provide more detail. 
+###################################################################################
+hibernate.show_sql=false
+
+###################################################################################
+# hibernate cache provider
+###################################################################################
+hibernate.cache.provider=org.hibernate.cache.EhCacheProvider
+
+###################################################################################
+# query cache
+###################################################################################
+hibernate.cache.use_query_cache=true
\ No newline at end of file
diff --git a/security/src/test/resources/properties/test.org.wamblee.security.database.properties b/security/src/test/resources/properties/test.org.wamblee.security.database.properties
new file mode 100644 (file)
index 0000000..4c2ddd9
--- /dev/null
@@ -0,0 +1,8 @@
+
+
+# Database properties for test runs in a J2SE environment. 
+
+database.driver=com.mysql.jdbc.Driver
+database.url=jdbc:mysql://localhost:3306/test
+database.username=erik
+database.password=abc123
\ No newline at end of file
diff --git a/security/src/test/resources/properties/test.org.wamblee.security.ehcache.xml b/security/src/test/resources/properties/test.org.wamblee.security.ehcache.xml
new file mode 100644 (file)
index 0000000..ee79950
--- /dev/null
@@ -0,0 +1,75 @@
+<ehcache>
+
+    <!-- Sets the path to the directory where cache .data files are created.
+
+         If the path is a Java System Property it is replaced by
+         its value in the running VM.
+
+         The following properties are translated:
+         user.home - User's home directory
+         user.dir - User's current working directory
+         java.io.tmpdir - Default temp file path -->
+    <diskStore path="java.io.tmpdir"/>
+
+
+    <!--Default Cache configuration. These will applied to caches programmatically created through
+        the CacheManager.
+
+        The following attributes are required:
+
+        maxElementsInMemory            - Sets the maximum number of objects that will be created in memory
+        eternal                        - Sets whether elements are eternal. If eternal,  timeouts are ignored and the
+                                         element is never expired.
+        overflowToDisk                 - Sets whether elements can overflow to disk when the in-memory cache
+                                         has reached the maxInMemory limit.
+
+        The following attributes are optional:
+        timeToIdleSeconds              - Sets the time to idle for an element before it expires.
+                                         i.e. The maximum amount of time between accesses before an element expires
+                                         Is only used if the element is not eternal.
+                                         Optional attribute. A value of 0 means that an Element can idle for infinity.
+                                         The default value is 0.
+        timeToLiveSeconds              - Sets the time to live for an element before it expires.
+                                         i.e. The maximum time between creation time and when an element expires.
+                                         Is only used if the element is not eternal.
+                                         Optional attribute. A value of 0 means that and Element can live for infinity.
+                                         The default value is 0.
+        diskPersistent                 - Whether the disk store persists between restarts of the Virtual Machine.
+                                         The default value is false.
+        diskExpiryThreadIntervalSeconds- The number of seconds between runs of the disk expiry thread. The default value
+                                         is 120 seconds.
+        -->
+
+    <defaultCache
+        maxElementsInMemory="10000"
+        eternal="false"
+        overflowToDisk="false"
+        timeToIdleSeconds="120"
+        timeToLiveSeconds="120"
+        diskPersistent="false"
+        diskExpiryThreadIntervalSeconds="120"
+        />
+        
+    <cache
+        name="users"
+        maxElementsInMemory="10000"
+        eternal="false"
+        overflowToDisk="false"
+        timeToIdleSeconds="120"
+        timeToLiveSeconds="120"
+        diskPersistent="false"
+        diskExpiryThreadIntervalSeconds="120"
+    />
+    
+    <cache
+        name="photos"
+        maxElementsInMemory="1000"
+        eternal="false"
+        overflowToDisk="false"
+        timeToIdleSeconds="120"
+        timeToLiveSeconds="120"
+        diskPersistent="false"
+        diskExpiryThreadIntervalSeconds="120"
+    />
+    
+</ehcache>
diff --git a/security/src/test/resources/properties/test.org.wamblee.security.hibernate.properties b/security/src/test/resources/properties/test.org.wamblee.security.hibernate.properties
new file mode 100644 (file)
index 0000000..42491b2
--- /dev/null
@@ -0,0 +1,20 @@
+
+###################################################################################
+# dialect
+###################################################################################
+hibernate.dialect=org.hibernate.dialect.MySQLInnoDBDialect
+
+###################################################################################
+# debugging settings: Log4j configuration can provide more detail. 
+###################################################################################
+hibernate.show_sql=false
+
+###################################################################################
+# hibernate cache provider
+###################################################################################
+hibernate.cache.provider=org.hibernate.cache.EhCacheProvider
+
+###################################################################################
+# query cache
+###################################################################################
+hibernate.cache.use_query_cache=true
\ No newline at end of file
diff --git a/security/src/test/resources/properties/test.org.wamblee.security.usermgt.properties b/security/src/test/resources/properties/test.org.wamblee.security.usermgt.properties
new file mode 100644 (file)
index 0000000..aa84735
--- /dev/null
@@ -0,0 +1,5 @@
+
+##############################################################################
+# Name of the administrators group
+##############################################################################
+org.wamblee.security.admingroup=administrators
diff --git a/security/src/test/resources/spring/test.org.wamblee.security.authorization.xml b/security/src/test/resources/spring/test.org.wamblee.security.authorization.xml
new file mode 100644 (file)
index 0000000..fe1cb4a
--- /dev/null
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+<beans>
+    
+    <bean id="org.wamblee.usermgt.UserAccessor"
+        class="org.wamblee.security.authorization.TestUserAccessor">
+        
+    </bean>
+    
+    <bean id="org.wamblee.security.authorization.OperationRegistry"
+        class="org.wamblee.security.authorization.DefaultOperationRegistry">
+        <constructor-arg>
+            <list>
+                <bean class="org.wamblee.security.authorization.AllOperation"/>
+                <bean class="org.wamblee.security.authorization.CreateOperation"/>
+                <bean class="org.wamblee.security.authorization.DeleteOperation"/>
+                <bean class="org.wamblee.security.authorization.ReadOperation"/>
+                <bean class="org.wamblee.security.authorization.WriteOperation"/>                
+            </list>
+        </constructor-arg>
+    </bean>
+    
+    <bean id="org.wamblee.security.authorization.AuthorizationService"
+        class="org.wamblee.security.authorization.hibernate.PersistentAuthorizationService">
+        <constructor-arg><value>DEFAULT</value></constructor-arg>
+        <constructor-arg><ref bean="org.springframework.orm.hibernate3.HibernateTemplate"/></constructor-arg>
+        <constructor-arg><ref bean="org.wamblee.usermgt.UserAccessor"/></constructor-arg>
+        <constructor-arg><value type="long">10000</value></constructor-arg>
+    </bean>
+    
+    <!-- any user -->
+    <bean id="anyUserCondition" class="org.wamblee.security.authorization.AnyUserCondition">
+    </bean>
+    
+    <!-- administrators -->
+    <bean id="adminUserCondition" class="org.wamblee.security.authorization.GroupUserCondition">
+        <constructor-arg><value>administrators</value></constructor-arg>
+    </bean>
+   
+</beans> 
\ No newline at end of file
diff --git a/security/src/test/resources/spring/test.org.wamblee.security.database.xml b/security/src/test/resources/spring/test.org.wamblee.security.database.xml
new file mode 100644 (file)
index 0000000..af51a1e
--- /dev/null
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+<!-- This is the Spring configuration to define the database-related stuff for the
+    all persistence tests.  -->
+<beans>
+    
+    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
+        <property name="dataSource">
+            <ref bean="dataSource"/>
+        </property>
+        <property name="hibernateProperties">
+            <props>
+                <prop key="hibernate.dialect">${hibernate.dialect}</prop>
+                <prop key="hibernate.cache.provider_class">${hibernate.cache.provider}</prop>
+                <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
+                <prop key="hibernate.cache.use_query_cache">${hibernate.cache.use_query_cache}</prop>
+            </props>
+        </property>
+        <property name="mappingResources"><ref bean="hibernateMappingFiles"/></property>
+    </bean>
+    
+    <bean id="transactionManager"
+        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
+        <property name="sessionFactory">
+            <ref local="sessionFactory"/>
+        </property>
+    </bean>
+    
+    <!--  Hibernate template used within test code for addition 
+            Hibernate-specific stuff -->
+    <bean id="org.springframework.orm.hibernate3.HibernateTemplate" 
+        class="org.springframework.orm.hibernate3.HibernateTemplate">
+        <property name="sessionFactory">
+            <ref bean="sessionFactory"/>
+        </property>
+    </bean>
+</beans>
diff --git a/security/src/test/resources/spring/test.org.wamblee.security.datasource.xml b/security/src/test/resources/spring/test.org.wamblee.security.datasource.xml
new file mode 100644 (file)
index 0000000..ec3d26a
--- /dev/null
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+<!-- This is the Spring configuration to define the database-related stuff for the
+    all persistence tests.  -->
+    <beans>
+        <bean id="dataSource" 
+            class="org.springframework.jdbc.datasource.DriverManagerDataSource">
+            <property name="driverClassName"><value>${database.driver}</value></property>
+            <property name="url"><value>${database.url}</value></property>
+            <property name="username"><value>${database.username}</value></property>
+            <property name="password"><value>${database.password}</value></property>
+        </bean>
+    </beans>
+    
\ No newline at end of file
diff --git a/security/src/test/resources/spring/test.org.wamblee.security.properties.xml b/security/src/test/resources/spring/test.org.wamblee.security.properties.xml
new file mode 100644 (file)
index 0000000..5fa2f72
--- /dev/null
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+ <beans> 
+    
+    <bean id="propertyBean"
+        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
+        <property name="locations">
+            <list>
+                <value>properties/test.org.wamblee.security.hibernate.properties</value>
+                <value>properties/test.org.wamblee.security.usermgt.properties</value>
+                <value>properties/test.org.wamblee.security.database.properties</value>
+            </list>
+        </property>
+    </bean> 
+ </beans>
\ No newline at end of file
diff --git a/security/src/test/resources/spring/test.org.wamblee.security.usermgt.xml b/security/src/test/resources/spring/test.org.wamblee.security.usermgt.xml
new file mode 100644 (file)
index 0000000..a7abe50
--- /dev/null
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+ <beans> 
+    
+     <bean id="cacheConfig" class="org.wamblee.io.ClassPathResource">
+         <constructor-arg><value>properties/test.org.wamblee.security.ehcache.xml</value></constructor-arg>
+     </bean>
+     
+     <bean id="userCache" class="org.wamblee.cache.EhCache">
+         <constructor-arg><ref local="cacheConfig"/></constructor-arg>
+         <constructor-arg><value>users</value></constructor-arg>
+     </bean> 
+     
+    
+    <bean id="passwordValidator" 
+         class="org.wamblee.usermgt.RegexpNameValidator">
+             <constructor-arg><value>.{5,}</value></constructor-arg>
+             <constructor-arg><value>INVALID_PASSWORD</value></constructor-arg>
+             <constructor-arg><value>Password must have at least 5 characters</value></constructor-arg>
+    </bean>
+    
+     <bean id="passwordDigester"
+         class="org.wamblee.security.encryption.Md5HexMessageDigester">
+     </bean>
+    
+    <bean id="org.wamblee.usermgt.UserSet"
+        class="org.wamblee.usermgt.hibernate.HibernateUserSet">
+        <constructor-arg><ref local="userCache"/></constructor-arg>
+        <constructor-arg><ref local="passwordValidator"/></constructor-arg>
+        <constructor-arg><ref local="passwordDigester"/></constructor-arg>
+        
+        <property name="sessionFactory"><ref bean="sessionFactory"/></property>
+        
+    </bean>
+    
+    <bean id="org.wamblee.usermgt.GroupSet"
+        class="org.wamblee.usermgt.hibernate.HibernateGroupSet">
+        
+        <property name="sessionFactory"><ref bean="sessionFactory"/></property>
+    </bean> 
+    
+    <bean id="org.wamblee.usermgt.UserAdministration-target"
+        class="org.wamblee.usermgt.UserAdministrationImpl">
+        
+        <constructor-arg>
+           <ref local="org.wamblee.usermgt.UserSet"/>
+        </constructor-arg>
+        
+        <constructor-arg>
+            <ref local="org.wamblee.usermgt.GroupSet"/>
+        </constructor-arg>
+        
+        <constructor-arg>
+            <bean class="org.wamblee.usermgt.RegexpNameValidator">
+                <constructor-arg><value>[a-zA-Z]+[a-zA-Z0-9]*</value></constructor-arg>
+                <constructor-arg><value>INVALID_USERNAME</value></constructor-arg>
+                <constructor-arg><value></value></constructor-arg>
+            </bean>
+        </constructor-arg>
+        
+        <constructor-arg>
+            <bean class="org.wamblee.usermgt.RegexpNameValidator">
+                <constructor-arg><value>[a-zA-Z]+[a-zA-Z0-9]*</value></constructor-arg>
+                <constructor-arg><value>INVALID_GROUPNAME</value></constructor-arg>
+                <constructor-arg><value></value></constructor-arg>
+            </bean>
+        </constructor-arg>
+      
+    </bean>
+    
+    <bean id="usermanagement-lock" class="org.wamblee.concurrency.JvmLock"/>
+    
+     <bean id="usermanagement-lock-advice" class="org.wamblee.concurrency.LockAdvice">
+         <constructor-arg><ref bean="usermanagement-lock"/></constructor-arg>
+     </bean>
+     
+     <bean id="org.wamblee.usermgt.UserAdministration" 
+         class="org.springframework.aop.framework.ProxyFactoryBean">
+         <property name="proxyInterfaces"><value>org.wamblee.usermgt.UserAdministration</value></property>
+         <property name="interceptorNames"><value>usermanagement-lock-advice</value></property>
+         <property name="target"><ref bean="org.wamblee.usermgt.UserAdministration-target"/></property>
+     </bean>
+     
+     <bean id="usermgtInitializer"
+         class="org.wamblee.usermgt.UserAdminInitializer">
+         <constructor-arg><ref local="org.wamblee.usermgt.UserAdministration"/></constructor-arg>
+         <!-- users -->
+         <constructor-arg>
+             <list>
+                 <value>erik</value>
+                 <value>admin</value>
+             </list>
+         </constructor-arg>
+         <!-- groups -->
+         <constructor-arg>
+             <list>
+                 <value>users</value>
+                 <value>${org.wamblee.security.admingroup}</value>
+             </list>
+         </constructor-arg>
+         <!-- passwords -->
+         <constructor-arg>
+             <list>
+                 <value>abc123</value>
+                 <value>abc123</value>
+             </list>
+         </constructor-arg>
+     </bean>
+     
+ </beans>
\ No newline at end of file
index 8b66e0e2419473e240b830108fb843c3efbed599..29334e003e78cea974ae913e64e919dfe6a841f6 100644 (file)
@@ -21,6 +21,7 @@ import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -453,7 +454,7 @@ public class SpringTestCase extends MockObjectTestCase {
     }
 
     /**
-     * Executes an SQL query within a transaction.
+     * Executes an SQL query.
      * 
      * @param aSql
      *            Query to execute.
@@ -479,7 +480,7 @@ public class SpringTestCase extends MockObjectTestCase {
     }
 
     /**
-     * Executes a query within a transaction. See
+     * Executes a query. See
      * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
      * supported argument types.
      * 
@@ -490,22 +491,16 @@ public class SpringTestCase extends MockObjectTestCase {
      * @return Result set.
      */
     public ResultSet executeQuery(final String aSql, final Object[] aArgs) {
-        Map results = executeTransaction(new TestTransactionCallback() {
-            public Map execute() throws Exception {
-                Connection connection = getConnection();
-
-                PreparedStatement statement = connection.prepareStatement(aSql);
-                setPreparedParams(aArgs, statement);
+        try {
+                       Connection connection = getConnection();
 
-                ResultSet resultSet = statement.executeQuery();
-                TreeMap results = new TreeMap();
-                results.put("resultSet", resultSet);
+                       PreparedStatement statement = connection.prepareStatement(aSql);
+                       setPreparedParams(aArgs, statement);
 
-                return results;
-            }
-        });
-
-        return (ResultSet) results.get("resultSet");
+                       return statement.executeQuery();
+               } catch (SQLException e) {
+                       throw new RuntimeException(e);
+               }
     }
 
     /**
@@ -576,14 +571,14 @@ public class SpringTestCase extends MockObjectTestCase {
      * @return
      * @throws SQLException
      */
-    protected int getTableSize(String aTable) throws SQLException {
+    protected int getTableSize(final String aTable) throws SQLException {
+
         ResultSet resultSet = executeQuery("select * from " + aTable);
         int count = 0;
 
         while (resultSet.next()) {
             count++;
         }
-
         return count;
     }