]> git.aero2k.de Git - tmp/jakarta-migration.git/commitdiff
CSPACE-642 a user on login is associated with a tenant
authorSanjay Dalal <sanjay.dalal@berkeley.edu>
Tue, 1 Dec 2009 21:45:08 +0000 (21:45 +0000)
committerSanjay Dalal <sanjay.dalal@berkeley.edu>
Tue, 1 Dec 2009 21:45:08 +0000 (21:45 +0000)
Account service now associates a user with tenant on account creation time if default identity provider is used. This association is retrieved (CSpacePrincipal) at the time of login. Tenant binding is retrieved using the tenant id established after login.
Moved db schema for default id provider to authentication/client.
Introduced ServiceException to capture error code and error reason. UnauthorizedException with 401 (derived from ServiceException) is thrown if tenant could not be found on login. Rest of the common exceptions are derived from ServiceException. Service resources changed to handle UnauthorizedException.

test: authentication client with security enabled on server (web.xml), all service tests without security

34 files changed:
pom.xml
services/account/service/src/main/java/org/collectionspace/services/account/AccountResource.java
services/account/service/src/main/java/org/collectionspace/services/account/storage/AccountStorageClient.java
services/acquisition/service/src/main/java/org/collectionspace/services/acquisition/AcquisitionResource.java
services/authentication/build.xml
services/authentication/client/build.xml [new file with mode: 0644]
services/authentication/client/pom.xml
services/authentication/client/src/main/resources/db/mysql/authentication.sql [new file with mode: 0644]
services/authentication/client/src/main/resources/db/mysql/test_authn.sql [new file with mode: 0644]
services/authentication/client/src/test/resources/META-INF/persistence.xml [new file with mode: 0644]
services/authentication/client/src/test/resources/hibernate.cfg.xml [new file with mode: 0644]
services/authentication/jaxb/src/main/resources/authentication_identity_provider.xsd
services/authentication/service/build.xml
services/authentication/service/pom.xml
services/authentication/service/src/main/java/org/collectionspace/authentication/CSpaceDBLoginModule.java
services/authentication/service/src/main/java/org/collectionspace/authentication/CSpacePrincipal.java [new file with mode: 0644]
services/authentication/service/src/main/resources/config/jboss-login-config.xml
services/authentication/service/src/main/resources/db/mysql/authentication.sql [deleted file]
services/authentication/service/src/main/resources/db/mysql/test_authn.sql [deleted file]
services/collectionobject/service/src/main/java/org/collectionspace/services/collectionobject/CollectionObjectResource.java
services/common/pom.xml
services/common/src/main/java/org/collectionspace/services/common/ServiceException.java [new file with mode: 0644]
services/common/src/main/java/org/collectionspace/services/common/context/AbstractServiceContext.java
services/common/src/main/java/org/collectionspace/services/common/context/MultipartServiceContextImpl.java
services/common/src/main/java/org/collectionspace/services/common/context/RemoteServiceContextImpl.java
services/common/src/main/java/org/collectionspace/services/common/document/BadRequestException.java
services/common/src/main/java/org/collectionspace/services/common/document/DocumentException.java
services/common/src/main/java/org/collectionspace/services/common/document/DocumentNotFoundException.java
services/common/src/main/java/org/collectionspace/services/common/security/ServiceForbiddenException.java [new file with mode: 0644]
services/common/src/main/java/org/collectionspace/services/common/security/UnauthorizedException.java [new file with mode: 0644]
services/intake/service/src/main/java/org/collectionspace/services/intake/IntakeResource.java
services/pom.xml
services/relation/service/src/main/java/org/collectionspace/services/relation/NewRelationResource.java
services/vocabulary/service/src/main/java/org/collectionspace/services/vocabulary/VocabularyResource.java

diff --git a/pom.xml b/pom.xml
index a4f6012709178e648585f03abd030695a8d09c5d..31a485ee08d410a134913b6eccbc948cdab49e88 100644 (file)
--- a/pom.xml
+++ b/pom.xml
                 <enabled>false</enabled>\r
             </snapshots>\r
         </repository>\r
+        <!-- some security binaries available under .com only perhaps due to licensing issues -->\r
+        <repository>\r
+            <id>jboss</id>\r
+            <url>http://repository.jboss.com/maven2</url>\r
+            <snapshots>\r
+                <enabled>false</enabled>\r
+            </snapshots>\r
+        </repository>\r
         <repository>\r
             <id>mojo</id>\r
             <name>mojo repo</name>\r
index e44155e693cf7a1d7f9a2dcd0ed6588675757b1f..b1d4a7a27099deb47db2473f3c0ae8016495587f 100644 (file)
@@ -40,12 +40,12 @@ import javax.ws.rs.core.UriInfo;
 import org.collectionspace.services.account.storage.AccountHandlerFactory;
 import org.collectionspace.services.account.storage.AccountStorageClient;
 import org.collectionspace.services.common.AbstractCollectionSpaceResource;
-import org.collectionspace.services.common.context.MultipartServiceContext;
 import org.collectionspace.services.common.context.RemoteServiceContextImpl;
 import org.collectionspace.services.common.context.ServiceContext;
 import org.collectionspace.services.common.document.DocumentFilter;
 import org.collectionspace.services.common.document.DocumentNotFoundException;
 import org.collectionspace.services.common.document.DocumentHandler;
+import org.collectionspace.services.common.security.UnauthorizedException;
 import org.collectionspace.services.common.storage.StorageClient;
 import org.jboss.resteasy.util.HttpResponseCodes;
 import org.slf4j.Logger;
@@ -66,7 +66,7 @@ public class AccountResource
         return serviceName;
     }
 
