From c212e0b507e15e0a2ee337d3362c9d83595cecc6 Mon Sep 17 00:00:00 2001 From: Patrick Schmitz Date: Wed, 17 Nov 2010 22:32:46 +0000 Subject: [PATCH] CSPACE-3090 Added code to create default admin and reader users for each tenancy at startup. These are tied to the respective appropriate roles. Disabled code in sql scripts to create test@collectionspace.org and other misc users/accounts. --- .../main/resources/db/mysql/test_account.sql | 10 +- .../db/mysql/test_authentication.sql | 4 +- .../importer/AuthorizationSeed.java | 4 +- .../applicationContext-authorization-test.xml | 3 +- .../resources/db/mysql/test_authorization.sql | 8 +- .../spring/SpringAuthorizationProvider.java | 2 +- .../collectionspace-client.properties | 6 +- .../main/config/services/tenant-bindings.xml | 4 +- .../services/common/ServiceMain.java | 420 +++++++++++++++++- services/security/client/pom.xml | 5 + .../client/test/AuthorizationServiceTest.java | 236 +++++++++- .../client/test/MultiTenancyTest.java | 9 +- 12 files changed, 668 insertions(+), 43 deletions(-) diff --git a/services/account/pstore/src/main/resources/db/mysql/test_account.sql b/services/account/pstore/src/main/resources/db/mysql/test_account.sql index 74d334fa0..2f18d5397 100644 --- a/services/account/pstore/src/main/resources/db/mysql/test_account.sql +++ b/services/account/pstore/src/main/resources/db/mysql/test_account.sql @@ -9,17 +9,17 @@ use cspace; -- movingimages -- INSERT INTO `cspace`.`tenants` (`id`, `name`, `created_at`) VALUES ('1','movingimages.us', now()); -- pahma -- -INSERT INTO `cspace`.`tenants` (`id`, `name`, `created_at`) VALUES ('2','hearstmuseum.berkeley.edu', now()); +--INSERT INTO `cspace`.`tenants` (`id`, `name`, `created_at`) VALUES ('2','hearstmuseum.berkeley.edu', now()); -- Accounts -- default test account -- INSERT INTO `cspace`.`accounts_common` (`csid`, `email`, `phone`, `mobile`, `userid`, `status`, `screen_name`, `created_at`) VALUES ('eeca40d7-dc77-4cc5-b489-16a53c75525a','test.test@berkeley.edu',NULL,NULL,'test','ACTIVE','test', now()); -- Additional account introduced during integration on release 0.6, and currently relied upon by the Application Layer. -INSERT INTO `cspace`.`accounts_common` (`csid`, `email`, `phone`, `mobile`, `userid`, `status`, `screen_name`, `created_at`) VALUES ('251f98f3-0292-4f3e-aa95-455314050e1b','test@collectionspace.org',NULL,NULL,'test@collectionspace.org','ACTIVE','test@collectionspace.org', now()); +--INSERT INTO `cspace`.`accounts_common` (`csid`, `email`, `phone`, `mobile`, `userid`, `status`, `screen_name`, `created_at`) VALUES ('251f98f3-0292-4f3e-aa95-455314050e1b','test@collectionspace.org',NULL,NULL,'test@collectionspace.org','ACTIVE','test@collectionspace.org', now()); -- PAHMA test account -- -INSERT INTO `cspace`.`accounts_common` (`csid`, `email`, `phone`, `mobile`, `userid`, `status`, `screen_name`, `created_at`) VALUES ('ff2b4440-ed0d-4892-adb4-b6999eba3ae7','test@hearstmuseum.berkeley.edu',NULL,NULL,'test-pahma','ACTIVE','test-pahma', now()); +--INSERT INTO `cspace`.`accounts_common` (`csid`, `email`, `phone`, `mobile`, `userid`, `status`, `screen_name`, `created_at`) VALUES ('ff2b4440-ed0d-4892-adb4-b6999eba3ae7','test@hearstmuseum.berkeley.edu',NULL,NULL,'test-pahma','ACTIVE','test-pahma', now()); -- Association of accounts with tenants INSERT INTO `cspace`.`accounts_tenants` (`TENANTS_ACCOUNTSCOMMON_CSID`, `tenant_id`) VALUES ('eeca40d7-dc77-4cc5-b489-16a53c75525a', '1'); -INSERT INTO `cspace`.`accounts_tenants` (`TENANTS_ACCOUNTSCOMMON_CSID`, `tenant_id`) VALUES ('251f98f3-0292-4f3e-aa95-455314050e1b', '1'); -INSERT INTO `cspace`.`accounts_tenants` (`TENANTS_ACCOUNTSCOMMON_CSID`, `tenant_id`) VALUES ('ff2b4440-ed0d-4892-adb4-b6999eba3ae7', '2'); +--INSERT INTO `cspace`.`accounts_tenants` (`TENANTS_ACCOUNTSCOMMON_CSID`, `tenant_id`) VALUES ('251f98f3-0292-4f3e-aa95-455314050e1b', '1'); +--INSERT INTO `cspace`.`accounts_tenants` (`TENANTS_ACCOUNTSCOMMON_CSID`, `tenant_id`) VALUES ('ff2b4440-ed0d-4892-adb4-b6999eba3ae7', '2'); diff --git a/services/authentication/pstore/src/main/resources/db/mysql/test_authentication.sql b/services/authentication/pstore/src/main/resources/db/mysql/test_authentication.sql index 26bb0bb16..f9a62ae1e 100644 --- a/services/authentication/pstore/src/main/resources/db/mysql/test_authentication.sql +++ b/services/authentication/pstore/src/main/resources/db/mysql/test_authentication.sql @@ -8,6 +8,6 @@ use cspace; -- default test user -- insert into `users` (`username`,`passwd`, `created_at`) VALUES ('test','n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=', now()); -- Additional account introduced during integration on release 0.6, and currently relied upon by the Application Layer. -insert into `users` (`username`,`passwd`, `created_at`) VALUES ('test@collectionspace.org','NyaDNd1pMQRb3N+SYj/4GaZCRLU9DnRtQ4eXNJ1NpXg=', now()); +--insert into `users` (`username`,`passwd`, `created_at`) VALUES ('test@collectionspace.org','NyaDNd1pMQRb3N+SYj/4GaZCRLU9DnRtQ4eXNJ1NpXg=', now()); -- user for testing pahma deployment -- -insert into `users` (`username`,`passwd`, `created_at`) VALUES ('test-pahma','n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=', now()); +--insert into `users` (`username`,`passwd`, `created_at`) VALUES ('test-pahma','n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=', now()); diff --git a/services/authorization-mgt/import/src/main/java/org/collectionspace/services/authorization/importer/AuthorizationSeed.java b/services/authorization-mgt/import/src/main/java/org/collectionspace/services/authorization/importer/AuthorizationSeed.java index fee4fa997..4e80ddb5b 100644 --- a/services/authorization-mgt/import/src/main/java/org/collectionspace/services/authorization/importer/AuthorizationSeed.java +++ b/services/authorization-mgt/import/src/main/java/org/collectionspace/services/authorization/importer/AuthorizationSeed.java @@ -85,8 +85,8 @@ public class AuthorizationSeed { public void seedPermissions(PermissionsList permList, PermissionsRolesList permRoleList) throws Exception { for (Permission p : permList.getPermissions()) { - if (logger.isDebugEnabled()) { - logger.debug("adding permission for res=" + p.getResourceName() + + if (logger.isTraceEnabled()) { + logger.trace("adding permission for res=" + p.getResourceName() + " for tenant=" + p.getTenantId()); } for (PermissionRole pr : permRoleList.getPermissionRoles()) { diff --git a/services/authorization-mgt/import/src/main/resources/applicationContext-authorization-test.xml b/services/authorization-mgt/import/src/main/resources/applicationContext-authorization-test.xml index 42e5bf502..2dcbb630b 100644 --- a/services/authorization-mgt/import/src/main/resources/applicationContext-authorization-test.xml +++ b/services/authorization-mgt/import/src/main/resources/applicationContext-authorization-test.xml @@ -89,5 +89,6 @@ - + + diff --git a/services/authorization/pstore/src/main/resources/db/mysql/test_authorization.sql b/services/authorization/pstore/src/main/resources/db/mysql/test_authorization.sql index 1e8ae2802..22aabe14f 100644 --- a/services/authorization/pstore/src/main/resources/db/mysql/test_authorization.sql +++ b/services/authorization/pstore/src/main/resources/db/mysql/test_authorization.sql @@ -13,14 +13,12 @@ insert into `accounts_roles`(`account_id`, `user_id`, `role_id`, `role_name`, `c insert into `accounts_roles`(`account_id`, `user_id`, `role_id`, `role_name`, `created_at`) values ('eeca40d7-dc77-4cc5-b489-16a53c75525a', 'test', '0', 'ROLE_ADMINISTRATOR', now()); -- Additional account introduced during integration on release 0.6, and currently relied upon by the Application Layer. -insert into `accounts_roles`(`account_id`, `user_id`, `role_id`, `role_name`, `created_at`) values ('251f98f3-0292-4f3e-aa95-455314050e1b', 'test@collectionspace.org', '-1', 'ROLE_SPRING_ADMIN', now()); -insert into `accounts_roles`(`account_id`, `user_id`, `role_id`, `role_name`, `created_at`) values ('251f98f3-0292-4f3e-aa95-455314050e1b', 'test@collectionspace.org', '0', 'ROLE_ADMINISTRATOR', now()); +--insert into `accounts_roles`(`account_id`, `user_id`, `role_id`, `role_name`, `created_at`) values ('251f98f3-0292-4f3e-aa95-455314050e1b', 'test@collectionspace.org', '0', 'ROLE_ADMINISTRATOR', now()); -- test account for pahma -- -insert into `accounts_roles`(`account_id`, `user_id`, `role_id`, `role_name`, `created_at`) values ('ff2b4440-ed0d-4892-adb4-b6999eba3ae7', 'test-pahma', '-1', 'ROLE_SPRING_ADMIN', now()); -insert into `accounts_roles`(`account_id`, `user_id`, `role_id`, `role_name`, `created_at`) values ('ff2b4440-ed0d-4892-adb4-b6999eba3ae7', 'test-pahma', '0', 'ROLE_ADMINISTRATOR', now()); +--insert into `accounts_roles`(`account_id`, `user_id`, `role_id`, `role_name`, `created_at`) values ('ff2b4440-ed0d-4892-adb4-b6999eba3ae7', 'test-pahma', '0', 'ROLE_ADMINISTRATOR', now()); -- todo: barney is created in security test but accountrole is not yet created there, so add fake account id -insert into `accounts_roles`(`account_id`, `user_id`, `role_id`, `role_name`, `created_at`) values ('1', 'barney', '2', 'ROLE_USERS', now()); +--insert into `accounts_roles`(`account_id`, `user_id`, `role_id`, `role_name`, `created_at`) values ('1', 'barney', '2', 'ROLE_USERS', now()); diff --git a/services/authorization/service/src/main/java/org/collectionspace/services/authorization/spring/SpringAuthorizationProvider.java b/services/authorization/service/src/main/java/org/collectionspace/services/authorization/spring/SpringAuthorizationProvider.java index f0dd05a00..b891f7a8d 100644 --- a/services/authorization/service/src/main/java/org/collectionspace/services/authorization/spring/SpringAuthorizationProvider.java +++ b/services/authorization/service/src/main/java/org/collectionspace/services/authorization/spring/SpringAuthorizationProvider.java @@ -52,7 +52,7 @@ import org.springframework.transaction.support.DefaultTransactionDefinition; */ public class SpringAuthorizationProvider implements CSpaceAuthorizationProvider { - final Log log = LogFactory.getLog(SpringPermissionEvaluator.class); + final Log log = LogFactory.getLog(SpringAuthorizationProvider.class); @Autowired private MutableAclService providerAclService; @Autowired diff --git a/services/client/src/main/resources/collectionspace-client.properties b/services/client/src/main/resources/collectionspace-client.properties index df104660a..e469b8f37 100644 --- a/services/client/src/main/resources/collectionspace-client.properties +++ b/services/client/src/main/resources/collectionspace-client.properties @@ -4,8 +4,8 @@ cspace.url=http://localhost:8180/cspace-services/ cspace.ssl=false cspace.auth=true # default user -cspace.user=test +cspace.user=admin@collectionspace.org #cspace.user=test-pahma -cspace.password=test +cspace.password=Administrator # default tenant -cspace.tenant=1 \ No newline at end of file +cspace.tenant=1 diff --git a/services/common/src/main/config/services/tenant-bindings.xml b/services/common/src/main/config/services/tenant-bindings.xml index 30d2381dd..ceb8c364f 100644 --- a/services/common/src/main/config/services/tenant-bindings.xml +++ b/services/common/src/main/config/services/tenant-bindings.xml @@ -14,7 +14,7 @@ + id="1" name="collectionspace.org" displayName="CollectionSpace Demo" version="0.1"> @@ -1200,7 +1200,7 @@ - + diff --git a/services/common/src/main/java/org/collectionspace/services/common/ServiceMain.java b/services/common/src/main/java/org/collectionspace/services/common/ServiceMain.java index 0cfbcfeb4..eb5aa130f 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/ServiceMain.java +++ b/services/common/src/main/java/org/collectionspace/services/common/ServiceMain.java @@ -3,11 +3,26 @@ */ package org.collectionspace.services.common; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; import java.util.Hashtable; import java.util.List; +import java.util.UUID; + +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.security.auth.login.LoginException; +import javax.sql.DataSource; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Response; import org.collectionspace.services.common.config.ServicesConfigReaderImpl; import org.collectionspace.services.common.config.TenantBindingConfigReaderImpl; +import org.collectionspace.services.common.security.SecurityUtils; import org.collectionspace.services.common.tenant.TenantBindingType; import org.collectionspace.services.common.types.PropertyItemType; import org.collectionspace.services.common.types.PropertyType; @@ -16,6 +31,7 @@ import org.collectionspace.services.nuxeo.client.java.TenantRepository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; + /** * Main class for Services layer. It reads configuration and performs service * level initialization. It is a singleton. @@ -32,6 +48,16 @@ public class ServiceMain { private String serverRootDir = null; private ServicesConfigReaderImpl servicesConfigReader; private TenantBindingConfigReaderImpl tenantBindingConfigReader; + private static final String TENANT_ADMIN_ACCT_PREFIX = "admin@"; + private static final String TENANT_READER_ACCT_PREFIX = "reader@"; + private static final String ROLE_PREFIX = "ROLE_"; + private static final String SPRING_ADMIN_ROLE = "ROLE_SPRING_ADMIN"; + private static final String TENANT_ADMIN_ROLE_SUFFIX = "_TENANT_ADMINISTRATOR"; + private static final String TENANT_READER_ROLE_SUFFIX = "_TENANT_READER"; + private static final String DEFAULT_ADMIN_PASSWORD = "Administrator"; + private static final String DEFAULT_READER_PASSWORD = "reader"; + + private static String repositoryName = "CspaceDS"; private ServiceMain() { } @@ -67,6 +93,11 @@ public class ServiceMain { setServerRootDir(); readConfig(); propagateConfiguredProperties(); + try { + createDefaultAccounts(); + } catch(Exception e) { + logger.error("Default Account setup failed on exception: "+e.getLocalizedMessage()); + } if (getClientType().equals(ClientType.JAVA)) { nuxeoConnector = NuxeoConnector.getInstance(); nuxeoConnector.initialize( @@ -96,7 +127,7 @@ public class ServiceMain { getServicesConfigReader().read(); tenantBindingConfigReader = new TenantBindingConfigReaderImpl(getServerRootDir()); - getTenantBindingConfigReader().read(); + tenantBindingConfigReader.read(); } private void propagateConfiguredProperties() { @@ -109,11 +140,396 @@ public class ServiceMain { } } } + + private void createDefaultAccounts() { + if (logger.isDebugEnabled()) { + logger.debug("ServiceMain.createDefaultAccounts starting..."); + } + Hashtable tenantBindings = + tenantBindingConfigReader.getTenantBindings(); + Hashtable tenantInfo = new Hashtable(); + for (TenantBindingType tenantBinding : tenantBindings.values()) { + String tId = tenantBinding.getId(); + String tName = tenantBinding.getName(); + tenantInfo.put(tId, tName); + if (logger.isDebugEnabled()) { + logger.debug("createDefaultAccounts found configured tenant id: "+tId+" name: "+tName); + } + } + Connection conn = null; + PreparedStatement pstmt = null; + Statement stmt = null; + // TODO - need to put in tests for existence first. + // We could just look for the accounts per tenant up front, and assume that + // the rest is there if the accounts are. + // Could add a sql script to remove these if need be - Spring only does roles, + // and we're not touching that, so we could safely toss the + // accounts, users, account-tenants, account-roles, and start over. + try { + conn = getConnection(); + // First find or create the tenants + String queryTenantSQL = + "SELECT `id`,`name` FROM `tenants`"; + stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(queryTenantSQL); + ArrayList existingTenants = new ArrayList(); + while (rs.next()) { + String tId = rs.getString("id"); + String tName = rs.getString("name"); + if(tenantInfo.containsKey(tId)) { + existingTenants.add(tId); + if(!tenantInfo.get(tId).equalsIgnoreCase(tName)) { + logger.warn("Configured name for tenant: " + +tId+" in repository: "+tName + +" does not match config'd name: "+ tenantInfo.get(tId)); + } + } + } + rs.close(); + + String insertTenantSQL = + "INSERT INTO `tenants` (`id`,`name`,`created_at`) VALUES (?,?, now())"; + pstmt = conn.prepareStatement(insertTenantSQL); // create a statement + for(String tId : tenantInfo.keySet()) { + if(existingTenants.contains(tId)) { + if (logger.isDebugEnabled()) { + logger.debug("createDefaultAccounts: tenant exists (skipping): " + +tenantInfo.get(tId)); + } + continue; + } + pstmt.setString(1, tId); // set id param + pstmt.setString(2, tenantInfo.get(tId)); // set name param + if (logger.isDebugEnabled()) { + logger.debug("createDefaultAccounts adding entry for tenant: "+tId); + } + pstmt.executeUpdate(); + } + pstmt.close(); + // Second find or create the users + String queryUserSQL = + "SELECT `username` FROM `users` WHERE `username` LIKE '" + +TENANT_ADMIN_ACCT_PREFIX+"%' OR `username` LIKE '" + +TENANT_READER_ACCT_PREFIX+"%'"; + rs = stmt.executeQuery(queryUserSQL); + ArrayList usersInRepo = new ArrayList(); + while (rs.next()) { + String uName = rs.getString("username"); + usersInRepo.add(uName); + } + rs.close(); + String insertUserSQL = + "INSERT INTO `users` (`username`,`passwd`, `created_at`)" + +" VALUES (?,?, now())"; + pstmt = conn.prepareStatement(insertUserSQL); // create a statement + for(String tName : tenantInfo.values()) { + String adminAcctName = getDefaultAdminUserID(tName); + if(!usersInRepo.contains(adminAcctName)) { + String secEncPasswd = SecurityUtils.createPasswordHash( + adminAcctName, DEFAULT_ADMIN_PASSWORD); + pstmt.setString(1, adminAcctName); // set username param + pstmt.setString(2, secEncPasswd); // set passwd param + if (logger.isDebugEnabled()) { + logger.debug("createDefaultAccounts adding user: " + +adminAcctName+" for tenant: "+tName); + } + pstmt.executeUpdate(); + } else if (logger.isDebugEnabled()) { + logger.debug("createDefaultAccounts: user: "+adminAcctName + +" already exists - skipping."); + } + + + String readerAcctName = getDefaultReaderUserID(tName); + if(!usersInRepo.contains(readerAcctName)) { + String secEncPasswd = SecurityUtils.createPasswordHash( + readerAcctName, DEFAULT_READER_PASSWORD); + pstmt.setString(1, readerAcctName); // set username param + pstmt.setString(2, secEncPasswd); // set passwd param + if (logger.isDebugEnabled()) { + logger.debug("createDefaultAccounts adding user: " + +readerAcctName+" for tenant: "+tName); + } + pstmt.executeUpdate(); + } else if (logger.isDebugEnabled()) { + logger.debug("createDefaultAccounts: user: "+readerAcctName + +" already exists - skipping."); + } + } + pstmt.close(); + // Third, create the accounts. Assume that if the users were already there, + // then the accounts were as well + String insertAccountSQL = + "INSERT INTO `accounts_common` " + + "(`csid`, `email`, `userid`, `status`, `screen_name`, `created_at`) " + + "VALUES (?,?,?,'ACTIVE',?, now())"; + Hashtable tenantAdminAcctCSIDs = new Hashtable(); + Hashtable tenantReaderAcctCSIDs = new Hashtable(); + pstmt = conn.prepareStatement(insertAccountSQL); // create a statement + for(String tId : tenantInfo.keySet()) { + String tName = tenantInfo.get(tId); + String adminCSID = UUID.randomUUID().toString(); + tenantAdminAcctCSIDs.put(tId, adminCSID); + String adminAcctName = getDefaultAdminUserID(tName); + if(!usersInRepo.contains(adminAcctName)) { + pstmt.setString(1, adminCSID); // set csid param + pstmt.setString(2, adminAcctName); // set email param (bogus) + pstmt.setString(3, adminAcctName); // set userid param + pstmt.setString(4, "Administrator");// set screen name param + if (logger.isDebugEnabled()) { + logger.debug("createDefaultAccounts adding account: " + +adminAcctName+" for tenant: "+tName); + } + pstmt.executeUpdate(); + } else if (logger.isDebugEnabled()) { + logger.debug("createDefaultAccounts: user: "+adminAcctName + +" already exists - skipping account generation."); + } + + String readerCSID = UUID.randomUUID().toString(); + tenantReaderAcctCSIDs.put(tId, readerCSID); + String readerAcctName = getDefaultReaderUserID(tName); + if(!usersInRepo.contains(readerAcctName)) { + pstmt.setString(1, readerCSID); // set csid param + pstmt.setString(2, readerAcctName); // set email param (bogus) + pstmt.setString(3, readerAcctName); // set userid param + pstmt.setString(4, "Reader"); // set screen name param + if (logger.isDebugEnabled()) { + logger.debug("createDefaultAccounts adding account: " + +readerAcctName+" for tenant: "+tName); + } + pstmt.executeUpdate(); + } else if (logger.isDebugEnabled()) { + logger.debug("createDefaultAccounts: user: "+readerAcctName + +" already exists - skipping account creation."); + } + } + pstmt.close(); + // Fourth, bind accounts to tenants. Assume that if the users were already there, + // then the accounts were bound to tenants correctly + String insertAccountTenantSQL = + "INSERT INTO `accounts_tenants` (`TENANTS_ACCOUNTSCOMMON_CSID`,`tenant_id`) " + + "VALUES (?, ?)"; + pstmt = conn.prepareStatement(insertAccountTenantSQL); // create a statement + for(String tId : tenantInfo.keySet()) { + String tName = tenantInfo.get(tId); + if(!usersInRepo.contains(getDefaultAdminUserID(tName))) { + String adminAcct = tenantAdminAcctCSIDs.get(tId); + pstmt.setString(1, adminAcct); // set acct CSID param + pstmt.setString(2, tId); // set tenant_id param + if (logger.isDebugEnabled()) { + logger.debug("createDefaultAccounts binding account id: " + +adminAcct+" to tenant id: "+tId); + } + pstmt.executeUpdate(); + } + if(!usersInRepo.contains(getDefaultReaderUserID(tName))) { + String readerAcct = tenantReaderAcctCSIDs.get(tId); + pstmt.setString(1, readerAcct); // set acct CSID param + pstmt.setString(2, tId); // set tenant_id param + if (logger.isDebugEnabled()) { + logger.debug("createDefaultAccounts binding account id: " + +readerAcct+" to tenant id: "+tId); + } + pstmt.executeUpdate(); + } + } + pstmt.close(); + // Fifth, fetch and save the default roles + String querySpringRole = + "SELECT `csid` from `Roles` WHERE `rolename`='"+SPRING_ADMIN_ROLE+"'"; + rs = stmt.executeQuery(querySpringRole); + if(!rs.next()) { + throw new RuntimeException("Cannot find SPRING ADMIN role!"); + } + String springAdminRoleCSID = rs.getString(1); + if (logger.isDebugEnabled()) { + logger.debug("createDefaultAccounts found Spring Admin role: " + +springAdminRoleCSID); + } + rs.close(); + String getRoleCSIDSql = + "SELECT `csid` from `Roles` WHERE `tenant_id`=? and `rolename`=?"; + pstmt = conn.prepareStatement(getRoleCSIDSql); // create a statement + rs = null; + Hashtable tenantAdminRoleCSIDs = new Hashtable(); + Hashtable tenantReaderRoleCSIDs = new Hashtable(); + for(String tId : tenantInfo.keySet()) { + pstmt.setString(1, tId); // set tenant_id param + pstmt.setString(2, getDefaultAdminRole(tId)); // set rolename param + rs = pstmt.executeQuery(); + // extract data from the ResultSet + if(!rs.next()) { + throw new RuntimeException("Cannot find role: "+getDefaultAdminRole(tId) + +" for tenant id: "+tId+" in Roles!"); + } + String tenantAdminRoleCSID = rs.getString(1); + if (logger.isDebugEnabled()) { + logger.debug("createDefaultAccounts found role: " + +getDefaultAdminRole(tId)+"("+tenantAdminRoleCSID + +") for tenant id: "+tId); + } + tenantAdminRoleCSIDs.put(tId, tenantAdminRoleCSID); + pstmt.setString(1, tId); // set tenant_id param + pstmt.setString(2, getDefaultReaderRole(tId)); // set rolename param + rs.close(); + rs = pstmt.executeQuery(); + // extract data from the ResultSet + if(!rs.next()) { + throw new RuntimeException("Cannot find role: "+getDefaultReaderRole(tId) + +" for tenant id: "+tId+" in Roles!"); + } + String tenantReaderRoleCSID = rs.getString(1); + if (logger.isDebugEnabled()) { + logger.debug("createDefaultAccounts found role: " + +getDefaultReaderRole(tId)+"("+tenantReaderRoleCSID + +") for tenant id: "+tId); + } + tenantReaderRoleCSIDs.put(tId, tenantReaderRoleCSID); + rs.close(); + } + pstmt.close(); + // Sixth, bind the accounts to roles. If the users already existed, + // we'll assume they were set up correctly. + String insertAccountRoleSQL = + "INSERT INTO `accounts_roles`(`account_id`, `user_id`, `role_id`, `role_name`, `created_at`)" + + " VALUES(?, ?, ?, ?, now())"; + if (logger.isDebugEnabled()) { + logger.debug("createDefaultAccounts binding accounts to roles with SQL:\n" + +insertAccountRoleSQL); + } + pstmt = conn.prepareStatement(insertAccountRoleSQL); // create a statement + for(String tId : tenantInfo.keySet()) { + String adminUserId = getDefaultAdminUserID(tenantInfo.get(tId)); + if(!usersInRepo.contains(adminUserId)) { + String adminAcct = tenantAdminAcctCSIDs.get(tId); + String adminRoleId = tenantAdminRoleCSIDs.get(tId); + pstmt.setString(1, adminAcct); // set acct CSID param + pstmt.setString(2, adminUserId); // set user_id param + pstmt.setString(3, adminRoleId); // set role_id param + pstmt.setString(4, getDefaultAdminRole(tId)); // set rolename param + if (logger.isDebugEnabled()) { + logger.debug("createDefaultAccounts binding account: " + +adminUserId+" to Admin role("+adminRoleId + +") for tenant id: "+tId); + } + pstmt.executeUpdate(); + // Now add the Spring Admin Role to the admin accounts + pstmt.setString(3, springAdminRoleCSID); // set role_id param + pstmt.setString(4, SPRING_ADMIN_ROLE); // set rolename param + if (logger.isDebugEnabled()) { + logger.debug("createDefaultAccounts binding account: " + +adminUserId+" to Spring Admin role: "+springAdminRoleCSID); + } + pstmt.executeUpdate(); + } + String readerUserId = getDefaultReaderUserID(tenantInfo.get(tId)); + if(!usersInRepo.contains(readerUserId)) { + String readerAcct = tenantReaderAcctCSIDs.get(tId); + String readerRoleId = tenantReaderRoleCSIDs.get(tId); + pstmt.setString(1, readerAcct); // set acct CSID param + pstmt.setString(2, readerUserId); // set user_id param + pstmt.setString(3, readerRoleId); // set role_id param + pstmt.setString(4, getDefaultReaderRole(tId)); // set rolename param + if (logger.isDebugEnabled()) { + logger.debug("createDefaultAccounts binding account: " + +readerUserId+" to Reader role("+readerRoleId + +") for tenant id: "+tId); + } + pstmt.executeUpdate(); + } + } + pstmt.close(); + stmt.close(); + } catch (RuntimeException rte) { + if (logger.isDebugEnabled()) { + logger.debug("Exception in createDefaultAccounts: "+ + rte.getLocalizedMessage()); + logger.debug(rte.getStackTrace().toString()); + } + throw rte; + } catch (SQLException sqle) { + // SQLExceptions can be chained. We have at least one exception, so + // set up a loop to make sure we let the user know about all of them + // if there happens to be more than one. + if (logger.isDebugEnabled()) { + SQLException tempException = sqle; + while (null != tempException) { + logger.debug("SQL Exception: " + sqle.getLocalizedMessage()); + tempException = tempException.getNextException(); + } + logger.debug(sqle.getStackTrace().toString()); + } + throw new RuntimeException("SQL problem in createDefaultAccounts: ", sqle); + } catch (Exception e) { + if (logger.isDebugEnabled()) { + logger.debug("Exception in createDefaultAccounts: "+ + e.getLocalizedMessage()); + } + } finally { + try { + if(conn!=null) + conn.close(); + if(pstmt!=null) + pstmt.close(); + if(stmt!=null) + stmt.close(); + } catch (SQLException sqle) { + if (logger.isDebugEnabled()) { + logger.debug("SQL Exception closing statement/connection: " + + sqle.getLocalizedMessage()); + } + } + } + } + + private String getDefaultAdminRole(String tenantId) { + return ROLE_PREFIX+tenantId+TENANT_ADMIN_ROLE_SUFFIX; + } + + private String getDefaultReaderRole(String tenantId) { + return ROLE_PREFIX+tenantId+TENANT_READER_ROLE_SUFFIX; + } + + private String getDefaultAdminUserID(String tenantName) { + return TENANT_ADMIN_ACCT_PREFIX+tenantName; + } + + private String getDefaultReaderUserID(String tenantName) { + return TENANT_READER_ACCT_PREFIX+tenantName; + } + + private Connection getConnection() throws LoginException, SQLException { + InitialContext ctx = null; + Connection conn = null; + try { + ctx = new InitialContext(); + DataSource ds = (DataSource) ctx.lookup(repositoryName); + if (ds == null) { + throw new IllegalArgumentException("datasource not found: " + repositoryName); + } + conn = ds.getConnection(); + return conn; + } catch (NamingException ex) { + LoginException le = new LoginException("Error looking up DataSource from: " + repositoryName); + le.initCause(ex); + throw le; + } finally { + if (ctx != null) { + try { + ctx.close(); + } catch (Exception e) { + } + } + } + } + + void retrieveAllWorkspaceIds() throws Exception { //all configs are read, connector is initialized, retrieve workspaceids Hashtable tenantBindings = - getTenantBindingConfigReader().getTenantBindings(); + tenantBindingConfigReader.getTenantBindings(); TenantRepository.get().setup(tenantBindings); } diff --git a/services/security/client/pom.xml b/services/security/client/pom.xml index 46dc2a07a..d777b2a0a 100644 --- a/services/security/client/pom.xml +++ b/services/security/client/pom.xml @@ -52,6 +52,11 @@ org.collectionspace.services.dimension.client ${project.version} + + org.collectionspace.services + org.collectionspace.services.intake.client + ${project.version} + org.testng diff --git a/services/security/client/src/test/java/org/collectionspace/services/security/client/test/AuthorizationServiceTest.java b/services/security/client/src/test/java/org/collectionspace/services/security/client/test/AuthorizationServiceTest.java index d5feb9ede..93956036a 100644 --- a/services/security/client/src/test/java/org/collectionspace/services/security/client/test/AuthorizationServiceTest.java +++ b/services/security/client/src/test/java/org/collectionspace/services/security/client/test/AuthorizationServiceTest.java @@ -53,6 +53,7 @@ import org.collectionspace.services.client.AccountRoleFactory; import org.collectionspace.services.client.CollectionSpaceClient; import org.collectionspace.services.client.DimensionClient; import org.collectionspace.services.client.DimensionFactory; +import org.collectionspace.services.client.IntakeClient; import org.collectionspace.services.client.PermissionClient; import org.collectionspace.services.client.PermissionFactory; import org.collectionspace.services.client.PermissionRoleClient; @@ -61,6 +62,7 @@ import org.collectionspace.services.client.RoleClient; import org.collectionspace.services.client.RoleFactory; import org.collectionspace.services.client.test.AbstractServiceTestImpl; import org.collectionspace.services.dimension.DimensionsCommon; +import org.collectionspace.services.intake.IntakesCommon; import org.collectionspace.services.jaxb.AbstractCommonList; import org.jboss.resteasy.client.ClientResponse; import org.jboss.resteasy.plugins.providers.multipart.MultipartInput; @@ -102,6 +104,7 @@ public class AuthorizationServiceTest extends AbstractServiceTestImpl { private String bigbirdPermId; private String elmoPermId; private final static String TEST_SERVICE_NAME = "dimensions"; + private boolean accountRolesFlipped = false; /* * This method is called only by the parent class, AbstractServiceTestImpl */ @@ -169,21 +172,29 @@ public class AuthorizationServiceTest extends AbstractServiceTestImpl { } private void seedAccounts() { - String userId = "bigbird2010"; - String accId = createAccount(userId, "bigbird@cspace.org"); - AccountValue ava = new AccountValue(); - ava.setScreenName(userId); - ava.setUserId(userId); - ava.setAccountId(accId); - accValues.put(ava.getUserId(), ava); + String userId1 = "bigbird2010"; + String accId1 = createAccount(userId1, "bigbird@cspace.org"); + AccountValue av1 = new AccountValue(); + av1.setScreenName(userId1); + av1.setUserId(userId1); + av1.setAccountId(accId1); + accValues.put(av1.getUserId(), av1); String userId2 = "elmo2010"; - String coAccId = createAccount(userId2, "elmo@cspace.org"); - AccountValue avc = new AccountValue(); - avc.setScreenName(userId2); - avc.setUserId(userId2); - avc.setAccountId(coAccId); - accValues.put(avc.getUserId(), avc); + String accId2 = createAccount(userId2, "elmo@cspace.org"); + AccountValue av2 = new AccountValue(); + av2.setScreenName(userId2); + av2.setUserId(userId2); + av2.setAccountId(accId2); + accValues.put(av2.getUserId(), av2); + + String userId3 = "lockedOut"; + String accId3 = createAccount(userId3, "lockedOut@cspace.org"); + AccountValue av3 = new AccountValue(); + av3.setScreenName(userId3); + av3.setUserId(userId3); + av3.setAccountId(accId3); + accValues.put(av3.getUserId(), av3); } private void seedAccountRoles() { @@ -271,6 +282,118 @@ public class AuthorizationServiceTest extends AbstractServiceTestImpl { if (logger.isDebugEnabled()) { logger.debug(testName + ": knownResourceId=" + knownResourceId); } + + // Now verify that elmo cannot create + client = new DimensionClient(); + client.setAuth(true, "elmo2010", true, "elmo2010", true); + res = client.create(multipart); + + statusCode = res.getStatus(); + if (logger.isDebugEnabled()) { + logger.debug(testName + " (verify not allowed): status = " + statusCode); + } + Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode), + invalidStatusCodeMessage(REQUEST_TYPE, statusCode)); + Assert.assertEquals(statusCode, Response.Status.FORBIDDEN.getStatusCode()); + + //Finally, verify that elmo has no access to Intakes + // Submit the request to the service and store the response. + IntakeClient iclient = new IntakeClient(); + iclient.setAuth(true, "elmo2010", true, "elmo2010", true); + multipart = createIntakeInstance( + "entryNumber-" + identifier, + "entryDate-" + identifier, + "depositor-" + identifier); + res = iclient.create(multipart); + if (logger.isDebugEnabled()) { + logger.debug(testName + " (verify create intake not allowed): status = " + statusCode); + } + Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode), + invalidStatusCodeMessage(REQUEST_TYPE, statusCode)); + Assert.assertEquals(statusCode, Response.Status.FORBIDDEN.getStatusCode()); + + } + + /** + * Creates the intake instance. + * + * @param entryNumber the entry number + * @param entryDate the entry date + * @param depositor the depositor + * @return the multipart output + */ + private MultipartOutput createIntakeInstance(String entryNumber, + String entryDate, + String depositor) { + IntakesCommon intake = new IntakesCommon(); + intake.setEntryNumber(entryNumber); + intake.setEntryDate(entryDate); + intake.setDepositor(depositor); + + MultipartOutput multipart = new MultipartOutput(); + OutputPart commonPart = + multipart.addPart(intake, MediaType.APPLICATION_XML_TYPE); + commonPart.getHeaders().add("label", new IntakeClient().getCommonPartName()); + + if(logger.isDebugEnabled()){ + logger.debug("to be created, intake common"); + logger.debug(objectAsXmlString(intake, IntakesCommon.class)); + } + + return multipart; + } + + + @Test(dataProvider = "testName", dataProviderClass = AbstractServiceTestImpl.class, + dependsOnMethods = {"delete"}) + public void verifyCreateWithFlippedRoles(String testName) throws Exception { + if (logger.isDebugEnabled()) { + logger.debug(testBanner(testName, CLASS_NAME)); + } + setupCreate(); + + // Submit the request to the service and store the response. + DimensionClient client = new DimensionClient(); + flipInitialAccountRoles(); + + // Now verify that elmo can create + client.setAuth(true, "elmo2010", true, "elmo2010", true); + client = new DimensionClient(); + + String identifier = createIdentifier(); + DimensionsCommon dimension = new DimensionsCommon(); + dimension.setDimension("dimensionType"); + dimension.setValue("value-" + identifier); + dimension.setValueDate(new Date().toString()); + MultipartOutput multipart = DimensionFactory.createDimensionInstance(client.getCommonPartName(), + dimension); + ClientResponse res = client.create(multipart); + + int statusCode = res.getStatus(); + + if (logger.isDebugEnabled()) { + logger.debug(testName + ": status = " + statusCode); + } + Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode), + invalidStatusCodeMessage(REQUEST_TYPE, statusCode)); + Assert.assertEquals(statusCode, Response.Status.CREATED.getStatusCode()); + knownResourceId = extractId(res); + if (logger.isDebugEnabled()) { + logger.debug(testName + ": knownResourceId=" + knownResourceId); + } + + //bigbird no longer allowed to create + client.setAuth(true, "bigbird2010", true, "bigbird2010", true); + res = client.create(multipart); + + statusCode = res.getStatus(); + if (logger.isDebugEnabled()) { + logger.debug(testName + " (verify not allowed): status = " + statusCode); + } + Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode), + invalidStatusCodeMessage(REQUEST_TYPE, statusCode)); + Assert.assertEquals(statusCode, Response.Status.FORBIDDEN.getStatusCode()); + restoreInitialAccountRoles(); } //to not cause uniqueness violation for permRole, createList is removed @@ -330,7 +453,33 @@ public class AuthorizationServiceTest extends AbstractServiceTestImpl { DimensionsCommon dimension = (DimensionsCommon) extractPart(input, client.getCommonPartName(), DimensionsCommon.class); Assert.assertNotNull(dimension); + } + + @Test(dataProvider = "testName", dataProviderClass = AbstractServiceTestImpl.class, + dependsOnMethods = {"read"}) + public void readLockedOut(String testName) throws Exception { + + if (logger.isDebugEnabled()) { + logger.debug(testBanner(testName, CLASS_NAME)); + } + // Perform setup. + setupRead(); + + // Submit the request to the service and store the response. + DimensionClient client = new DimensionClient(); + //lockedOut allowed to read + client.setAuth(true, "lockedOut", true, "lockedOut", true); + ClientResponse res = client.read(knownResourceId); + int statusCode = res.getStatus(); + // Check the status code of the response: does it match + // the expected response(s)? + if (logger.isDebugEnabled()) { + logger.debug(testName + " (test lockedOut): status = " + statusCode); + } + Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode), + invalidStatusCodeMessage(REQUEST_TYPE, statusCode)); + Assert.assertEquals(statusCode, Response.Status.FORBIDDEN.getStatusCode()); } // Failure outcomes @@ -400,6 +549,21 @@ public class AuthorizationServiceTest extends AbstractServiceTestImpl { Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode), invalidStatusCodeMessage(REQUEST_TYPE, statusCode)); Assert.assertEquals(statusCode, Response.Status.FORBIDDEN.getStatusCode()); + + client = new DimensionClient(); + + //lockedOut not allowed to update + client.setAuth(true, "lockedOut", true, "lockedOut", true); + res = client.update(knownResourceId, output); + statusCode = res.getStatus(); + // Check the status code of the response: does it match the expected response(s)? + if (logger.isDebugEnabled()) { + logger.debug(testName + ": (lockedOut) status = " + statusCode); + } + Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode), + invalidStatusCodeMessage(REQUEST_TYPE, statusCode)); + Assert.assertEquals(statusCode, Response.Status.FORBIDDEN.getStatusCode()); + } // Failure outcomes @@ -524,7 +688,7 @@ public class AuthorizationServiceTest extends AbstractServiceTestImpl { //FIXME delete on permission deletes all associations with roles //this would delete association with ROLE_ADMINISTRATOR too //deletePermissions(); - deleteRoles(); + deleteRoles(); deleteAccounts(); } @@ -543,12 +707,52 @@ public class AuthorizationServiceTest extends AbstractServiceTestImpl { private void deleteAccountRoles() { List bigbirdRoleValues = new ArrayList(); bigbirdRoleValues.add(roleValues.get("ROLE_TEST_CM")); - deleteAccountRole(accValues.get("bigbird2010"), bigbirdRoleValues); List elmoRoleValues = new ArrayList(); elmoRoleValues.add(roleValues.get("ROLE_TEST_INTERN")); - deleteAccountRole(accValues.get("elmo2010"), elmoRoleValues); + if(!accountRolesFlipped) { + deleteAccountRole(accValues.get("bigbird2010"), bigbirdRoleValues); + deleteAccountRole(accValues.get("elmo2010"), elmoRoleValues); + } else { + deleteAccountRole(accValues.get("bigbird2010"), elmoRoleValues); + deleteAccountRole(accValues.get("elmo2010"),bigbirdRoleValues ); + } } + + private void flipInitialAccountRoles() { + if(!accountRolesFlipped) { + List cmRoleValues = new ArrayList(); + List internRoleValues = new ArrayList(); + cmRoleValues.add(roleValues.get("ROLE_TEST_CM")); + internRoleValues.add(roleValues.get("ROLE_TEST_INTERN")); + + deleteAccountRole(accValues.get("bigbird2010"), cmRoleValues); + deleteAccountRole(accValues.get("elmo2010"), internRoleValues); + + createAccountRole(accValues.get("bigbird2010"), internRoleValues); + createAccountRole(accValues.get("elmo2010"), cmRoleValues); + + accountRolesFlipped = true; + } + } + + private void restoreInitialAccountRoles() { + if(accountRolesFlipped) { + List cmRoleValues = new ArrayList(); + List internRoleValues = new ArrayList(); + cmRoleValues.add(roleValues.get("ROLE_TEST_CM")); + internRoleValues.add(roleValues.get("ROLE_TEST_INTERN")); + + deleteAccountRole(accValues.get("bigbird2010"), internRoleValues); + deleteAccountRole(accValues.get("elmo2010"), cmRoleValues); + + createAccountRole(accValues.get("bigbird2010"), internRoleValues); + createAccountRole(accValues.get("elmo2010"), cmRoleValues); + accountRolesFlipped = false; + } + } + + private void deletePermissions() { //delete entities diff --git a/services/security/client/src/test/java/org/collectionspace/services/security/client/test/MultiTenancyTest.java b/services/security/client/src/test/java/org/collectionspace/services/security/client/test/MultiTenancyTest.java index 5eef253b6..fdb9ffcc9 100644 --- a/services/security/client/src/test/java/org/collectionspace/services/security/client/test/MultiTenancyTest.java +++ b/services/security/client/src/test/java/org/collectionspace/services/security/client/test/MultiTenancyTest.java @@ -90,8 +90,9 @@ public class MultiTenancyTest extends AbstractServiceTestImpl { private final String CLASS_NAME = MultiTenancyTest.class.getName(); private final Logger logger = LoggerFactory.getLogger(CLASS_NAME); - private final static String TENANT_1_ADMIN_USER = "test"; - private final static String TENANT_2_ADMIN_USER = "test-pahma"; + private final static String TENANT_1_ADMIN_USER = "admin@collectionspace.org"; + private final static String TENANT_2_ADMIN_USER = "admin@hearstmuseum.berkeley.edu"; + private final static String TENANT_ADMIN_PASS = "Administrator"; private final static String TENANT_1_USER = "user1@museum1.org"; private final static String TENANT_2_USER = "user2@museum2.org"; private final static String TENANT_1 = "1"; @@ -143,8 +144,8 @@ public class MultiTenancyTest extends AbstractServiceTestImpl { //tenant admin users are used to create accounts, roles and permissions and relationships //assumption : two tenant admin users exist before running this test - tenantAdminUsers.put(TENANT_1, new UserInfo(TENANT_1_ADMIN_USER, "test")); - tenantAdminUsers.put(TENANT_2, new UserInfo(TENANT_2_ADMIN_USER, "test")); + tenantAdminUsers.put(TENANT_1, new UserInfo(TENANT_1_ADMIN_USER, TENANT_ADMIN_PASS)); + tenantAdminUsers.put(TENANT_2, new UserInfo(TENANT_2_ADMIN_USER, TENANT_ADMIN_PASS)); seedAccounts(); seedPermissions(); -- 2.47.3