-    private <T> ServiceContext createServiceContext(T obj) {
+    private <T> ServiceContext createServiceContext(T obj) throws Exception {
         ServiceContext ctx = new RemoteServiceContextImpl<T, T>(getServiceName());
         ctx.setInput(obj);
         ctx.setDocumentType(AccountsCommon.class.getPackage().getName()); //persistence unit
@@ -99,6 +99,10 @@ public class AccountResource
             path.path("" + csid);
             Response response = Response.created(path.build()).build();
             return response;
+        } catch (UnauthorizedException ue) {
+            Response response = Response.status(
+                    Response.Status.UNAUTHORIZED).entity("Create failed reason " + ue.getErrorReason()).type("text/plain").build();
+            throw new WebApplicationException(response);
         } catch (Exception e) {
             if (logger.isDebugEnabled()) {
                 logger.debug("Caught exception in createAccount", e);
@@ -128,7 +132,11 @@ public class AccountResource
             ServiceContext ctx = createServiceContext((AccountsCommon) null);
             DocumentHandler handler = createDocumentHandler(ctx);
             getStorageClient(ctx).get(ctx, csid, handler);
-            result = (AccountsCommon)ctx.getOutput();
+            result = (AccountsCommon) ctx.getOutput();
+        } catch (UnauthorizedException ue) {
+            Response response = Response.status(
+                    Response.Status.UNAUTHORIZED).entity("Get failed reason " + ue.getErrorReason()).type("text/plain").build();
+            throw new WebApplicationException(response);
         } catch (DocumentNotFoundException dnfe) {
             if (logger.isDebugEnabled()) {
                 logger.debug("getAccount", dnfe);
@@ -165,7 +173,12 @@ public class AccountResource
             DocumentFilter myFilter = new DocumentFilter();
             handler.setDocumentFilter(myFilter);
             getStorageClient(ctx).getFiltered(ctx, handler);
-            accountList = (AccountsCommonList)handler.getCommonPartList();
+            accountList = (AccountsCommonList) handler.getCommonPartList();
+        } catch (UnauthorizedException ue) {
+            Response response = Response.status(
+                    Response.Status.UNAUTHORIZED).entity("Index failed reason " + ue.getErrorReason()).type("text/plain").build();
+            throw new WebApplicationException(response);
+
         } catch (Exception e) {
             if (logger.isDebugEnabled()) {
                 logger.debug("Caught exception in getAccountList", e);
@@ -197,7 +210,12 @@ public class AccountResource
             ServiceContext ctx = createServiceContext(theUpdate);
             DocumentHandler handler = createDocumentHandler(ctx);
             getStorageClient(ctx).update(ctx, csid, handler);
-            result = (AccountsCommon)ctx.getOutput();
+            result = (AccountsCommon) ctx.getOutput();
+        } catch (UnauthorizedException ue) {
+            Response response = Response.status(
+                    Response.Status.UNAUTHORIZED).entity("Update failed reason " + ue.getErrorReason()).type("text/plain").build();
+            throw new WebApplicationException(response);
+
         } catch (DocumentNotFoundException dnfe) {
             if (logger.isDebugEnabled()) {
                 logger.debug("caugth exception in updateAccount", dnfe);
@@ -232,6 +250,11 @@ public class AccountResource
             ServiceContext ctx = createServiceContext((AccountsCommon) null);
             getStorageClient(ctx).delete(ctx, csid);
             return Response.status(HttpResponseCodes.SC_OK).build();
+        } catch (UnauthorizedException ue) {
+            Response response = Response.status(
+                    Response.Status.UNAUTHORIZED).entity("Delete failed reason " + ue.getErrorReason()).type("text/plain").build();
+            throw new WebApplicationException(response);
+
         } catch (DocumentNotFoundException dnfe) {
             if (logger.isDebugEnabled()) {
                 logger.debug("caught exception in deleteAccount", dnfe);
index 8131f10ff8c84bfd366949f3c1974f5b8866f882..bda4a6234898a0f50dd9d32a714a8ac7d84f4e91 100644 (file)
@@ -28,7 +28,6 @@ import javax.persistence.EntityManagerFactory;
 import javax.persistence.Query;
 import org.apache.commons.codec.binary.Base64;
 import org.collectionspace.services.account.AccountsCommon;
-import org.collectionspace.services.account.AccountsCommon;
 import org.collectionspace.services.authentication.User;
 import org.collectionspace.services.common.context.ServiceContext;
 import org.collectionspace.services.common.document.BadRequestException;
@@ -82,7 +81,7 @@ public class AccountStorageClient extends JpaStorageClient {
             em.getTransaction().begin();
             //if userid and password are given, add to default id provider
             if (account.getUserId() != null && account.getPassword() != null) {
-                User user = createUser(account);
+                User user = createUser(account, ctx.getTenantId());
                 em.persist(user);
             }
             account.setTenantid(ctx.getTenantId());
@@ -194,8 +193,9 @@ public class AccountStorageClient extends JpaStorageClient {
         }
     }
 
-    private User createUser(AccountsCommon account) {
+    private User createUser(AccountsCommon account, String tenantId) {
         User user = new User();
+        user.setTenantid(tenantId);
         user.setUsername(account.getUserId());
         byte[] bpass = Base64.decodeBase64(account.getPassword());
         SecurityUtils.validatePassword(new String(bpass));
index 26ef178a1363ab9a84adfe7aedf45a4ddadbc908..aa722c1b6013eead07f2cc376f6f6ed594a6b253 100644 (file)
@@ -44,6 +44,7 @@ import org.collectionspace.services.common.context.MultipartServiceContextFactor
 import org.collectionspace.services.common.context.ServiceContext;
 import org.collectionspace.services.common.document.DocumentNotFoundException;
 import org.collectionspace.services.common.document.DocumentHandler;
+import org.collectionspace.services.common.security.UnauthorizedException;
 import org.jboss.resteasy.plugins.providers.multipart.MultipartInput;
 import org.jboss.resteasy.plugins.providers.multipart.MultipartOutput;
 import org.jboss.resteasy.util.HttpResponseCodes;
@@ -70,7 +71,7 @@ public class AcquisitionResource
                 ctx.getRepositoryClientType().toString());
         docHandler.setServiceContext(ctx);
         if (ctx.getInput() != null) {
-            Object obj = ((MultipartServiceContext)ctx).getInputPart(ctx.getCommonPartLabel(), AcquisitionsCommon.class);
+            Object obj = ((MultipartServiceContext) ctx).getInputPart(ctx.getCommonPartLabel(), AcquisitionsCommon.class);
             if (obj != null) {
                 docHandler.setCommonPart((AcquisitionsCommon) obj);
             }
@@ -93,6 +94,10 @@ public class AcquisitionResource
             path.path("" + csid);
             Response response = Response.created(path.build()).build();
             return response;
+        } catch (UnauthorizedException ue) {
+            Response response = Response.status(
+                    Response.Status.UNAUTHORIZED).entity("Create failed reason " + ue.getErrorReason()).type("text/plain").build();
+            throw new WebApplicationException(response);
         } catch (Exception e) {
             if (logger.isDebugEnabled()) {
                 logger.debug("Caught exception in createAcquisition", e);
@@ -123,6 +128,10 @@ public class AcquisitionResource
             DocumentHandler handler = createDocumentHandler(ctx);
             getRepositoryClient(ctx).get(ctx, csid, handler);
             result = (MultipartOutput) ctx.getOutput();
+        } catch (UnauthorizedException ue) {
+            Response response = Response.status(
+                    Response.Status.UNAUTHORIZED).entity("Get failed reason " + ue.getErrorReason()).type("text/plain").build();
+            throw new WebApplicationException(response);
         } catch (DocumentNotFoundException dnfe) {
             if (logger.isDebugEnabled()) {
                 logger.debug("getAcquisition", dnfe);
@@ -158,6 +167,10 @@ public class AcquisitionResource
             DocumentHandler handler = createDocumentHandler(ctx);
             getRepositoryClient(ctx).getAll(ctx, handler);
             acquisitionObjectList = (AcquisitionsCommonList) handler.getCommonPartList();
+        } catch (UnauthorizedException ue) {
+            Response response = Response.status(
+                    Response.Status.UNAUTHORIZED).entity("Index failed reason " + ue.getErrorReason()).type("text/plain").build();
+            throw new WebApplicationException(response);
         } catch (Exception e) {
             if (logger.isDebugEnabled()) {
                 logger.debug("Caught exception in getAcquisitionList", e);
@@ -193,6 +206,10 @@ public class AcquisitionResource
             DocumentHandler handler = createDocumentHandler(ctx);
             getRepositoryClient(ctx).update(ctx, csid, handler);
             result = (MultipartOutput) ctx.getOutput();
+        } catch (UnauthorizedException ue) {
+            Response response = Response.status(
+                    Response.Status.UNAUTHORIZED).entity("Update failed reason " + ue.getErrorReason()).type("text/plain").build();
+            throw new WebApplicationException(response);
         } catch (DocumentNotFoundException dnfe) {
             if (logger.isDebugEnabled()) {
                 logger.debug("caugth exception in updateAcquisition", dnfe);
@@ -227,6 +244,10 @@ public class AcquisitionResource
             ServiceContext ctx = MultipartServiceContextFactory.get().createServiceContext(null, getServiceName());
             getRepositoryClient(ctx).delete(ctx, csid);
             return Response.status(HttpResponseCodes.SC_OK).build();
+        } catch (UnauthorizedException ue) {
+            Response response = Response.status(
+                    Response.Status.UNAUTHORIZED).entity("Delete failed reason " + ue.getErrorReason()).type("text/plain").build();
+            throw new WebApplicationException(response);
         } catch (DocumentNotFoundException dnfe) {
             if (logger.isDebugEnabled()) {
                 logger.debug("caught exception in deleteAcquisition", dnfe);
index 5a88b75aea2bc3e06d207d16c2fe8cb745c47485..6d4ccff2ba62828614aca1f00c350a9a39884b1e 100644 (file)
 \r
     <target name="create_db"\r
     description="create tables(s), indices for authentication service">\r
-        <ant antfile="service/build.xml" target="create_db" inheritAll="false"/>\r
+        <ant antfile="client/build.xml" target="create_db" inheritAll="false"/>\r
     </target>\r
 \r
     <target name="deploy" depends="install"\r
     description="deploy authentication service">\r
+        <ant antfile="client/build.xml" target="deploy" inheritAll="false"/>\r
         <ant antfile="service/build.xml" target="deploy" inheritall="false"/>\r
     </target>\r
 \r
     <target name="undeploy"\r
     description="undeploy authentication service">\r
+        <ant antfile="client/build.xml" target="undeploy" inheritAll="false"/>\r
         <ant antfile="service/build.xml" target="undeploy" inheritall="false"/>\r
     </target>\r
 \r
diff --git a/services/authentication/client/build.xml b/services/authentication/client/build.xml
new file mode 100644 (file)
index 0000000..6736c39
--- /dev/null
@@ -0,0 +1,178 @@
+\r
+<project name="authentication.client" default="package" basedir=".">\r
+    <description>\r
+        collectionspace authentication service\r
+    </description>\r
+  <!-- set global properties for this build -->\r
+    <property name="services.trunk" value="../../.."/>\r
+    <property file="${services.trunk}/build.properties" />\r
+    <property name="mvn.opts" value="" />\r
+    <property name="src" location="src"/>\r
+\r
+    <condition property="osfamily-unix">\r
+        <os family="unix" />\r
+    </condition>\r
+    <condition property="osfamily-windows">\r
+        <os family="windows" />\r
+    </condition>\r
+\r
+    <target name="init">\r
+    <!-- Create the time stamp -->\r
+        <tstamp/>\r
+    </target>\r
+\r
+\r
+    <target name="package" depends="package-unix,package-windows"\r
+  description="Package CollectionSpace Services" />\r
+    <target name="package-unix" if="osfamily-unix">\r
+        <exec executable="mvn" failonerror="true">\r
+            <arg value="package" />\r
+            <arg value="-Dmaven.test.skip=true" />\r
+            <arg value="-f" />\r
+            <arg value="${basedir}/pom.xml" />\r
+            <arg value="-N" />\r
+            <arg value="${mvn.opts}" />\r
+        </exec>\r
+    </target>\r
+    <target name="package-windows" if="osfamily-windows">\r
+        <exec executable="cmd" failonerror="true">\r
+            <arg value="/c" />\r
+            <arg value="mvn.bat" />\r
+            <arg value="package" />\r
+            <arg value="-Dmaven.test.skip=true" />\r
+            <arg value="-f" />\r
+            <arg value="${basedir}/pom.xml" />\r
+            <arg value="-N" />\r
+            <arg value="${mvn.opts}" />\r
+        </exec>\r
+    </target>\r
+\r
+    <target name="install" depends="package,install-unix,install-windows"\r
+  description="Install" />\r
+    <target name="install-unix" if="osfamily-unix">\r
+        <exec executable="mvn" failonerror="true">\r
+            <arg value="install" />\r
+            <arg value="-Dmaven.test.skip=true" />\r
+            <arg value="-f" />\r
+            <arg value="${basedir}/pom.xml" />\r
+            <arg value="-N" />\r
+            <arg value="${mvn.opts}" />\r
+        </exec>\r
+    </target>\r
+    <target name="install-windows" if="osfamily-windows">\r
+        <exec executable="cmd" failonerror="true">\r
+            <arg value="/c" />\r
+            <arg value="mvn.bat" />\r
+            <arg value="install" />\r
+            <arg value="-Dmaven.test.skip=true" />\r
+            <arg value="-f" />\r
+            <arg value="${basedir}/pom.xml" />\r
+            <arg value="-N" />\r
+            <arg value="${mvn.opts}" />\r
+        </exec>\r
+    </target>\r
+    \r
+    <target name="clean" depends="clean-unix,clean-windows"\r
+  description="Delete target directories" >\r
+        <delete dir="${build}"/>\r
+    </target>\r
+    <target name="clean-unix" if="osfamily-unix">\r
+        <exec executable="mvn" failonerror="true">\r
+            <arg value="clean" />\r
+            <arg value="${mvn.opts}" />\r
+        </exec>\r
+    </target>\r
+    <target name="clean-windows" if="osfamily-windows">\r
+        <exec executable="cmd" failonerror="true">\r
+            <arg value="/c" />\r
+            <arg value="mvn.bat" />\r
+            <arg value="clean" />\r
+            <arg value="${mvn.opts}" />\r
+        </exec>\r
+    </target>\r
+\r
+    <target name="test" depends="test-unix,test-windows" description="Run tests" />\r
+    <target name="test-unix" if="osfamily-unix">\r
+        <exec executable="mvn" failonerror="true">\r
+            <arg value="test" />\r
+            <arg value="${mvn.opts}" />\r
+        </exec>\r
+    </target>\r
+    <target name="test-windows" if="osfamily-windows">\r
+        <exec executable="cmd" failonerror="true">\r
+            <arg value="/c" />\r
+            <arg value="mvn.bat" />\r
+            <arg value="test" />\r
+            <arg value="${mvn.opts}" />\r
+        </exec>\r
+    </target>\r
+\r
+    <target name="gen_ddl" depends="gen_ddl-unix,gen_ddl-windows"\r
+  description="geneate ddl" />\r
+    <target name="gen_ddl-unix" if="osfamily-unix">\r
+        <exec executable="mvn" failonerror="true">\r
+            <arg value="-Pddl" />\r
+            <arg value="process-test-resources" />\r
+            <arg value="-f" />\r
+            <arg value="${basedir}/pom.xml" />\r
+            <arg value="-N" />\r
+            <arg value="${mvn.opts}" />\r
+        </exec>\r
+    </target>\r
+    <target name="gen_ddl-windows" if="osfamily-windows">\r
+        <exec executable="cmd" failonerror="true">\r
+            <arg value="/c" />\r
+            <arg value="mvn.bat" />\r
+            <arg value="-Pddl" />\r
+            <arg value="process-test-resources" />\r
+            <arg value="-f" />\r
+            <arg value="${basedir}/pom.xml" />\r
+            <arg value="-N" />\r
+            <arg value="${mvn.opts}" />\r
+        </exec>\r
+    </target>\r
+\r
+    <target name="create_db"\r
+    description="create tables(s), indices for authentication service">\r
+        <sql driver="com.mysql.jdbc.Driver"\r
+        url="jdbc:mysql://${db.host}:${db.port}/cspace"\r
+        userid="${db.user}"\r
+        password="${db.user.password}"\r
+        src="${db.script.dir}/authentication.sql"\r
+        >\r
+            <classpath>\r
+                <pathelement path="${db.driver.jar}"/>\r
+            </classpath>\r
+        </sql>\r
+        <sql driver="com.mysql.jdbc.Driver"\r
+        url="jdbc:mysql://${db.host}:${db.port}/cspace"\r
+        userid="${db.user}"\r
+        password="${db.user.password}"\r
+        src="${db.script.dir}/test_authn.sql"\r
+        >\r
+            <classpath>\r
+                <pathelement path="${db.driver.jar}"/>\r
+            </classpath>\r
+        </sql>\r
+    </target>\r
+    \r
+    <target name="deploy" depends="install"\r
+    description="deploy authentication service in ${jboss.server.cspace}">\r
+        <copy todir="${jboss.server.cspace}/cspace/services">\r
+            <fileset dir="${src}/main/resources/"/>\r
+        </copy>\r
+    </target>\r
+\r
+    <target name="undeploy"\r
+    description="undeploy authentication service from ${jboss.server.cspace}">\r
+    </target>\r
+\r
+    <target name="dist"\r
+    description="generate distribution for authentication service" depends="package">\r
+        <!-- copy db scripts, etc. -->\r
+        <copy todir="${services.trunk}/${dist.server.cspace}/cspace/services">\r
+            <fileset dir="${src}/main/resources/"/>\r
+        </copy>\r
+    </target>\r
+\r
+</project>\r
index b3ff5a8859cfd2ee38930597e39ecdbfb8f8739f..d3fd746cd24acd7ff16481a487ffd9064432a5ad 100644 (file)
     <artifactId>org.collectionspace.services.authentication.client</artifactId>\r
     <version>1.0</version>\r
     <name>services.authentication.client</name>\r
-    \r
+    <properties>\r
+        <sql.file>authentication.sql</sql.file>\r
+        <sql.dir>${basedir}/src/main/resources/db/mysql</sql.dir>\r
+    </properties>\r
     <dependencies>\r
         <!-- keep slf4j dependencies on the top -->\r
         <dependency>\r
             <artifactId>slf4j-log4j12</artifactId>\r
             <scope>test</scope>\r
         </dependency>\r
+        <dependency>\r
+            <groupId>org.collectionspace.services</groupId>\r
+            <artifactId>org.collectionspace.services.authentication.jaxb</artifactId>\r
+            <version>1.0</version>\r
+        </dependency>\r
         <dependency>\r
             <groupId>org.collectionspace.services</groupId>\r
             <artifactId>org.collectionspace.services.client</artifactId>\r
             <artifactId>commons-httpclient</artifactId>\r
             <version>3.1</version>\r
         </dependency>\r
+        <dependency>\r
+            <groupId>mysql</groupId>\r
+            <artifactId>mysql-connector-java</artifactId>\r
+        </dependency>\r
     </dependencies>\r
     \r
     <build>\r
             </plugin>\r
         </plugins>\r
     </build>\r
+    <profiles>\r
+        <profile>\r
+            <!-- use profile as this task is not needed for every build and test -->\r
+            <id>ddl</id>\r
+            <build>\r
+                <plugins>\r
+                    <plugin>\r
+                        <groupId>org.codehaus.mojo</groupId>\r
+                        <artifactId>hibernate3-maven-plugin</artifactId>\r
+                        <version>2.2</version>\r
+                        <executions>\r
+                            <execution>\r
+                                <phase>process-test-resources</phase>\r
+                                <goals>\r
+                                    <goal>hbm2ddl</goal>\r
+                                </goals>\r
+                            </execution>\r
+                        </executions>\r
+                        <configuration>\r
+                            <components>\r
+                                <component>\r
+                                    <name>hbm2ddl</name>\r
+                                </component>\r
+                            </components>\r
+                            <componentProperties>\r
+                                <outputfilename>${sql.file}</outputfilename>\r
+                                <implementation>jpaconfiguration</implementation>\r
+                                <drop>true</drop>\r
+                                <create>true</create>\r
+                                <export>true</export>\r
+                                <jdk5>true</jdk5>\r
+                                <ejb3>false</ejb3>\r
+                                <persistenceunit>org.collectionspace.services.authentication</persistenceunit>\r
+                            </componentProperties>\r
+                        </configuration>\r
+                        <dependencies>\r
+                            <dependency>\r
+                                <groupId>mysql</groupId>\r
+                                <artifactId>mysql-connector-java</artifactId>\r
+                                <version>5.0.5</version>\r
+                            </dependency>\r
+                        </dependencies>\r
+                    </plugin>\r
+                    <plugin>\r
+                        <artifactId>maven-antrun-plugin</artifactId>\r
+                        <executions>\r
+                            <execution>\r
+                                <phase>process-test-resources</phase>\r
+                                <configuration>\r
+                                    <tasks>\r
+                                        <copy file="${basedir}/target/hibernate3/sql/${sql.file}" tofile="${sql.dir}/${sql.file}"/>\r
+                                    </tasks>\r
+                                </configuration>\r
+                                <goals>\r
+                                    <goal>run</goal>\r
+                                </goals>\r
+                            </execution>\r
+                        </executions>\r
+                    </plugin>\r
+\r
+                </plugins>\r
+            </build>\r
+        </profile>\r
+    </profiles>\r
 </project>\r
 \r
diff --git a/services/authentication/client/src/main/resources/db/mysql/authentication.sql b/services/authentication/client/src/main/resources/db/mysql/authentication.sql
new file mode 100644 (file)
index 0000000..863a09c
--- /dev/null
@@ -0,0 +1,6 @@
+drop table if exists roles;
+drop table if exists users;
+drop table if exists users_roles;
+create table roles (rolename varchar(255) not null, rolegroup varchar(255) not null, tenantid varchar(255) not null, primary key (rolename));
+create table users (username varchar(255) not null, passwd varchar(128) not null, tenantid varchar(255) not null, primary key (username));
+create table users_roles (HJID bigint not null auto_increment, rolename varchar(255) not null, tenantid varchar(255) not null, username varchar(255) not null, primary key (HJID));
diff --git a/services/authentication/client/src/main/resources/db/mysql/test_authn.sql b/services/authentication/client/src/main/resources/db/mysql/test_authn.sql
new file mode 100644 (file)
index 0000000..dde9556
--- /dev/null
@@ -0,0 +1,14 @@
+--\r
+-- Copyright 2009 University of California at Berkeley\r
+-- Licensed under the Educational Community License (ECL), Version 2.0.\r
+-- You may not use this file except in compliance with this License.\r
+--\r
+use cspace;\r
+\r
+INSERT INTO `users` (`username`,`passwd`, `tenantid`) VALUES ('test','n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=', '1');\r
+\r
+insert into roles (rolename, rolegroup, `tenantid`) values ('collections_manager', 'collections', '1');\r
+insert into roles (rolename, rolegroup, `tenantid`) values ('collections_registrar', 'collections', '1');\r
+\r
+insert into users_roles(username, rolename, `tenantid`) values ('test', 'collections_manager', '1');\r
+insert into users_roles(username, rolename, `tenantid`) values('admin', 'collections_registrar', '1');
\ No newline at end of file
diff --git a/services/authentication/client/src/test/resources/META-INF/persistence.xml b/services/authentication/client/src/test/resources/META-INF/persistence.xml
new file mode 100644 (file)
index 0000000..97d00a0
--- /dev/null
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<persistence version="1.0" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd
+http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_1_0.xsd" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:orm="http://java.sun.com/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+    <persistence-unit name="org.collectionspace.services.authentication">
+        <class>org.collectionspace.services.authentication.User</class>
+        <class>org.collectionspace.services.authentication.Role</class>
+        <class>org.collectionspace.services.authentication.UserRole</class>
+        <properties>
+            <property name="hibernate.ejb.cfgfile" value="hibernate.cfg.xml"/>
+
+            <!--property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
+            <property name="hibernate.max_fetch_depth" value="3"/>
+            <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>
+            <property name="hibernate.connection.username" value="test"/>
+            <property name="hibernate.connection.password" value="test"/>
+            <property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/cspace"/-->
+        </properties>
+    </persistence-unit>
+</persistence>
diff --git a/services/authentication/client/src/test/resources/hibernate.cfg.xml b/services/authentication/client/src/test/resources/hibernate.cfg.xml
new file mode 100644 (file)
index 0000000..9c2b94e
--- /dev/null
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+    Document   : hibernate.cfg.xml.xml
+    Created on : 
+    Author     : 
+    Description:
+        Hibernate configuration file for testing and tools
+-->
+<!DOCTYPE hibernate-configuration PUBLIC
+          "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
+          "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
+<hibernate-configuration>
+    <session-factory>
+        <property name="connection.url">jdbc:mysql://localhost:3306/cspace</property>
+        <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
+        <property name="connection.username">test</property>
+        <property name="connection.password">test</property>
+        <property name="dialect">org.hibernate.dialect.MySQLDialect</property>
+        <property name="transaction.factory_class">org.hibernate.transaction.JDBCTransactionFactory</property>
+        <property name="current_session_context_class">thread</property>
+        <property name="hibernate.show_sql">true</property>
+    </session-factory>
+</hibernate-configuration>
index a49a1e7553a80c33a44a6f2da70f4ed4fc769e73..e50ed0fa72e36ee3176c1c17f55e3942596f3e34 100644 (file)
                 </xs:appinfo>
             </xs:annotation>
             <xs:sequence>
+                <!-- tenant id is not needed from service consumer, it will be ignored if provided -->
+                <!-- tenant id is for internal use for CollectionSpace -->
+                <!-- it is assumed that account creation is performed under valid -->
+                <!-- tenant context. tenant id is never returned in response -->
+                <xs:element name="tenantid" type="xs:string" minOccurs="0" maxOccurs="1">
+                    <xs:annotation>
+                        <xs:appinfo>
+                            <hj:basic>
+                                <orm:column name="tenantid" nullable="false"/>
+                            </hj:basic>
+                        </xs:appinfo>
+                    </xs:annotation>
+                </xs:element>
                 <xs:element name="username" type="xs:string" minOccurs="1" maxOccurs="1">
                     <xs:annotation>
                         <xs:appinfo>
                             <hj:id>
-                                <orm:column name="username"  length="512" nullable="false"/>
+                                <orm:column name="username"  nullable="false"/>
                             </hj:id>
                         </xs:appinfo>
                     </xs:annotation>
                 </xs:appinfo>
             </xs:annotation>
             <xs:sequence>
+                 <!-- tenant id is not needed from service consumer, it will be ignored if provided -->
+                <!-- tenant id is for internal use for CollectionSpace -->
+                <!-- it is assumed that account creation is performed under valid -->
+                <!-- tenant context. tenant id is never returned in response -->
+                <xs:element name="tenantid" type="xs:string" minOccurs="0" maxOccurs="1">
+                    <xs:annotation>
+                        <xs:appinfo>
+                            <hj:basic>
+                                <orm:column name="tenantid" nullable="false"/>
+                            </hj:basic>
+                        </xs:appinfo>
+                    </xs:annotation>
+                </xs:element>
                 <xs:element name="rolename" type="xs:string" minOccurs="1" maxOccurs="1">
                     <xs:annotation>
                         <xs:appinfo>
                             <hj:id>
-                                <orm:column name="rolename"  length="128" nullable="false"/>
+                                <orm:column name="rolename" nullable="false"/>
                             </hj:id>
                         </xs:appinfo>
                     </xs:annotation>
                     <xs:annotation>
                         <xs:appinfo>
                             <hj:basic>
-                                <orm:column name="rolegroup" length="128" nullable="false"/>
+                                <orm:column name="rolegroup" nullable="false"/>
                             </hj:basic>
                         </xs:appinfo>
                     </xs:annotation>
                 </xs:appinfo>
             </xs:annotation>
             <xs:sequence>
+                <!-- tenant id is not needed from service consumer, it will be ignored if provided -->
+                <!-- tenant id is for internal use for CollectionSpace -->
+                <!-- it is assumed that account creation is performed under valid -->
+                <!-- tenant context. tenant id is never returned in response -->
+                <xs:element name="tenantid" type="xs:string" minOccurs="0" maxOccurs="1">
+                    <xs:annotation>
+                        <xs:appinfo>
+                            <hj:basic>
+                                <orm:column name="tenantid" nullable="false"/>
+                            </hj:basic>
+                        </xs:appinfo>
+                    </xs:annotation>
+                </xs:element>
                 <xs:element name="username" type="xs:string" minOccurs="1" maxOccurs="1">
                     <xs:annotation>
                         <xs:appinfo>
                             <hj:basic>
-                                <orm:column name="username" length="128" nullable="false"/>
+                                <orm:column name="username" nullable="false"/>
                             </hj:basic>
                         </xs:appinfo>
                     </xs:annotation>
                     <xs:annotation>
                         <xs:appinfo>
                             <hj:basic>
-                                <orm:column name="rolename" length="128" nullable="false"/>
+                                <orm:column name="rolename" nullable="false"/>
                             </hj:basic>
                         </xs:appinfo>
                     </xs:annotation>
index 5bbd99dd8cf29c7d3c5246c81cd994f75f89210b..a10f3150a86a80bbdc3b42ab64e061fb025220d7 100644 (file)
         </exec>\r
     </target>\r
 \r
-    <target name="create_db"\r
-    description="create tables(s), indices for authentication service">\r
-        <sql driver="com.mysql.jdbc.Driver"\r
-        url="jdbc:mysql://${db.host}:${db.port}/cspace"\r
-        userid="${db.user}"\r
-        password="${db.user.password}"\r
-        src="${db.script.dir}/authentication.sql"\r
-        >\r
-            <classpath>\r
-                <pathelement path="${db.driver.jar}"/>\r
-            </classpath>\r
-        </sql>\r
-        <sql driver="com.mysql.jdbc.Driver"\r
-        url="jdbc:mysql://${db.host}:${db.port}/cspace"\r
-        userid="${db.user}"\r
-        password="${db.user.password}"\r
-        src="${db.script.dir}/test_authn.sql"\r
-        >\r
-            <classpath>\r
-                <pathelement path="${db.driver.jar}"/>\r
-            </classpath>\r
-        </sql>\r
-    </target>\r
     <target name="deploy" depends="install"\r
     description="deploy authentication service in ${jboss.server.cspace}">\r
         <copy file="${basedir}/target/${authentication.jar}" todir="${jboss.server.cspace}/lib"/>\r
index 79b335f65fdd28b59665c91b651bc4223a967a87..d0574ba170bb4db2e3a155c57f616238e107db3a 100644 (file)
@@ -35,6 +35,8 @@
             <scope>test</scope>
         </dependency>
 
+
+
       <!-- javax -->
         <dependency>
             <groupId>javax.security</groupId>
             <version>1.0.01</version>
             <scope>provided</scope>
         </dependency>
+
+        <!-- jboss -->
+        <dependency>
+            <groupId>org.jboss.logging</groupId>
+            <artifactId>jboss-logging-log4j</artifactId>
+            <version>2.1.0.GA</version>
+        </dependency>
         <dependency>
             <groupId>jboss</groupId>
             <artifactId>jbosssx</artifactId>
index 1d2a7d3dd9155595ea14200f7d4d6f84cf1b040c..6ef9ba8270e7dcee3485fcd1990f3823bd85c4dd 100644 (file)
@@ -32,26 +32,28 @@ import java.sql.ResultSet;
 import java.sql.SQLException;\r
 \r
 import java.util.HashMap;\r
+import java.util.Map;\r
 import javax.naming.InitialContext;\r
 import javax.naming.NamingException;\r
+import javax.security.auth.Subject;\r
+import javax.security.auth.callback.CallbackHandler;\r
 import javax.security.auth.login.FailedLoginException;\r
 import javax.security.auth.login.LoginException;\r
 import javax.sql.DataSource;\r
 import org.jboss.security.SimpleGroup;\r
 import org.jboss.security.SimplePrincipal;\r
 import org.jboss.security.auth.spi.DatabaseServerLoginModule;\r
-//import org.slf4j.Logger;\r
-//import org.slf4j.LoggerFactory;\r
 \r
+/**\r
+ * CollectionSpace default identity provider supporting multi-tenancy\r
+ * @author\r
+ */\r
 public class CSpaceDBLoginModule extends DatabaseServerLoginModule {\r
 \r
+    protected String tenantQuery = "select tenantid from users where username=?";\r
     //disabled due to classloading problem\r
 //    private Logger logger = LoggerFactory.getLogger(CSpaceDBLoginModule.class);\r
-    private boolean log = true; //logger.isDebugEnabled();\r
-\r
-    private void log(String str) {\r
-        System.out.println(str);\r
-    }\r
+    private String tenantId;\r
 \r
     protected String getUsersPassword() throws LoginException {\r
 \r
@@ -60,38 +62,33 @@ public class CSpaceDBLoginModule extends DatabaseServerLoginModule {
         Connection conn = null;\r
         PreparedStatement ps = null;\r
         ResultSet rs = null;\r
-        InitialContext ctx = null;\r
         try {\r
-\r
-            ctx = new InitialContext();\r
-            DataSource ds = (DataSource) ctx.lookup(dsJndiName);\r
-            if (ds == null) {\r
-                throw new IllegalArgumentException("datasource not found: " + dsJndiName);\r
-            }\r
-            conn = ds.getConnection();\r
+            conn = getConnection();\r
             // Get the password\r
-            if (log) {\r
-                log("Executing query: " + principalsQuery + ", with username: " + username);\r
+            if (log.isDebugEnabled()) {\r
+                log.debug("Executing query: " + principalsQuery + ", with username: " + username);\r
             }\r
             ps = conn.prepareStatement(principalsQuery);\r
             ps.setString(1, username);\r
             rs = ps.executeQuery();\r
             if (rs.next() == false) {\r
-                if (log) {\r
-                    log("Query returned no matches from db");\r
+                if (log.isDebugEnabled()) {\r
+                    log.debug(principalsQuery + " returned no matches from db");\r
                 }\r
                 throw new FailedLoginException("No matching username found");\r
             }\r
 \r
             password = rs.getString(1);\r
             password = convertRawPassword(password);\r
-            if (log) {\r
-                log("Obtained user password");\r
+            if (log.isDebugEnabled()) {\r
+                log.debug("Obtained user password");\r
             }\r
-        } catch (NamingException ex) {\r
-            LoginException le = new LoginException("Error looking up DataSource from: " + dsJndiName);\r
-            le.initCause(ex);\r
-            throw le;\r
+            tenantId = rs.getString(2);\r
+            if (log.isDebugEnabled()) {\r
+                log.debug("Obtained tenantId");\r
+            }\r
+            CSpacePrincipal principal = (CSpacePrincipal)getIdentity();\r
+            principal.setTenantId(tenantId);\r
         } catch (SQLException ex) {\r
             LoginException le = new LoginException("Query failed");\r
             le.initCause(ex);\r
@@ -119,12 +116,6 @@ public class CSpaceDBLoginModule extends DatabaseServerLoginModule {
                 } catch (SQLException ex) {\r
                 }\r
             }\r
-            if (ctx != null) {\r
-                try {\r
-                    ctx.close();\r
-                } catch (Exception e) {\r
-                }\r
-            }\r
         }\r
         return password;\r
     }\r
@@ -136,8 +127,8 @@ public class CSpaceDBLoginModule extends DatabaseServerLoginModule {
      */\r
     protected Group[] getRoleSets() throws LoginException {\r
         String username = getUsername();\r
-        if (log) {\r
-            log("getRoleSets using rolesQuery: " + rolesQuery + ", username: " + username);\r
+        if (log.isDebugEnabled()) {\r
+            log.debug("getRoleSets using rolesQuery: " + rolesQuery + ", username: " + username);\r
         }\r
 \r
         Connection conn = null;\r
@@ -146,24 +137,23 @@ public class CSpaceDBLoginModule extends DatabaseServerLoginModule {
         ResultSet rs = null;\r
 \r
         try {\r
-            InitialContext ctx = new InitialContext();\r
-            DataSource ds = (DataSource) ctx.lookup(dsJndiName);\r
-            conn = ds.getConnection();\r
+            conn = getConnection();\r
             // Get the user role names\r
-            if (log) {\r
-                log("Executing query: " + rolesQuery + ", with username: " + username);\r
+            if (log.isDebugEnabled()) {\r
+                log.debug("Executing query: " + rolesQuery + ", with username: " + username);\r
             }\r
 \r
             ps = conn.prepareStatement(rolesQuery);\r
             try {\r
                 ps.setString(1, username);\r
+                ps.setString(2, tenantId);\r
             } catch (ArrayIndexOutOfBoundsException ignore) {\r
                 // The query may not have any parameters so just try it\r
             }\r
             rs = ps.executeQuery();\r
             if (rs.next() == false) {\r
-                if (log) {\r
-                    log("No roles found");\r
+                if (log.isDebugEnabled()) {\r
+                    log.debug("No roles found");\r
                 }\r
 //                if(aslm.getUnauthenticatedIdentity() == null){\r
 //                    throw new FailedLoginException("No matching username found in Roles");\r
@@ -192,20 +182,16 @@ public class CSpaceDBLoginModule extends DatabaseServerLoginModule {
                 try {\r
 //                    Principal p = aslm.createIdentity(name);\r
                     Principal p = createIdentity(name);\r
-                    if (log) {\r
-                        log("Assign user to role " + name);\r
+                    if (log.isDebugEnabled()) {\r
+                        log.debug("Assign user to role " + name);\r
                     }\r
 \r
                     group.addMember(p);\r
                 } catch (Exception e) {\r
-                    log("Failed to create principal: " + name + " " + e.toString());\r
+                    log.error("Failed to create principal: " + name + " " + e.toString());\r
                 }\r
 \r
             } while (rs.next());\r
-        } catch (NamingException ex) {\r
-            LoginException le = new LoginException("Error looking up DataSource from: " + dsJndiName);\r
-            le.initCause(ex);\r
-            throw le;\r
         } catch (SQLException ex) {\r
             LoginException le = new LoginException("Query failed");\r
             le.initCause(ex);\r
@@ -237,29 +223,30 @@ public class CSpaceDBLoginModule extends DatabaseServerLoginModule {
         return roleSets;\r
     }\r
 \r
-    /** Utility method to create a Principal for the given username. This\r
-     * creates an instance of the principalClassName type if this option was\r
-     * specified using the class constructor matching: ctor(String). If\r
-     * principalClassName was not specified, a SimplePrincipal is created.\r
-     *\r
-     * @param username the name of the principal\r
-     * @return the principal instance\r
-     * @throws java.lang.Exception thrown if the custom principal type cannot be created.\r
-     */\r
-    protected Principal createIdentity(String username)\r
-            throws Exception {\r
-        Principal p = null;\r
-        if (principalClassName == null) {\r
-            p = new SimplePrincipal(username);\r
-        } else {\r
-            ClassLoader loader = Thread.currentThread().getContextClassLoader();\r
-            Class clazz = loader.loadClass(principalClassName);\r
-            Class[] ctorSig = {String.class};\r
-            Constructor ctor = clazz.getConstructor(ctorSig);\r
-            Object[] ctorArgs = {username};\r
-            p = (Principal) ctor.newInstance(ctorArgs);\r
+\r
+    private Connection getConnection() throws LoginException, SQLException {\r
+        InitialContext ctx = null;\r
+        Connection conn = null;\r
+        try {\r
+            ctx = new InitialContext();\r
+            DataSource ds = (DataSource) ctx.lookup(dsJndiName);\r
+            if (ds == null) {\r
+                throw new IllegalArgumentException("datasource not found: " + dsJndiName);\r
+            }\r
+            conn = ds.getConnection();\r
+            return conn;\r
+        } catch (NamingException ex) {\r
+            LoginException le = new LoginException("Error looking up DataSource from: " + dsJndiName);\r
+            le.initCause(ex);\r
+            throw le;\r
+        } finally {\r
+            if (ctx != null) {\r
+                try {\r
+                    ctx.close();\r
+                } catch (Exception e) {\r
+                }\r
+            }\r
         }\r
 \r
-        return p;\r
     }\r
 }\r
diff --git a/services/authentication/service/src/main/java/org/collectionspace/authentication/CSpacePrincipal.java b/services/authentication/service/src/main/java/org/collectionspace/authentication/CSpacePrincipal.java
new file mode 100644 (file)
index 0000000..d992194
--- /dev/null
@@ -0,0 +1,80 @@
+/**
+ *  This document is a part of the source code and related artifacts
+ *  for CollectionSpace, an open source collections management system
+ *  for museums and related institutions:
+
+ *  http://www.collectionspace.org
+ *  http://wiki.collectionspace.org
+
+ *  Copyright 2009 University of California at Berkeley
+
+ *  Licensed under the Educational Community License (ECL), Version 2.0.
+ *  You may not use this file except in compliance with this License.
+
+ *  You may obtain a copy of the ECL 2.0 License at
+
+ *  https://source.collectionspace.org/collection-space/LICENSE.txt
+
+ *  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.collectionspace.authentication;
+
+import java.security.Principal;
+
+/**
+ * CSpacePrincipal provides additional tenant-specific context to application
+ * @author 
+ */
+public class CSpacePrincipal
+        implements Principal, java.io.Serializable {
+
+    private String name;
+    private String tenantId;
+
+    public CSpacePrincipal(String name) {
+        this.name = name;
+    }
+
+    public int hashCode() {
+        return name.hashCode();
+    }
+
+    public boolean equals(Object obj) {
+        Principal p = (Principal) obj;
+        return name.equals(p.getName());
+    }
+
+    public String toString() {
+        return name;
+    }
+
+    /**
+     * Returns the name of this principal.
+     *
+     * @return the name of this principal.
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Returns the id of the tenant this principal is associated with
+     * @return
+     */
+    public String getTenantId() {
+        return tenantId;
+    }
+
+    /**
+     * Set the id for the tenant to which princiapal is associated with
+     * The access to this method must be package private
+     * @param tenantId
+     */
+    void setTenantId(String tenantId) {
+        this.tenantId = tenantId;
+    }
+}
index 4efd480783f046bfcb8352f0419187ad040b03a4..9d4221a702ceb1539a7c194a55b82930008f5fe7 100644 (file)
@@ -16,19 +16,20 @@ copy the following snippet into $JBOSS_HOME/server/cspace/conf/login-config.xml
 copy before the "other" application-policy
 -->
 
-    <application-policy name="cspace">
-        <authentication>
-            <login-module code="org.collectionspace.authentication.CSpaceDBLoginModule"
+<application-policy name="cspace">
+    <authentication>
+        <login-module code="org.collectionspace.authentication.CSpaceDBLoginModule"
                              flag="required">
-                <module-option name="dsJndiName">CspaceDS</module-option>
-                <module-option name="hashAlgorithm">SHA-256</module-option>
-                <module-option name="ignorePasswordCase">false</module-option>
-                <module-option name="principalsQuery">
-                    select passwd from users where username=?
-                </module-option>
-                <module-option name="rolesQuery">
-                    select rolename, 'Roles' from users_roles where username=?
-                </module-option>
-            </login-module>
-        </authentication>
-    </application-policy>
+            <module-option name="dsJndiName">CspaceDS</module-option>
+            <module-option name="hashAlgorithm">SHA-256</module-option>
+            <module-option name="ignorePasswordCase">false</module-option>
+            <module-option name = "principalClass">org.collectionspace.authentication.CSpacePrincipal</module-option>
+            <module-option name="principalsQuery">
+                    select passwd, tenantid from users where username=?
+            </module-option>
+            <module-option name="rolesQuery">
+                    select rolename, 'Roles' from users_roles where username=? and tenantid=?
+            </module-option>
+        </login-module>
+    </authentication>
+</application-policy>
diff --git a/services/authentication/service/src/main/resources/db/mysql/authentication.sql b/services/authentication/service/src/main/resources/db/mysql/authentication.sql
deleted file mode 100644 (file)
index aa85386..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
---\r
--- Copyright 2009 University of California at Berkeley\r
--- Licensed under the Educational Community License (ECL), Version 2.0.\r
--- You may not use this file except in compliance with this License.\r
---\r
-use cspace;\r
-\r
-DROP TABLE IF EXISTS users;\r
-CREATE TABLE users(username VARCHAR(512) PRIMARY KEY, passwd VARCHAR(128) NOT NULL );\r
-CREATE INDEX username_users on users(username);\r
-\r
-DROP TABLE IF EXISTS roles;\r
-CREATE TABLE roles(rolename VARCHAR(128) PRIMARY KEY, rolegroup VARCHAR(128));\r
-CREATE INDEX rolename_roles on roles(rolename);\r
-\r
-DROP TABLE IF EXISTS users_roles;\r
-CREATE TABLE users_roles(username VARCHAR(512) NOT NULL, rolename VARCHAR(128) NOT NULL);\r
-CREATE INDEX username_users_roles on users_roles(username);
\ No newline at end of file
diff --git a/services/authentication/service/src/main/resources/db/mysql/test_authn.sql b/services/authentication/service/src/main/resources/db/mysql/test_authn.sql
deleted file mode 100644 (file)
index ccdf609..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
---\r
--- Copyright 2009 University of California at Berkeley\r
--- Licensed under the Educational Community License (ECL), Version 2.0.\r
--- You may not use this file except in compliance with this License.\r
---\r
-use cspace;\r
-\r
-INSERT INTO `users` (`username`,`passwd`) VALUES ('test','n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=');\r
-\r
-insert into roles (rolename, rolegroup) values ('collections_manager', 'collections');\r
-insert into roles (rolename, rolegroup) values ('collections_registrar', 'collections');\r
-\r
-insert into users_roles(username, rolename) values ('test', 'collections_manager');\r
-insert into users_roles(username, rolename) values('admin', 'collections_registrar');
\ No newline at end of file
index efc67f77d2fe55cddae8eeedc5a029371db60779..b1fd2c79616649c86b4d201319b127f16d54f802 100644 (file)
@@ -45,6 +45,7 @@ import org.collectionspace.services.common.context.MultipartServiceContextFactor
 import org.collectionspace.services.common.context.ServiceContext;
 import org.collectionspace.services.common.document.DocumentNotFoundException;
 import org.collectionspace.services.common.document.DocumentHandler;
+import org.collectionspace.services.common.security.UnauthorizedException;
 import org.jboss.resteasy.plugins.providers.multipart.MultipartInput;
 import org.jboss.resteasy.plugins.providers.multipart.MultipartOutput;
 import org.jboss.resteasy.util.HttpResponseCodes;
@@ -89,6 +90,10 @@ public class CollectionObjectResource
             path.path("" + csid);
             Response response = Response.created(path.build()).build();
             return response;
+        } catch (UnauthorizedException ue) {
+            Response response = Response.status(
+                    Response.Status.UNAUTHORIZED).entity("Create failed reason " + ue.getErrorReason()).type("text/plain").build();
+            throw new WebApplicationException(response);
         } catch (Exception e) {
             if (logger.isDebugEnabled()) {
                 logger.debug("Caught exception in createCollectionObject", e);
@@ -119,6 +124,10 @@ public class CollectionObjectResource
             DocumentHandler handler = createDocumentHandler(ctx);
             getRepositoryClient(ctx).get(ctx, csid, handler);
             result = (MultipartOutput) ctx.getOutput();
+        } catch (UnauthorizedException ue) {
+            Response response = Response.status(
+                    Response.Status.UNAUTHORIZED).entity("Get failed reason " + ue.getErrorReason()).type("text/plain").build();
+            throw new WebApplicationException(response);
         } catch (DocumentNotFoundException dnfe) {
             if (logger.isDebugEnabled()) {
                 logger.debug("getCollectionObject", dnfe);
@@ -154,6 +163,10 @@ public class CollectionObjectResource
             DocumentHandler handler = createDocumentHandler(ctx);
             getRepositoryClient(ctx).getAll(ctx, handler);
             collectionObjectList = (CollectionobjectsCommonList) handler.getCommonPartList();
+        } catch (UnauthorizedException ue) {
+            Response response = Response.status(
+                    Response.Status.UNAUTHORIZED).entity("Index failed reason " + ue.getErrorReason()).type("text/plain").build();
+            throw new WebApplicationException(response);
         } catch (Exception e) {
             if (logger.isDebugEnabled()) {
                 logger.debug("Caught exception in getCollectionObjectList", e);
@@ -186,6 +199,10 @@ public class CollectionObjectResource
             DocumentHandler handler = createDocumentHandler(ctx);
             getRepositoryClient(ctx).update(ctx, csid, handler);
             result = (MultipartOutput) ctx.getOutput();
+        } catch (UnauthorizedException ue) {
+            Response response = Response.status(
+                    Response.Status.UNAUTHORIZED).entity("Update failed reason " + ue.getErrorReason()).type("text/plain").build();
+            throw new WebApplicationException(response);
         } catch (DocumentNotFoundException dnfe) {
             if (logger.isDebugEnabled()) {
                 logger.debug("caugth exception in updateCollectionObject", dnfe);
@@ -220,6 +237,10 @@ public class CollectionObjectResource
             ServiceContext ctx = MultipartServiceContextFactory.get().createServiceContext(null, getServiceName());
             getRepositoryClient(ctx).delete(ctx, csid);
             return Response.status(HttpResponseCodes.SC_OK).build();
+        } catch (UnauthorizedException ue) {
+            Response response = Response.status(
+                    Response.Status.UNAUTHORIZED).entity("Delete failed reason " + ue.getErrorReason()).type("text/plain").build();
+            throw new WebApplicationException(response);
         } catch (DocumentNotFoundException dnfe) {
             if (logger.isDebugEnabled()) {
                 logger.debug("caught exception in deleteCollectionObject", dnfe);
index 2b6c1657ce79045bc90e2999000dd1832f91f750..e9b0f3eeedb53f9d212e93c0643bd9294c6206d8 100644 (file)
             <version>1.0.01</version>\r
             <scope>provided</scope>\r
         </dependency>\r
-\r
+        <dependency>\r
+            <groupId>javax.security</groupId>\r
+            <artifactId>jacc</artifactId>\r
+            <version>1.0</version>\r
+             <scope>provided</scope>\r
+        </dependency>\r
         <!--\r
             <dependency>\r
             <groupId>dom4j</groupId>\r
diff --git a/services/common/src/main/java/org/collectionspace/services/common/ServiceException.java b/services/common/src/main/java/org/collectionspace/services/common/ServiceException.java
new file mode 100644 (file)
index 0000000..dca50ac
--- /dev/null
@@ -0,0 +1,136 @@
+/**
+ *  This document is a part of the source code and related artifacts
+ *  for CollectionSpace, an open source collections management system
+ *  for museums and related institutions:
+
+ *  http://www.collectionspace.org
+ *  http://wiki.collectionspace.org
+
+ *  Copyright 2009 University of California at Berkeley
+
+ *  Licensed under the Educational Community License (ECL), Version 2.0.
+ *  You may not use this file except in compliance with this License.
+
+ *  You may obtain a copy of the ECL 2.0 License at
+
+ *  https://source.collectionspace.org/collection-space/LICENSE.txt
+
+ *  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.collectionspace.services.common;
+
+/**
+ * ServiceException is a base exception for any exception thrown by the service layer
+ * The error code is the code that is sent to the service consumer with relevant
+ * error reason. Note that detail message could be server side service-centric message.
+ * This message should not be sent to the service consumer who does not need to know
+ * about the internals of the service. The service consumer should see error code
+ * and error reason only. CollectionSpace service documentation should be checked
+ * to get help on troubleshooting the error.
+ * @author 
+ */
+public class ServiceException extends Exception {
+
+    private int errorCode;
+    private String errorReason;
+
+    /**
+     * Creates a new instance of <code>ServiceException</code> without detail message, error code
+     * or error reason.
+     */
+    public ServiceException() {
+    }
+
+    /**
+     * Constructs an instance of <code>ServiceException</code> with the specified detail message
+     * without error code or error reason.
+     * @param msg the detail message.
+     */
+    public ServiceException(String msg) {
+        super(msg);
+    }
+
+        /**
+     * Constructs an instance of <code>ServiceException</code> with the specified error code.
+     * @param errorCode error code
+     */
+    public ServiceException(int errorCode) {
+        this.errorCode = errorCode;
+    }
+
+    /**
+     * Constructs an instance of <code>ServiceException</code> with the specified error code and error reason.
+     * @param errorCode error code
+     * @param errorReason reason for error
+     */
+    public ServiceException(int errorCode, String errorReason) {
+        this.errorCode = errorCode;
+        this.errorReason = errorReason;
+    }
+
+    /**
+     * Constructs a new an instance of <code>ServiceException</code> with the specified detail message and
+     * cause.  <p>Note that the detail message associated with
+     * <code>cause</code> is <i>not</i> automatically incorporated in
+     * this exception's detail message.
+     *
+     * @param  msg the detail message (which is saved for later retrieval
+     *         by the {@link #getMessage()} method).
+     * @param  cause the cause (which is saved for later retrieval by the
+     *         {@link #getCause()} method).  (A <tt>null</tt> value is
+     *         permitted, and indicates that the cause is nonexistent or
+     *         unknown.)
+     */
+    public ServiceException(String msg, Throwable cause) {
+        super(msg, cause);
+    }
+
+    /**
+     * Constructs a new an instance of <code>ServiceException</code> with the specified cause and a detail
+     * message of <tt>(cause==null ? null : cause.toString())</tt> (which
+     * typically contains the class and detail message of <tt>cause</tt>).
+     * This constructor is useful for exceptions that are little more than
+     * wrappers for other throwables (for example, {@link
+     * java.security.PrivilegedActionException}).
+     *
+     * @param  cause the cause (which is saved for later retrieval by the
+     *         {@link #getCause()} method).  (A <tt>null</tt> value is
+     *         permitted, and indicates that the cause is nonexistent or
+     *         unknown.)
+     */
+    public ServiceException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * @return the errorCode
+     */
+    public int getErrorCode() {
+        return errorCode;
+    }
+
+    /**
+     * @param errorCode the errorCode to set
+     */
+    public void setErrorCode(int errorCode) {
+        this.errorCode = errorCode;
+    }
+
+    /**
+     * @return the errorReason
+     */
+    public String getErrorReason() {
+        return errorReason;
+    }
+
+    /**
+     * @param errorReason the ErrorReason to set
+     */
+    public void setErrorReason(String errorReason) {
+        this.errorReason = errorReason;
+    }
+}
index 47885dc9c1f3cf4afe75fc6e53ad3e1b25579a93..d6d0a4b779de4a59c6f81a82a21f193a41a5ce1f 100644 (file)
  */
 package org.collectionspace.services.common.context;
 
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.security.Principal;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import javax.security.auth.Subject;
+import javax.security.jacc.PolicyContext;
+import javax.security.jacc.PolicyContextException;
+
 import org.collectionspace.services.common.ClientType;
 import org.collectionspace.services.common.ServiceMain;
 import org.collectionspace.services.common.config.TenantBindingConfigReader;
+import org.collectionspace.services.common.security.UnauthorizedException;
 import org.collectionspace.services.common.service.ObjectPartType;
 import org.collectionspace.services.common.service.ServiceBindingType;
 import org.collectionspace.services.common.service.ServiceObjectType;
@@ -52,11 +61,15 @@ public abstract class AbstractServiceContext<IT, OT>
     private TenantBindingType tenantBinding;
     private String overrideDocumentType = null;
 
-    public AbstractServiceContext(String serviceName) {
+    public AbstractServiceContext(String serviceName) throws UnauthorizedException {
         TenantBindingConfigReader tReader =
                 ServiceMain.getInstance().getTenantBindingConfigReader();
-        //TODO: get tenant binding from security context (Subject.g
-        String tenantId = "1"; //hardcoded for movingimages.us
+        //TODO: get tenant binding from security context
+        String tenantId = retrieveTenantId();
+        if (tenantId == null) {
+            //for testing purposes
+            tenantId = "1"; //hardcoded for movingimages.us
+        }
         tenantBinding = tReader.getTenantBinding(tenantId);
         if (tenantBinding == null) {
             String msg = "No tenant binding found while processing request for " +
@@ -205,6 +218,45 @@ public abstract class AbstractServiceContext<IT, OT>
     public void setProperty(String name, Object o) {
         properties.put(name, o);
     }
+    private static final String SUBJECT_CONTEXT_KEY = "javax.security.auth.Subject.container";
+
+    private String retrieveTenantId() throws UnauthorizedException {
+
+        String tenantId = null;
+        Set<Principal> principals = null;
+        try {
+            Subject caller = (Subject) PolicyContext.getContext(SUBJECT_CONTEXT_KEY);
+            if(caller == null) {
+                //logger.warn("security not enabled...");
+                return tenantId;
+            }
+            principals = caller.getPrincipals(Principal.class);
+        } catch (PolicyContextException pce) {
+            String msg = "Could not retrieve principal information";
+            logger.error(msg, pce);
+            throw new UnauthorizedException(msg);
+        }
+        for (Principal p : principals) {
+            try {
+                Method m = p.getClass().getMethod("getTenantId");
+                Object r = m.invoke(p);
+                if (logger.isDebugEnabled()) {
+                    logger.debug("retrieved tenantid=" + r +
+                            " for principal=" + p.getName());
+                }
+                tenantId = (String) r;
+                break;
+            } catch (Exception e) {
+                //continue with next principal
+            } 
+        }
+        if(tenantId == null) {
+            String msg = "Could not find tenant context";
+            logger.error(msg);
+            throw new UnauthorizedException(msg);
+        }
+        return tenantId;
+    }
 
     @Override
     public String toString() {
@@ -216,7 +268,7 @@ public abstract class AbstractServiceContext<IT, OT>
         msg.append("tenant name=" + tenantBinding.getName() + " ");
         msg.append(tenantBinding.getDisplayName() + " ");
         msg.append("tenant repository domain=" + tenantBinding.getRepositoryDomain());
-        for(Map.Entry<String, Object> entry : properties.entrySet()) {
+        for (Map.Entry<String, Object> entry : properties.entrySet()) {
             msg.append("property name=" + entry.getKey() + " value=" + entry.getValue().toString());
         }
         msg.append("]");
index a204f06cd3add25dc4ba443fd0bfa5ce17c9995c..5e99ab85033123d2010351a021149a35239264d6 100644 (file)
@@ -28,6 +28,7 @@ import java.io.IOException;
 import java.lang.reflect.Constructor;
 import javax.ws.rs.core.MediaType;
 import org.collectionspace.services.common.document.DocumentUtils;
+import org.collectionspace.services.common.security.UnauthorizedException;
 import org.jboss.resteasy.plugins.providers.multipart.InputPart;
 import org.jboss.resteasy.plugins.providers.multipart.MultipartInput;
 import org.jboss.resteasy.plugins.providers.multipart.MultipartOutput;
@@ -37,7 +38,7 @@ import org.slf4j.LoggerFactory;
 import org.w3c.dom.Document;
 
 /**
- * RemoteServiceContextImpl
+ * MultipartServiceContextImpl takes Multipart Input/Output
  *
  * $LastChangedRevision: $
  * $LastChangedDate: $
@@ -48,7 +49,7 @@ public class MultipartServiceContextImpl
 
     final Logger logger = LoggerFactory.getLogger(MultipartServiceContextImpl.class);
 
-    public MultipartServiceContextImpl(String serviceName) {
+    public MultipartServiceContextImpl(String serviceName) throws UnauthorizedException {
         super(serviceName);
         setOutput(new MultipartOutput());
     }
index a5bdb2f4eee5c39c2d0f17dc62505c211ae50ccd..d5321864445168e377103da9770779b36da76b73 100644 (file)
@@ -24,6 +24,7 @@
 package org.collectionspace.services.common.context;
 
 import java.lang.reflect.Constructor;
+import org.collectionspace.services.common.security.UnauthorizedException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -42,7 +43,7 @@ public class RemoteServiceContextImpl<IT, OT>
     private IT input;
     private OT output;
 
-    public RemoteServiceContextImpl(String serviceName) {
+    public RemoteServiceContextImpl(String serviceName) throws UnauthorizedException {
         super(serviceName);
     }
 
index 11feb26051d1cebea258ea90e47e038464a53ca8..79cab7d2fcadf6c0c5d518a048d0f5c82aba8cb5 100644 (file)
  *  See the License for the specific language governing permissions and
  *  limitations under the License.
  */
-
 package org.collectionspace.services.common.document;
 
+import org.collectionspace.services.common.ServiceException;
+
 /**
  * BadRequestException
  * 
  */
-public class BadRequestException extends Exception {
+public class BadRequestException extends ServiceException {
+
+    final public static int HTTP_CODE = 400;
 
     /**
      * Creates a new instance of <code>BadRequestException</code> without detail message.
      */
     public BadRequestException() {
+        super(HTTP_CODE);
     }
 
-
     /**
      * Constructs an instance of <code>BadRequestException</code> with the specified detail message.
      * @param msg the detail message.
      */
     public BadRequestException(String msg) {
         super(msg);
+        setErrorCode(HTTP_CODE);
     }
 
-
     /**
      * Constructs a new exception with the specified detail message and
      * cause.  <p>Note that the detail message associated with
@@ -62,6 +65,7 @@ public class BadRequestException extends Exception {
      */
     public BadRequestException(String message, Throwable cause) {
         super(message, cause);
+        setErrorCode(HTTP_CODE);
     }
 
     /**
@@ -80,5 +84,6 @@ public class BadRequestException extends Exception {
      */
     public BadRequestException(Throwable cause) {
         super(cause);
+        setErrorCode(HTTP_CODE);
     }
 }
index efead821209bd128aec87e07f3dff960808762fd..de727116343d4beda2cda560aa4e1cb959ba9361 100644 (file)
 
 package org.collectionspace.services.common.document;
 
+import org.collectionspace.services.common.ServiceException;
+
 /**
  * DocumentException
  * document handling exception
  */
-public class DocumentException extends Exception {
+public class DocumentException extends ServiceException {
 
-    /**
-     * collectionspace specific error code
-     */
-    private int errorCode;
 
     /**
      * Creates a new instance of <code>DocumentException</code> without detail message.
@@ -43,15 +41,20 @@ public class DocumentException extends Exception {
         super(msg);
     }
 
-
     /**
-     * DocumentException with application specific code and message
-     * @param code
-     * @param msg
+     * DocumentException with application specific code
+     * @param errorCode
      */
-    public DocumentException(int code, String msg) {
-        super(msg);
-        this.errorCode = code;
+    public DocumentException(int errorCode) {
+        super(errorCode);
+    }
+    /**
+     * DocumentException with application specific code and reason
+     * @param errorCode
+     * @param errorReason
+     */
+    public DocumentException(int errorCode, String errorReason) {
+        super(errorCode, errorReason);
     }
     /**
      * Constructs a new exception with the specified detail message and
@@ -89,18 +92,4 @@ public class DocumentException extends Exception {
         super(cause);
     }
 
-
-    /**
-     * @return the collectionspace errorCode
-     */
-    public int getErrorCode() {
-        return errorCode;
-    }
-
-    /**
-     * @param errorCode the errorCode to set
-     */
-    public void setErrorCode(int errorCode) {
-        this.errorCode = errorCode;
-    }
 }
index 403ffcdf5d4c8559170920579990c9f8749f390a..af15a9ad2913f082b11e831cede06823def46ffc 100644 (file)
@@ -15,7 +15,6 @@
 
  *  https://source.collectionspace.org/collection-space/LICENSE.txt
  */
-
 package org.collectionspace.services.common.document;
 
 /**
@@ -24,22 +23,24 @@ package org.collectionspace.services.common.document;
  */
 public class DocumentNotFoundException extends DocumentException {
 
+    final public static int HTTP_CODE = 404;
+
     /**
      * Creates a new instance of <code>DocumentNotFoundException</code> without detail message.
      */
     public DocumentNotFoundException() {
+        setErrorCode(HTTP_CODE);
     }
 
-
     /**
      * Constructs an instance of <code>DocumentNotFoundException</code> with the specified detail message.
      * @param msg the detail message.
      */
     public DocumentNotFoundException(String msg) {
         super(msg);
+        setErrorCode(HTTP_CODE);
     }
 
-
     /**
      * Constructs a new exception with the specified detail message and
      * cause.  <p>Note that the detail message associated with
@@ -56,6 +57,7 @@ public class DocumentNotFoundException extends DocumentException {
      */
     public DocumentNotFoundException(String message, Throwable cause) {
         super(message, cause);
+        setErrorCode(HTTP_CODE);
     }
 
     /**
@@ -74,5 +76,6 @@ public class DocumentNotFoundException extends DocumentException {
      */
     public DocumentNotFoundException(Throwable cause) {
         super(cause);
+        setErrorCode(HTTP_CODE);
     }
 }
diff --git a/services/common/src/main/java/org/collectionspace/services/common/security/ServiceForbiddenException.java b/services/common/src/main/java/org/collectionspace/services/common/security/ServiceForbiddenException.java
new file mode 100644 (file)
index 0000000..d331453
--- /dev/null
@@ -0,0 +1,92 @@
+/**
+ *  This document is a part of the source code and related artifacts
+ *  for CollectionSpace, an open source collections management system
+ *  for museums and related institutions:
+
+ *  http://www.collectionspace.org
+ *  http://wiki.collectionspace.org
+
+ *  Copyright 2009 University of California at Berkeley
+
+ *  Licensed under the Educational Community License (ECL), Version 2.0.
+ *  You may not use this file except in compliance with this License.
+
+ *  You may obtain a copy of the ECL 2.0 License at
+
+ *  https://source.collectionspace.org/collection-space/LICENSE.txt
+
+ *  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.collectionspace.services.common.security;
+
+import org.collectionspace.services.common.ServiceException;
+
+/**
+ * ServiceForbidenException is thrown when access to service is not allowed for
+ * one or more of the following reasons:
+ * - access not allowed
+ * - no application key found
+ * @author 
+ */
+public class ServiceForbiddenException extends ServiceException {
+
+    final public static int HTTP_CODE = 401;
+
+    /**
+     * Creates a new instance of <code>UnauthorizedException</code> without detail message.
+     */
+    public ServiceForbiddenException() {
+        super(HTTP_CODE);
+    }
+
+    /**
+     * Constructs an instance of <code>UnauthorizedException</code> with the specified detail message.
+     * @param msg the detail message.
+     */
+    public ServiceForbiddenException(String msg) {
+        super(msg);
+        setErrorCode(HTTP_CODE);
+    }
+
+    /**
+     * Constructs a new exception with the specified detail message and
+     * cause.  <p>Note that the detail message associated with
+     * <code>cause</code> is <i>not</i> automatically incorporated in
+     * this exception's detail message.
+     *
+     * @param  message the detail message (which is saved for later retrieval
+     *         by the {@link #getMessage()} method).
+     * @param  cause the cause (which is saved for later retrieval by the
+     *         {@link #getCause()} method).  (A <tt>null</tt> value is
+     *         permitted, and indicates that the cause is nonexistent or
+     *         unknown.)
+     * @since  1.4
+     */
+    public ServiceForbiddenException(String message, Throwable cause) {
+        super(message, cause);
+        setErrorCode(HTTP_CODE);
+    }
+
+    /**
+     * Constructs a new exception with the specified cause and a detail
+     * message of <tt>(cause==null ? null : cause.toString())</tt> (which
+     * typically contains the class and detail message of <tt>cause</tt>).
+     * This constructor is useful for exceptions that are little more than
+     * wrappers for other throwables (for example, {@link
+     * java.security.PrivilegedActionException}).
+     *
+     * @param  cause the cause (which is saved for later retrieval by the
+     *         {@link #getCause()} method).  (A <tt>null</tt> value is
+     *         permitted, and indicates that the cause is nonexistent or
+     *         unknown.)
+     * @since  1.4
+     */
+    public ServiceForbiddenException(Throwable cause) {
+        super(cause);
+        setErrorCode(HTTP_CODE);
+    }
+}
diff --git a/services/common/src/main/java/org/collectionspace/services/common/security/UnauthorizedException.java b/services/common/src/main/java/org/collectionspace/services/common/security/UnauthorizedException.java
new file mode 100644 (file)
index 0000000..7c99fa3
--- /dev/null
@@ -0,0 +1,91 @@
+/**
+ *  This document is a part of the source code and related artifacts
+ *  for CollectionSpace, an open source collections management system
+ *  for museums and related institutions:
+
+ *  http://www.collectionspace.org
+ *  http://wiki.collectionspace.org
+
+ *  Copyright 2009 University of California at Berkeley
+
+ *  Licensed under the Educational Community License (ECL), Version 2.0.
+ *  You may not use this file except in compliance with this License.
+
+ *  You may obtain a copy of the ECL 2.0 License at
+
+ *  https://source.collectionspace.org/collection-space/LICENSE.txt
+
+ *  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.collectionspace.services.common.security;
+
+import org.collectionspace.services.common.ServiceException;
+
+/**
+ * ServiceForbidenException is thrown when access to service is not allowed for
+ * one or more of the following reasons:
+ * - tenant id not found
+ * @author 
+ */
+public class UnauthorizedException extends ServiceException {
+
+    final public static int HTTP_CODE = 401;
+
+    /**
+     * Creates a new instance of <code>UnauthorizedException</code> without detail message.
+     */
+    public UnauthorizedException() {
+        super(HTTP_CODE);
+    }
+
+    /**
+     * Constructs an instance of <code>UnauthorizedException</code> with the specified detail message.
+     * @param msg the detail message.
+     */
+    public UnauthorizedException(String msg) {
+        super(msg);
+        setErrorCode(HTTP_CODE);
+    }
+
+    /**
+     * Constructs a new exception with the specified detail message and
+     * cause.  <p>Note that the detail message associated with
+     * <code>cause</code> is <i>not</i> automatically incorporated in
+     * this exception's detail message.
+     *
+     * @param  message the detail message (which is saved for later retrieval
+     *         by the {@link #getMessage()} method).
+     * @param  cause the cause (which is saved for later retrieval by the
+     *         {@link #getCause()} method).  (A <tt>null</tt> value is
+     *         permitted, and indicates that the cause is nonexistent or
+     *         unknown.)
+     * @since  1.4
+     */
+    public UnauthorizedException(String message, Throwable cause) {
+        super(message, cause);
+        setErrorCode(HTTP_CODE);
+    }
+
+    /**
+     * Constructs a new exception with the specified cause and a detail
+     * message of <tt>(cause==null ? null : cause.toString())</tt> (which
+     * typically contains the class and detail message of <tt>cause</tt>).
+     * This constructor is useful for exceptions that are little more than
+     * wrappers for other throwables (for example, {@link
+     * java.security.PrivilegedActionException}).
+     *
+     * @param  cause the cause (which is saved for later retrieval by the
+     *         {@link #getCause()} method).  (A <tt>null</tt> value is
+     *         permitted, and indicates that the cause is nonexistent or
+     *         unknown.)
+     * @since  1.4
+     */
+    public UnauthorizedException(Throwable cause) {
+        super(cause);
+        setErrorCode(HTTP_CODE);
+    }
+}
index e1a965c410ab167f795638482c27c1f38cfa5305..f05003f0c22265f8e88d9d4a1373cfbf3ae4181d 100644 (file)
@@ -48,6 +48,7 @@ import org.collectionspace.services.common.context.MultipartServiceContextFactor
 import org.collectionspace.services.common.context.ServiceContext;
 import org.collectionspace.services.common.document.DocumentNotFoundException;
 import org.collectionspace.services.common.document.DocumentHandler;
+import org.collectionspace.services.common.security.UnauthorizedException;
 import org.jboss.resteasy.plugins.providers.multipart.MultipartInput;
 import org.jboss.resteasy.plugins.providers.multipart.MultipartOutput;
 import org.jboss.resteasy.util.HttpResponseCodes;
@@ -79,7 +80,7 @@ public class IntakeResource extends AbstractCollectionSpaceResource {
                 ctx.getRepositoryClientType().toString());
         docHandler.setServiceContext(ctx);
         if (ctx.getInput() != null) {
-            Object obj = ((MultipartServiceContext)ctx).getInputPart(ctx.getCommonPartLabel(), IntakesCommon.class);
+            Object obj = ((MultipartServiceContext) ctx).getInputPart(ctx.getCommonPartLabel(), IntakesCommon.class);
             if (obj != null) {
                 docHandler.setCommonPart((IntakesCommon) obj);
             }
@@ -98,6 +99,10 @@ public class IntakeResource extends AbstractCollectionSpaceResource {
             path.path("" + csid);
             Response response = Response.created(path.build()).build();
             return response;
+        } catch (UnauthorizedException ue) {
+            Response response = Response.status(
+                    Response.Status.UNAUTHORIZED).entity("Create failed reason " + ue.getErrorReason()).type("text/plain").build();
+            throw new WebApplicationException(response);
         } catch (Exception e) {
             if (logger.isDebugEnabled()) {
                 logger.debug("Caught exception in createIntake", e);
@@ -128,6 +133,10 @@ public class IntakeResource extends AbstractCollectionSpaceResource {
             DocumentHandler handler = createDocumentHandler(ctx);
             getRepositoryClient(ctx).get(ctx, csid, handler);
             result = (MultipartOutput) ctx.getOutput();
+        } catch (UnauthorizedException ue) {
+            Response response = Response.status(
+                    Response.Status.UNAUTHORIZED).entity("Get failed reason " + ue.getErrorReason()).type("text/plain").build();
+            throw new WebApplicationException(response);
         } catch (DocumentNotFoundException dnfe) {
             if (logger.isDebugEnabled()) {
                 logger.debug("getIntake", dnfe);
@@ -162,6 +171,10 @@ public class IntakeResource extends AbstractCollectionSpaceResource {
             DocumentHandler handler = createDocumentHandler(ctx);
             getRepositoryClient(ctx).getAll(ctx, handler);
             intakeObjectList = (IntakesCommonList) handler.getCommonPartList();
+        } catch (UnauthorizedException ue) {
+            Response response = Response.status(
+                    Response.Status.UNAUTHORIZED).entity("Index failed reason " + ue.getErrorReason()).type("text/plain").build();
+            throw new WebApplicationException(response);
         } catch (Exception e) {
             if (logger.isDebugEnabled()) {
                 logger.debug("Caught exception in getIntakeList", e);
@@ -194,6 +207,10 @@ public class IntakeResource extends AbstractCollectionSpaceResource {
             DocumentHandler handler = createDocumentHandler(ctx);
             getRepositoryClient(ctx).update(ctx, csid, handler);
             result = (MultipartOutput) ctx.getOutput();
+        } catch (UnauthorizedException ue) {
+            Response response = Response.status(
+                    Response.Status.UNAUTHORIZED).entity("Update failed reason " + ue.getErrorReason()).type("text/plain").build();
+            throw new WebApplicationException(response);
         } catch (DocumentNotFoundException dnfe) {
             if (logger.isDebugEnabled()) {
                 logger.debug("caugth exception in updateIntake", dnfe);
@@ -228,6 +245,10 @@ public class IntakeResource extends AbstractCollectionSpaceResource {
             ServiceContext ctx = MultipartServiceContextFactory.get().createServiceContext(null, getServiceName());
             getRepositoryClient(ctx).delete(ctx, csid);
             return Response.status(HttpResponseCodes.SC_OK).build();
+        } catch (UnauthorizedException ue) {
+            Response response = Response.status(
+                    Response.Status.UNAUTHORIZED).entity("Delete failed reason " + ue.getErrorReason()).type("text/plain").build();
+            throw new WebApplicationException(response);
         } catch (DocumentNotFoundException dnfe) {
             if (logger.isDebugEnabled()) {
                 logger.debug("caught exception in deleteIntake", dnfe);
index a77fb66e500a9ee52ca99749b911963fb80a77de..06431c47498ebb285d45526c542a0727ce2d7ae2 100644 (file)
@@ -14,8 +14,8 @@
 
     <modules>
         <!-- add modules below in the order based on dependencies -->
-        <module>common</module>
         <module>authentication</module>
+        <module>common</module>
         <module>account</module>
         <module>relation</module>
         <!--module>query</module-->
index 552a34210eef1450c0ccce260099b0ae40027506..436ee70be67bc9bc2bacf32ff52fb997a630b4d7 100644 (file)
@@ -50,6 +50,7 @@ import org.collectionspace.services.common.context.ServiceContext;
 import org.collectionspace.services.common.relation.IRelationsManager;
 import org.collectionspace.services.common.document.DocumentNotFoundException;
 import org.collectionspace.services.common.document.DocumentHandler;
+import org.collectionspace.services.common.security.UnauthorizedException;
 import org.collectionspace.services.relation.nuxeo.RelationHandlerFactory;
 import org.jboss.resteasy.plugins.providers.multipart.MultipartInput;
 import org.jboss.resteasy.plugins.providers.multipart.MultipartOutput;
@@ -76,7 +77,7 @@ public class NewRelationResource extends AbstractCollectionSpaceResource {
                 ctx.getRepositoryClientType().toString());
         docHandler.setServiceContext(ctx);
         if (ctx.getInput() != null) {
-            Object obj = ((MultipartServiceContext)ctx).getInputPart(ctx.getCommonPartLabel(), RelationsCommon.class);
+            Object obj = ((MultipartServiceContext) ctx).getInputPart(ctx.getCommonPartLabel(), RelationsCommon.class);
             if (obj != null) {
                 docHandler.setCommonPart((RelationsCommon) obj);
             }
@@ -95,6 +96,10 @@ public class NewRelationResource extends AbstractCollectionSpaceResource {
             path.path("" + csid);
             Response response = Response.created(path.build()).build();
             return response;
+        } catch (UnauthorizedException ue) {
+            Response response = Response.status(
+                    Response.Status.UNAUTHORIZED).entity("Create failed reason " + ue.getErrorReason()).type("text/plain").build();
+            throw new WebApplicationException(response);
         } catch (Exception e) {
             if (logger.isDebugEnabled()) {
                 logger.debug("Caught exception in createRelation", e);
@@ -124,6 +129,10 @@ public class NewRelationResource extends AbstractCollectionSpaceResource {
             DocumentHandler handler = createDocumentHandler(ctx);
             getRepositoryClient(ctx).get(ctx, csid, handler);
             result = (MultipartOutput) ctx.getOutput();
+        } catch (UnauthorizedException ue) {
+            Response response = Response.status(
+                    Response.Status.UNAUTHORIZED).entity("Get failed reason " + ue.getErrorReason()).type("text/plain").build();
+            throw new WebApplicationException(response);
         } catch (DocumentNotFoundException dnfe) {
             if (logger.isDebugEnabled()) {
                 logger.debug("getRelation", dnfe);
@@ -259,6 +268,10 @@ public class NewRelationResource extends AbstractCollectionSpaceResource {
             DocumentHandler handler = createDocumentHandler(ctx);
             getRepositoryClient(ctx).update(ctx, csid, handler);
             result = (MultipartOutput) ctx.getOutput();
+        } catch (UnauthorizedException ue) {
+            Response response = Response.status(
+                    Response.Status.UNAUTHORIZED).entity("Update failed reason " + ue.getErrorReason()).type("text/plain").build();
+            throw new WebApplicationException(response);
         } catch (DocumentNotFoundException dnfe) {
             if (logger.isDebugEnabled()) {
                 logger.debug("caugth exception in updateRelation", dnfe);
@@ -292,6 +305,10 @@ public class NewRelationResource extends AbstractCollectionSpaceResource {
             ServiceContext ctx = MultipartServiceContextFactory.get().createServiceContext(null, getServiceName());
             getRepositoryClient(ctx).delete(ctx, csid);
             return Response.status(HttpResponseCodes.SC_OK).build();
+        } catch (UnauthorizedException ue) {
+            Response response = Response.status(
+                    Response.Status.UNAUTHORIZED).entity("Delete failed reason " + ue.getErrorReason()).type("text/plain").build();
+            throw new WebApplicationException(response);
         } catch (DocumentNotFoundException dnfe) {
             if (logger.isDebugEnabled()) {
                 logger.debug("caught exception in deleteRelation", dnfe);
@@ -332,6 +349,10 @@ public class NewRelationResource extends AbstractCollectionSpaceResource {
             propsFromPath.put(IRelationsManager.OBJECT, objectCsid);
             getRepositoryClient(ctx).getAll(ctx, handler);
             relationList = (RelationsCommonList) handler.getCommonPartList();
+        } catch (UnauthorizedException ue) {
+            Response response = Response.status(
+                    Response.Status.UNAUTHORIZED).entity("Get relations failed reason " + ue.getErrorReason()).type("text/plain").build();
+            throw new WebApplicationException(response);
         } catch (Exception e) {
             if (logger.isDebugEnabled()) {
                 logger.debug("Caught exception in getRelationList", e);
index f627bb8927e302370a45e94a6add675d4324efc0..359580097b438b211bea0b3b07f78a18b222eff9 100644 (file)
@@ -47,6 +47,7 @@ import org.collectionspace.services.common.context.ServiceContext;
 import org.collectionspace.services.common.document.DocumentFilter;
 import org.collectionspace.services.common.document.DocumentHandler;
 import org.collectionspace.services.common.document.DocumentNotFoundException;
+import org.collectionspace.services.common.security.UnauthorizedException;
 import org.collectionspace.services.vocabulary.nuxeo.VocabularyHandlerFactory;
 import org.collectionspace.services.vocabulary.nuxeo.VocabularyItemDocumentModelHandler;
 import org.collectionspace.services.vocabulary.nuxeo.VocabularyItemHandlerFactory;
@@ -109,7 +110,7 @@ public class VocabularyResource extends AbstractCollectionSpaceResource {
         docHandler.setServiceContext(ctx);
         ((VocabularyItemDocumentModelHandler) docHandler).setInVocabulary(inVocabulary);
         if (ctx.getInput() != null) {
-            Object obj = ((MultipartServiceContext)ctx).getInputPart(ctx.getCommonPartLabel(getItemServiceName()),
+            Object obj = ((MultipartServiceContext) ctx).getInputPart(ctx.getCommonPartLabel(getItemServiceName()),
                     VocabularyitemsCommon.class);
             if (obj != null) {
                 docHandler.setCommonPart((VocabularyitemsCommon) obj);
@@ -129,6 +130,10 @@ public class VocabularyResource extends AbstractCollectionSpaceResource {
             path.path("" + csid);
             Response response = Response.created(path.build()).build();
             return response;
+        } catch (UnauthorizedException ue) {
+            Response response = Response.status(
+                    Response.Status.UNAUTHORIZED).entity("Create failed reason " + ue.getErrorReason()).type("text/plain").build();
+            throw new WebApplicationException(response);
         } catch (Exception e) {
             if (logger.isDebugEnabled()) {
                 logger.debug("Caught exception in createVocabulary", e);
@@ -159,6 +164,10 @@ public class VocabularyResource extends AbstractCollectionSpaceResource {
             DocumentHandler handler = createDocumentHandler(ctx);
             getRepositoryClient(ctx).get(ctx, csid, handler);
             result = (MultipartOutput) ctx.getOutput();
+        } catch (UnauthorizedException ue) {
+            Response response = Response.status(
+                    Response.Status.UNAUTHORIZED).entity("Get failed reason " + ue.getErrorReason()).type("text/plain").build();
+            throw new WebApplicationException(response);
         } catch (DocumentNotFoundException dnfe) {
             if (logger.isDebugEnabled()) {
                 logger.debug("getVocabulary", dnfe);
@@ -192,15 +201,19 @@ public class VocabularyResource extends AbstractCollectionSpaceResource {
             ServiceContext ctx = MultipartServiceContextFactory.get().createServiceContext(null, getServiceName());
             MultivaluedMap<String, String> queryParams = ui.getQueryParameters();
             DocumentHandler handler = createDocumentHandler(ctx);
-            DocumentFilter myFilter = 
-               DocumentFilter.CreatePaginatedDocumentFilter(queryParams);
-               String nameQ = queryParams.getFirst("refName");
-               if(nameQ!= null) {
-                myFilter.setWhereClause("vocabularies_common:refName='"+nameQ+"'");
-               }
+            DocumentFilter myFilter =
+                    DocumentFilter.CreatePaginatedDocumentFilter(queryParams);
+            String nameQ = queryParams.getFirst("refName");
+            if (nameQ != null) {
+                myFilter.setWhereClause("vocabularies_common:refName='" + nameQ + "'");
+            }
             handler.setDocumentFilter(myFilter);
             getRepositoryClient(ctx).getFiltered(ctx, handler);
             vocabularyObjectList = (VocabulariesCommonList) handler.getCommonPartList();
+        } catch (UnauthorizedException ue) {
+            Response response = Response.status(
+                    Response.Status.UNAUTHORIZED).entity("Index failed reason " + ue.getErrorReason()).type("text/plain").build();
+            throw new WebApplicationException(response);
         } catch (Exception e) {
             if (logger.isDebugEnabled()) {
                 logger.debug("Caught exception in getVocabularyList", e);
@@ -233,6 +246,10 @@ public class VocabularyResource extends AbstractCollectionSpaceResource {
             DocumentHandler handler = createDocumentHandler(ctx);
             getRepositoryClient(ctx).update(ctx, csid, handler);
             result = (MultipartOutput) ctx.getOutput();
+        } catch (UnauthorizedException ue) {
+            Response response = Response.status(
+                    Response.Status.UNAUTHORIZED).entity("Update failed reason " + ue.getErrorReason()).type("text/plain").build();
+            throw new WebApplicationException(response);
         } catch (DocumentNotFoundException dnfe) {
             if (logger.isDebugEnabled()) {
                 logger.debug("caugth exception in updateVocabulary", dnfe);
@@ -264,9 +281,13 @@ public class VocabularyResource extends AbstractCollectionSpaceResource {
             throw new WebApplicationException(response);
         }
         try {
-ServiceContext ctx = MultipartServiceContextFactory.get().createServiceContext(null, getServiceName());
+            ServiceContext ctx = MultipartServiceContextFactory.get().createServiceContext(null, getServiceName());
             getRepositoryClient(ctx).delete(ctx, csid);
             return Response.status(HttpResponseCodes.SC_OK).build();
+        } catch (UnauthorizedException ue) {
+            Response response = Response.status(
+                    Response.Status.UNAUTHORIZED).entity("Delete failed reason " + ue.getErrorReason()).type("text/plain").build();
+            throw new WebApplicationException(response);
         } catch (DocumentNotFoundException dnfe) {
             if (logger.isDebugEnabled()) {
                 logger.debug("caught exception in deleteVocabulary", dnfe);
@@ -297,6 +318,10 @@ ServiceContext ctx = MultipartServiceContextFactory.get().createServiceContext(n
             path.path(parentcsid + "/items/" + itemcsid);
             Response response = Response.created(path.build()).build();
             return response;
+        } catch (UnauthorizedException ue) {
+            Response response = Response.status(
+                    Response.Status.UNAUTHORIZED).entity("Create failed reason " + ue.getErrorReason()).type("text/plain").build();
+            throw new WebApplicationException(response);
         } catch (Exception e) {
             if (logger.isDebugEnabled()) {
                 logger.debug("Caught exception in createVocabularyItem", e);
@@ -336,7 +361,11 @@ ServiceContext ctx = MultipartServiceContextFactory.get().createServiceContext(n
             DocumentHandler handler = createItemDocumentHandler(ctx, parentcsid);
             getRepositoryClient(ctx).get(ctx, itemcsid, handler);
             // TODO should we assert that the item is in the passed vocab?
-            result = (MultipartOutput)ctx.getOutput();
+            result = (MultipartOutput) ctx.getOutput();
+        } catch (UnauthorizedException ue) {
+            Response response = Response.status(
+                    Response.Status.UNAUTHORIZED).entity("Get failed reason " + ue.getErrorReason()).type("text/plain").build();
+            throw new WebApplicationException(response);
         } catch (DocumentNotFoundException dnfe) {
             if (logger.isDebugEnabled()) {
                 logger.debug("getVocabularyItem", dnfe);
@@ -378,6 +407,10 @@ ServiceContext ctx = MultipartServiceContextFactory.get().createServiceContext(n
             handler.setDocumentFilter(myFilter);
             getRepositoryClient(ctx).getFiltered(ctx, handler);
             vocabularyItemObjectList = (VocabularyitemsCommonList) handler.getCommonPartList();
+        } catch (UnauthorizedException ue) {
+            Response response = Response.status(
+                    Response.Status.UNAUTHORIZED).entity("Index failed reason " + ue.getErrorReason()).type("text/plain").build();
+            throw new WebApplicationException(response);
         } catch (Exception e) {
             if (logger.isDebugEnabled()) {
                 logger.debug("Caught exception in getVocabularyItemList", e);
@@ -418,7 +451,11 @@ ServiceContext ctx = MultipartServiceContextFactory.get().createServiceContext(n
             ServiceContext ctx = MultipartServiceContextFactory.get().createServiceContext(theUpdate, getItemServiceName());
             DocumentHandler handler = createItemDocumentHandler(ctx, parentcsid);
             getRepositoryClient(ctx).update(ctx, itemcsid, handler);
-            result = (MultipartOutput)ctx.getOutput();
+            result = (MultipartOutput) ctx.getOutput();
+        } catch (UnauthorizedException ue) {
+            Response response = Response.status(
+                    Response.Status.UNAUTHORIZED).entity("Update failed reason " + ue.getErrorReason()).type("text/plain").build();
+            throw new WebApplicationException(response);
         } catch (DocumentNotFoundException dnfe) {
             if (logger.isDebugEnabled()) {
                 logger.debug("caugth exception in updateVocabularyItem", dnfe);
@@ -462,6 +499,10 @@ ServiceContext ctx = MultipartServiceContextFactory.get().createServiceContext(n
             ServiceContext ctx = MultipartServiceContextFactory.get().createServiceContext(null, getItemServiceName());
             getRepositoryClient(ctx).delete(ctx, itemcsid);
             return Response.status(HttpResponseCodes.SC_OK).build();
+        } catch (UnauthorizedException ue) {
+            Response response = Response.status(
+                    Response.Status.UNAUTHORIZED).entity("Delete failed reason " + ue.getErrorReason()).type("text/plain").build();
+            throw new WebApplicationException(response);
         } catch (DocumentNotFoundException dnfe) {
             if (logger.isDebugEnabled()) {
                 logger.debug("caught exception in deleteVocabulary", dnfe);