]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
3ba8a36dc95ce67dcf58e44d8d64637537f4b187
[tmp/jakarta-migration.git] /
1 package org.collectionspace.services.common.authorization_mgt;
2
3 import java.security.NoSuchAlgorithmException;
4 import java.sql.Connection;
5 import java.sql.PreparedStatement;
6 import java.sql.ResultSet;
7 import java.sql.SQLException;
8 import java.sql.Statement;
9 import java.util.ArrayList;
10 import java.util.Date;
11 import java.util.HashMap;
12 import java.util.HashSet;
13 import java.util.Hashtable;
14 import java.util.List;
15 import java.util.Map;
16 import java.util.UUID;
17
18 import javax.naming.NamingException;
19 import javax.persistence.EntityManager;
20 import javax.persistence.EntityManagerFactory;
21
22 import org.collectionspace.authentication.AuthN;
23 import org.collectionspace.services.account.AccountListItem;
24
25 import org.collectionspace.services.authentication.Token;
26 import org.collectionspace.services.authorization.AuthZ;
27 import org.collectionspace.services.authorization.CSpaceAction;
28 import org.collectionspace.services.authorization.CSpaceResource;
29 import org.collectionspace.services.authorization.PermissionException;
30 import org.collectionspace.services.authorization.PermissionRole;
31 import org.collectionspace.services.authorization.PermissionRoleRel;
32 import org.collectionspace.services.authorization.PermissionValue;
33 import org.collectionspace.services.authorization.Role;
34 import org.collectionspace.services.authorization.RoleValue;
35 import org.collectionspace.services.authorization.SubjectType;
36 import org.collectionspace.services.authorization.URIResourceImpl;
37 import org.collectionspace.services.authorization.perms.ActionType;
38 import org.collectionspace.services.authorization.perms.EffectType;
39 import org.collectionspace.services.authorization.perms.Permission;
40 import org.collectionspace.services.authorization.perms.PermissionAction;
41
42 import org.collectionspace.services.client.Profiler;
43 import org.collectionspace.services.client.RoleClient;
44 import org.collectionspace.services.client.workflow.WorkflowClient;
45
46 import org.collectionspace.services.common.config.ServiceConfigUtils;
47 import org.collectionspace.services.common.config.TenantBindingConfigReaderImpl;
48 import org.collectionspace.services.common.context.ServiceBindingUtils;
49 import org.collectionspace.services.common.document.DocumentHandler;
50 import org.collectionspace.services.common.security.SecurityUtils;
51 import org.collectionspace.services.common.storage.DatabaseProductType;
52 import org.collectionspace.services.common.storage.JDBCTools;
53 import org.collectionspace.services.common.storage.jpa.JPATransactionContext;
54 import org.collectionspace.services.common.storage.jpa.JpaStorageUtils;
55
56 import org.collectionspace.services.config.service.ServiceBindingType;
57 import org.collectionspace.services.config.tenant.EmailConfig;
58 import org.collectionspace.services.config.tenant.PasswordResetConfig;
59 import org.collectionspace.services.config.tenant.TenantBindingType;
60
61 import org.collectionspace.services.lifecycle.Lifecycle;
62 import org.collectionspace.services.lifecycle.TransitionDef;
63 import org.collectionspace.services.lifecycle.TransitionDefList;
64
65 //import org.mortbay.log.Log;
66 import org.slf4j.Logger;
67 import org.slf4j.LoggerFactory;
68 import org.springframework.security.acls.model.AlreadyExistsException;
69
70
71 public class AuthorizationCommon {
72         
73         final public static String REFRESH_AUTHZ_PROP = "refreshAuthZOnStartup";
74         
75         //
76         // For token generation and password reset
77         //
78         final private static String DEFAULT_PASSWORD_RESET_EMAIL_MESSAGE = "Hello {{greeting}},\n\r\n\rYou've started the process to reset your CollectionSpace account password. To finish resetting your password, go to the Reset Password page {{link}} on CollectionSpace.\n\r\n\rIf clicking the link doesn't work, copy and paste the following link into your browser address bar and click Go.\n\r\n\r{{link}}\n\r Thanks,\n\r\n\r CollectionSpace Administrator\n\r\n\rPlease do not reply to this email. This mailbox is not monitored and you will not receive a response. For assistance, contact your CollectionSpace Administrator directly.";
79         final private static String tokensalt = "74102328UserDetailsReset";
80         final private static int TIME_SCALAR = 100000;
81         private static final String DEFAULT_PASSWORD_RESET_EMAIL_SUBJECT = "Password reset for CollectionSpace account";
82         
83         //
84         // Keep track of the MD5 hash value for the tenant bindings
85         //
86         private static final Map<String, String> tenantConfigMD5HashTable = new HashMap<String, String>();
87
88     //
89     // ActionGroup labels/constants
90     //
91         
92         // for READ-WRITE
93     final public static String ACTIONGROUP_CRUDL_NAME = "CRUDL";
94     final public static ActionType[] ACTIONSET_CRUDL = {ActionType.CREATE, ActionType.READ, ActionType.UPDATE, ActionType.DELETE, ActionType.SEARCH};
95     // for READ-ONLY
96     final public static String ACTIONGROUP_RL_NAME = "RL";
97     final public static ActionType[] ACTIONSET_RL = {ActionType.READ, ActionType.SEARCH};
98         
99         static ActionGroup ACTIONGROUP_CRUDL;
100         static ActionGroup ACTIONGROUP_RL;
101         
102         // A static block to initialize the predefined action groups
103         static {
104                 // For admin
105                 ACTIONGROUP_CRUDL = new ActionGroup();
106                 ACTIONGROUP_CRUDL.name = ACTIONGROUP_CRUDL_NAME;
107                 ACTIONGROUP_CRUDL.actions = ACTIONSET_CRUDL;
108                 // For reader
109                 ACTIONGROUP_RL = new ActionGroup();
110                 ACTIONGROUP_RL.name = ACTIONGROUP_RL_NAME;
111                 ACTIONGROUP_RL.actions = ACTIONSET_RL;
112
113         }
114         
115     final static Logger logger = LoggerFactory.getLogger(AuthorizationCommon.class);
116
117     final public static String ROLE_TENANT_ADMINISTRATOR = "TENANT_ADMINISTRATOR";
118     final public static String ROLE_TENANT_READER = "TENANT_READER";
119         
120     public static final String TENANT_MANAGER_USER = "tenantManager"; 
121     public static final String TENANT_MANAGER_SCREEN_NAME = TENANT_MANAGER_USER; 
122     public static final String DEFAULT_TENANT_MANAGER_PASSWORD = "manage"; 
123     public static final String DEFAULT_TENANT_MANAGER_EMAIL = "tenantManager@collectionspace.org"; 
124     
125     public static final String TENANT_ADMIN_ACCT_PREFIX = "admin@"; 
126     public static final String TENANT_READER_ACCT_PREFIX = "reader@"; 
127     public static final String ROLE_PREFIX = "ROLE_"; 
128     public static final String TENANT_ADMIN_ROLE_SUFFIX = "_TENANT_ADMINISTRATOR"; 
129     public static final String TENANT_READER_ROLE_SUFFIX = "_TENANT_READER"; 
130     public static final String DEFAULT_ADMIN_PASSWORD = "Administrator";
131     public static final String DEFAULT_READER_PASSWORD = "reader";
132     
133     // SQL for init tasks
134         final private static String INSERT_ACCOUNT_ROLE_SQL_MYSQL = 
135                         "INSERT INTO accounts_roles(account_id, user_id, role_id, role_name, created_at)"
136                                         +" VALUES(?, ?, ?, ?, now())";
137         final private static String INSERT_ACCOUNT_ROLE_SQL_POSTGRES =
138                         "INSERT INTO accounts_roles(HJID, account_id, user_id, role_id, role_name, created_at)"
139                                         +" VALUES(nextval('hibernate_sequence'), ?, ?, ?, ?, now())";
140         final private static String QUERY_USERS_SQL = 
141                 "SELECT username FROM users WHERE username LIKE '"
142                         +TENANT_ADMIN_ACCT_PREFIX+"%' OR username LIKE '"+TENANT_READER_ACCT_PREFIX+"%'";
143         final private static String INSERT_USER_SQL = 
144                         "INSERT INTO users (username,passwd, created_at) VALUES (?,?, now())";
145         final private static String INSERT_ACCOUNT_SQL = 
146                         "INSERT INTO accounts_common "
147                                         + "(csid, email, userid, status, screen_name, metadata_protection, roles_protection, created_at) "
148                                         + "VALUES (?,?,?,'ACTIVE',?, 'immutable', 'immutable', now())";
149         
150         // TENANT MANAGER specific SQL
151         final private static String QUERY_TENANT_MGR_USER_SQL = 
152                 "SELECT username FROM users WHERE username = '"+TENANT_MANAGER_USER+"'";
153         final private static String GET_TENANT_MGR_ROLE_SQL =
154                         "SELECT csid from roles WHERE tenant_id='" + AuthN.ALL_TENANTS_MANAGER_TENANT_ID + "' and rolename=?";
155
156         public static final String IGNORE_TENANT_ID = null; // A null constant to indicate an empty/unused value for the tenant ID
157
158
159         public static String getTenantConfigMD5Hash(String tenantId) {
160                 return tenantConfigMD5HashTable.get(tenantId);
161         }
162         
163         public static String setTenantConfigMD5Hash(String tenantId, String md5hash) {
164                 return tenantConfigMD5HashTable.put(tenantId, md5hash);
165         }       
166         
167         @Deprecated
168     public static Role xgetRole(String tenantId, String displayName) {
169         Role role = null;
170         
171         String roleName = AuthorizationCommon.getQualifiedRoleName(tenantId, displayName);
172         //role = AuthorizationStore.getRoleByName(roleName, tenantId);
173         
174         return role;
175     }
176     
177     public static Role getRole(JPATransactionContext jpaTransactionContext, String tenantId, String displayName) {
178         Role role = null;
179         
180         String roleName = AuthorizationCommon.getQualifiedRoleName(tenantId, displayName);
181         role = AuthorizationStore.getRoleByName(jpaTransactionContext, roleName, tenantId);
182         
183         return role;
184     }
185     
186     
187     public static Role createRole(String tenantId, String name, String description) {
188         return createRole(tenantId, name, description, false /* mutable by default */);
189     }
190     
191     public static Role createRole(String tenantId, String name, String description, boolean immutable) {
192         Role role = new Role();
193         
194         role.setCreatedAtItem(new Date());
195         role.setDisplayName(name);
196         String roleName = AuthorizationCommon.getQualifiedRoleName(tenantId, name);     
197         role.setRoleName(roleName);
198         String id = UUID.randomUUID().toString(); //FIXME: The qualified role name should be unique enough to use as an ID/key
199         role.setCsid(id);
200                 role.setDescription(description);
201         role.setTenantId(tenantId);
202         if (immutable == true) {
203                 role.setMetadataProtection(RoleClient.IMMUTABLE);
204                 role.setPermsProtection(RoleClient.IMMUTABLE);
205         }
206         
207         return role;
208     }
209     
210     /**
211      * Add permission to the Spring Security tables
212      * with assumption that resource is of type URI
213      * @param permission configuration
214      */
215     public static void addPermissionsForUri(Permission perm,
216             PermissionRole permRole) throws PermissionException {
217         //
218         // First check the integrity of the incoming arguments.
219         //
220         if (!perm.getCsid().equals(permRole.getPermission().get(0).getPermissionId())) {
221             throw new IllegalArgumentException("permission ids do not"
222                     + " match for role=" + permRole.getRole().get(0).getRoleName()
223                     + " with permissionId=" + permRole.getPermission().get(0).getPermissionId()
224                     + " for permission with csid=" + perm.getCsid());
225         }
226         
227         List<String> principals = new ArrayList<String>();        
228         for (RoleValue roleValue : permRole.getRole()) {
229             principals.add(roleValue.getRoleName());
230         }
231         
232         boolean grant = perm.getEffect().equals(EffectType.PERMIT) ? true : false;
233         List<PermissionAction> permActions = perm.getAction();
234         ArrayList<CSpaceResource> resources = new ArrayList<CSpaceResource>();
235         for (PermissionAction permAction : permActions) {
236             CSpaceAction action = URIResourceImpl.getAction(permAction.getName()); 
237             URIResourceImpl uriRes = new URIResourceImpl(perm.getTenantId(), perm.getResourceName(), action);
238             resources.add(uriRes);
239         }
240         AuthZ.get().addPermissions(resources.toArray(new CSpaceResource[0]), principals.toArray(new String[0]), grant); // CSPACE-4967
241     }
242     
243     private static Connection getConnection(String databaseName) throws NamingException, SQLException {
244         return JDBCTools.getConnection(JDBCTools.CSPACE_DATASOURCE_NAME,
245                         databaseName);
246     }
247     
248     /*
249      * Spring security seems to require that all of our role names start
250      * with the ROLE_PREFIX string.
251      */
252     public static String getQualifiedRoleName(String tenantId, String name) {
253         String result = name;
254         
255         String qualifiedName = ROLE_PREFIX + tenantId.toUpperCase() + "_" + name.toUpperCase();         
256         if (name.equals(qualifiedName) == false) {
257                 result = qualifiedName;
258         }
259         
260         return result;
261     }
262         
263     private static ActionGroup getActionGroup(String actionGroupStr) {
264         ActionGroup result = null;
265         
266         if (actionGroupStr.equalsIgnoreCase(ACTIONGROUP_CRUDL_NAME)) {
267                 result = ACTIONGROUP_CRUDL;
268         } else if (actionGroupStr.equalsIgnoreCase(ACTIONGROUP_RL_NAME)) {
269                 result = ACTIONGROUP_RL;
270         }
271         
272         return result;
273     }
274     
275     public static Permission createPermission(String tenantId,
276                 String resourceName,
277                 String description,
278                 String actionGroupStr) {
279         Permission result = null;
280         
281         ActionGroup actionGroup = getActionGroup(actionGroupStr);
282         result = createPermission(tenantId, resourceName, description, actionGroup);
283         
284         return result;
285     }
286     
287     private static Permission createPermission(String tenantId,
288                 String resourceName,
289                 String description,
290                 ActionGroup actionGroup) {
291         String id = tenantId
292                         + "-" + resourceName.replace('/', '_') // Remove the slashes so the ID can be used in a URI/URL
293                         + "-" + actionGroup.name;
294         Permission perm = new Permission();
295         perm.setCsid(id);
296         perm.setDescription(description);
297         perm.setCreatedAtItem(new Date());
298         perm.setResourceName(resourceName.toLowerCase().trim());
299         perm.setEffect(EffectType.PERMIT);
300         perm.setTenantId(tenantId);
301         
302         perm.setActionGroup(actionGroup.name);
303         ArrayList<PermissionAction> pas = new ArrayList<PermissionAction>();
304         perm.setAction(pas);
305         for (ActionType actionType : actionGroup.actions) {
306                 PermissionAction permAction = createPermissionAction(perm, actionType);
307                 pas.add(permAction);
308         }
309         
310         return perm;
311     }
312     
313     private static Permission createWorkflowPermission(TenantBindingType tenantBinding,
314                 ServiceBindingType serviceBinding,
315                 String transitionVerb,
316                 ActionGroup actionGroup)
317     {
318         Permission result = null;
319         String workFlowServiceSuffix;
320         String transitionName;
321         if (transitionVerb != null) {
322                 transitionName = transitionVerb;
323                 workFlowServiceSuffix = WorkflowClient.SERVICE_AUTHZ_SUFFIX;
324         } else {
325                 transitionName = ""; //since the transitionDef was null, we're assuming that this is the base workflow permission to be created                 
326                 workFlowServiceSuffix = WorkflowClient.SERVICE_PATH;
327         }
328         
329         String tenantId = tenantBinding.getId();
330         String resourceName = "/"
331                         + serviceBinding.getName().toLowerCase().trim()
332                         + workFlowServiceSuffix
333                         + transitionName;
334         String description = "A generated workflow permission for actiongroup " + actionGroup.name;
335         result = createPermission(tenantId, resourceName, description, actionGroup);
336         
337         if (logger.isDebugEnabled() == true) {
338                 logger.debug("Generated a workflow permission: "
339                                 + result.getResourceName()
340                                 + ":" + transitionName
341                                 + ":" + "tenant id=" + result.getTenantId()
342                                 + ":" + actionGroup.name);
343         }
344         
345         return result;
346     }
347     
348     private static PermissionRole createPermissionRole(
349                 Permission permission,
350                 Role role,
351                 boolean enforceTenancy) throws Exception
352     {
353         PermissionRole permRole = new PermissionRole();
354         // Check to see if the tenant ID of the permission and the tenant ID of the role match
355         boolean tenantIdsMatch = role.getTenantId().equalsIgnoreCase(permission.getTenantId());
356         if (tenantIdsMatch == false && enforceTenancy == false) {
357                 tenantIdsMatch = true; // If we don't need to enforce tenancy then we'll just consider them matched.
358         }
359                         
360                 if (tenantIdsMatch == true) {
361                 permRole.setSubject(SubjectType.ROLE);
362                 //
363                 // Set of the permission value list of the permrole
364                 //
365                 List<PermissionValue> permValues = new ArrayList<PermissionValue>();
366                 PermissionValue permValue = new PermissionValue();
367                 permValue.setPermissionId(permission.getCsid());
368                 permValue.setResourceName(permission.getResourceName().toLowerCase());
369                 permValue.setActionGroup(permission.getActionGroup());
370                 permValues.add(permValue);
371                 permRole.setPermission(permValues);
372                 //
373                 // Set of the role value list of the permrole
374                 //
375                 List<RoleValue> roleValues = new ArrayList<RoleValue>();
376                 RoleValue rv = new RoleValue();
377             // This needs to use the qualified name, not the display name
378             rv.setRoleName(role.getRoleName());
379             rv.setRoleId(role.getCsid());
380             roleValues.add(rv);
381             permRole.setRole(roleValues);
382                 } else {
383                 String errMsg = "The tenant ID of the role: " + role.getTenantId()
384                                 + " did not match the tenant ID of the permission: " + permission.getTenantId();
385                 throw new Exception(errMsg);
386                 }
387         
388         return permRole;
389     }
390     
391     private static Hashtable<String, String> getTenantNamesFromConfig(TenantBindingConfigReaderImpl tenantBindingConfigReader) {
392
393         // Note that this only handles tenants not marked as "createDisabled"
394         Hashtable<String, TenantBindingType> tenantBindings =
395                         tenantBindingConfigReader.getTenantBindings();
396         Hashtable<String, String> tenantInfo = new Hashtable<String, String>();
397         for (TenantBindingType tenantBinding : tenantBindings.values()) {
398                 String tId = tenantBinding.getId();
399                 String tName = tenantBinding.getName();
400                 tenantInfo.put(tId, tName);
401                 if (logger.isDebugEnabled()) {
402                         logger.debug("getTenantNamesFromConfig found configured tenant id: "+tId+" name: "+tName);
403                 }
404         }
405         return tenantInfo;
406     }
407     
408     private static ArrayList<String> compileExistingTenants(Connection conn, Hashtable<String, String> tenantInfo)
409         throws SQLException, Exception {
410         Statement stmt = null;
411         ArrayList<String> existingTenants = new ArrayList<String>();
412         // First find or create the tenants
413         final String queryTenantSQL = "SELECT id,name FROM tenants";
414         try {
415                 stmt = conn.createStatement();
416                 ResultSet rs = stmt.executeQuery(queryTenantSQL);
417                 while (rs.next()) {
418                         String tId = rs.getString("id");
419                         String tName = rs.getString("name");
420                         if(tenantInfo.containsKey(tId)) {
421                                 existingTenants.add(tId);
422                                 if(!tenantInfo.get(tId).equalsIgnoreCase(tName)) {
423                                         logger.warn("Configured name for tenant: "
424                                                         +tId+" in repository: "+tName
425                                                         +" does not match config'd name: "+ tenantInfo.get(tId));
426                                 }
427                         }
428                 }
429                 rs.close();
430         } catch(Exception e) {
431                 throw e;
432         } finally {
433                 if(stmt!=null)
434                         stmt.close();
435         }
436
437         return existingTenants;
438     }
439         
440     private static ArrayList<String> findOrCreateDefaultUsers(Connection conn, Hashtable<String, String> tenantInfo) 
441                 throws SQLException, Exception {
442         // Second find or create the users
443         Statement stmt = null;
444         PreparedStatement pstmt = null;
445         ArrayList<String> usersInRepo = new ArrayList<String>();
446         try {
447                 stmt = conn.createStatement();
448                 ResultSet rs = stmt.executeQuery(QUERY_USERS_SQL);
449                 while (rs.next()) {
450                         String uName = rs.getString("username");
451                         usersInRepo.add(uName);
452                 }
453                 rs.close();
454                 pstmt = conn.prepareStatement(INSERT_USER_SQL); // create a statement
455                 for(String tName : tenantInfo.values()) {
456                         String adminAcctName = getDefaultAdminUserID(tName);
457                         if(!usersInRepo.contains(adminAcctName)) {
458                                 String secEncPasswd = SecurityUtils.createPasswordHash(
459                                                 adminAcctName, DEFAULT_ADMIN_PASSWORD);
460                                 pstmt.setString(1, adminAcctName);      // set username param
461                                 pstmt.setString(2, secEncPasswd);       // set passwd param
462                                 if (logger.isDebugEnabled()) {
463                                         logger.debug("createDefaultUsersAndAccounts adding user: "
464                                                         +adminAcctName+" for tenant: "+tName);
465                                 }
466                                 pstmt.executeUpdate();
467                         } else if (logger.isDebugEnabled()) {
468                                 logger.debug("createDefaultUsersAndAccounts: user: "+adminAcctName
469                                                 +" already exists - skipping.");
470                         }
471
472
473                         String readerAcctName =  getDefaultReaderUserID(tName);
474                         if(!usersInRepo.contains(readerAcctName)) {
475                                 String secEncPasswd = SecurityUtils.createPasswordHash(
476                                                 readerAcctName, DEFAULT_READER_PASSWORD);
477                                 pstmt.setString(1, readerAcctName);     // set username param
478                                 pstmt.setString(2, secEncPasswd);       // set passwd param
479                                 if (logger.isDebugEnabled()) {
480                                         logger.debug("createDefaultUsersAndAccounts adding user: "
481                                                         +readerAcctName+" for tenant: "+tName);
482                                 }
483                                 pstmt.executeUpdate();
484                         } else if (logger.isDebugEnabled()) {
485                                 logger.debug("createDefaultUsersAndAccounts: user: "+readerAcctName
486                                                 +" already exists - skipping.");
487                         }
488                 }
489                 pstmt.close();
490         } catch(Exception e) {
491                 throw e;
492         } finally {
493                 if(stmt!=null)
494                         stmt.close();
495                 if(pstmt!=null)
496                         pstmt.close();
497         }
498         return usersInRepo;
499     }
500     
501     private static void findOrCreateDefaultAccounts(Connection conn, Hashtable<String, String> tenantInfo,
502                 ArrayList<String> usersInRepo,
503                 Hashtable<String, String> tenantAdminAcctCSIDs, Hashtable<String, String> tenantReaderAcctCSIDs) 
504                         throws SQLException, Exception {
505         // Third, create the accounts. Assume that if the users were already there,
506         // then the accounts were as well
507         PreparedStatement pstmt = null;
508         try {
509                 pstmt = conn.prepareStatement(INSERT_ACCOUNT_SQL); // create a statement
510                 for(String tId : tenantInfo.keySet()) {
511                         String tName = tenantInfo.get(tId);
512                         String adminCSID = UUID.randomUUID().toString();
513                         tenantAdminAcctCSIDs.put(tId, adminCSID);
514                         String adminAcctName =  getDefaultAdminUserID(tName);
515                         if(!usersInRepo.contains(adminAcctName)) {
516                                 pstmt.setString(1, adminCSID);                  // set csid param
517                                 pstmt.setString(2, adminAcctName);      // set email param (bogus)
518                                 pstmt.setString(3, adminAcctName);      // set userid param
519                                 pstmt.setString(4, "Administrator");// set screen name param
520                                 if (logger.isDebugEnabled()) {
521                                         logger.debug("createDefaultAccounts adding account: "
522                                                         +adminAcctName+" for tenant: "+tName);
523                                 }
524                                 pstmt.executeUpdate();
525                         } else if (logger.isDebugEnabled()) {
526                                 logger.debug("createDefaultAccounts: user: "+adminAcctName
527                                                 +" already exists - skipping account generation.");
528                         }
529
530                         String readerCSID = UUID.randomUUID().toString();       
531                         tenantReaderAcctCSIDs.put(tId, readerCSID);
532                         String readerAcctName =  getDefaultReaderUserID(tName);
533                         if(!usersInRepo.contains(readerAcctName)) {
534                                 pstmt.setString(1, readerCSID);         // set csid param
535                                 pstmt.setString(2, readerAcctName);     // set email param (bogus)
536                                 pstmt.setString(3, readerAcctName);     // set userid param
537                                 pstmt.setString(4, "Reader");           // set screen name param
538                                 if (logger.isDebugEnabled()) {
539                                         logger.debug("createDefaultAccounts adding account: "
540                                                         +readerAcctName+" for tenant: "+tName);
541                                 }
542                                 pstmt.executeUpdate();
543                         } else if (logger.isDebugEnabled()) {
544                                 logger.debug("createDefaultAccounts: user: "+readerAcctName
545                                                 +" already exists - skipping account creation.");
546                         }
547                 }
548                 pstmt.close();
549         } catch(Exception e) {
550                 throw e;
551         } finally {
552                 if(pstmt!=null)
553                         pstmt.close();
554         }
555     }
556     
557     private static boolean findOrCreateTenantManagerUserAndAccount(Connection conn) 
558                         throws SQLException, Exception {
559         // Find or create the special tenant manager account.
560         // Later can make the user name for tenant manager be configurable, settable.
561         Statement stmt = null;
562         PreparedStatement pstmt = null;
563         boolean created = false;
564         try {
565                 boolean foundTMgrUser = false;
566                 stmt = conn.createStatement();
567                 ResultSet rs = stmt.executeQuery(QUERY_TENANT_MGR_USER_SQL);
568                 // Should only find one - only consider it
569                 if(rs.next()) {
570                         String uName = rs.getString("username");
571                         foundTMgrUser = uName.equals(TENANT_MANAGER_USER);
572                 }
573                 rs.close();
574                 if(!foundTMgrUser) {
575                         pstmt = conn.prepareStatement(INSERT_USER_SQL); // create a statement
576                         String secEncPasswd = SecurityUtils.createPasswordHash(
577                                         TENANT_MANAGER_USER, DEFAULT_TENANT_MANAGER_PASSWORD);
578                         pstmt.setString(1, TENANT_MANAGER_USER);        // set username param
579                         pstmt.setString(2, secEncPasswd);       // set passwd param
580                         if (logger.isDebugEnabled()) {
581                                 logger.debug("findOrCreateTenantManagerUserAndAccount adding tenant manager user: "
582                                                 +TENANT_MANAGER_USER);
583                         }
584                         pstmt.executeUpdate();
585                 pstmt.close();
586                 // Now create the account to match
587                         pstmt = conn.prepareStatement(INSERT_ACCOUNT_SQL); // create a statement
588                                 pstmt.setString(1, AuthN.TENANT_MANAGER_ACCT_ID);                // set csid param
589                                 pstmt.setString(2, DEFAULT_TENANT_MANAGER_EMAIL);       // set email param (bogus)
590                                 pstmt.setString(3, TENANT_MANAGER_USER);        // set userid param
591                                 pstmt.setString(4, TENANT_MANAGER_SCREEN_NAME);// set screen name param
592                                 if (logger.isDebugEnabled()) {
593                                         logger.debug("findOrCreateTenantManagerUserAndAccount adding tenant manager account: "
594                                                         +TENANT_MANAGER_USER);
595                                 }
596                                 pstmt.executeUpdate();
597                         pstmt.close();
598                         created = true;
599                 } else if (logger.isDebugEnabled()) {
600                         logger.debug("findOrCreateTenantManagerUserAndAccount: tenant manager: "+TENANT_MANAGER_USER
601                                         +" already exists.");
602                 }
603         } catch(Exception e) {
604                 throw e;
605         } finally {
606                 if(stmt!=null)
607                         stmt.close();
608                 if(pstmt!=null)
609                         pstmt.close();
610         }
611         return created;
612     }
613     
614     private static void bindDefaultAccountsToTenants(Connection conn, DatabaseProductType databaseProductType,
615                 Hashtable<String, String> tenantInfo, ArrayList<String> usersInRepo,
616                 Hashtable<String, String> tenantAdminAcctCSIDs, Hashtable<String, String> tenantReaderAcctCSIDs) 
617                         throws SQLException, Exception {
618         // Fourth, bind accounts to tenants. Assume that if the users were already there,
619         // then the accounts were bound to tenants correctly
620         PreparedStatement pstmt = null;
621         try {
622                 String insertAccountTenantSQL;
623                 if (databaseProductType == DatabaseProductType.MYSQL) {
624                         insertAccountTenantSQL =
625                                         "INSERT INTO accounts_tenants (TENANTS_ACCOUNTS_COMMON_CSID,tenant_id) "
626                                                         + " VALUES(?, ?)";
627                 } else if (databaseProductType == DatabaseProductType.POSTGRESQL) {
628                         insertAccountTenantSQL =
629                                         "INSERT INTO accounts_tenants (HJID, TENANTS_ACCOUNTS_COMMON_CSID,tenant_id) "
630                                                         + " VALUES(nextval('hibernate_sequence'), ?, ?)";
631                 } else {
632                         throw new Exception("Unrecognized database system.");
633                 }
634                 pstmt = conn.prepareStatement(insertAccountTenantSQL); // create a statement
635                 for(String tId : tenantInfo.keySet()) {
636                         String tName = tenantInfo.get(tId);
637                         if(!usersInRepo.contains(getDefaultAdminUserID(tName))) {
638                                 String adminAcct = tenantAdminAcctCSIDs.get(tId);
639                                 pstmt.setString(1, adminAcct);          // set acct CSID param
640                                 pstmt.setString(2, tId);                        // set tenant_id param
641                                 if (logger.isDebugEnabled()) {
642                                         logger.debug("createDefaultAccounts binding account id: "
643                                                         +adminAcct+" to tenant id: "+tId);
644                                 }
645                                 pstmt.executeUpdate();
646                         }
647                         if(!usersInRepo.contains(getDefaultReaderUserID(tName))) {
648                                 String readerAcct = tenantReaderAcctCSIDs.get(tId);
649                                 pstmt.setString(1, readerAcct);         // set acct CSID param
650                                 pstmt.setString(2, tId);                        // set tenant_id param
651                                 if (logger.isDebugEnabled()) {
652                                         logger.debug("createDefaultAccounts binding account id: "
653                                                         +readerAcct+" to tenant id: "+tId);
654                                 }
655                                 pstmt.executeUpdate();
656                         }
657                 }
658                 pstmt.close();
659         } catch(Exception e) {
660                 throw e;
661         } finally {
662                 if(pstmt!=null)
663                         pstmt.close();
664         }
665     }
666     
667     
668     private static String findOrCreateDefaultRoles(Connection conn, Hashtable<String, String> tenantInfo,
669                 Hashtable<String, String> tenantAdminRoleCSIDs, Hashtable<String, String> tenantReaderRoleCSIDs) 
670                         throws SQLException, Exception {
671         // Fifth, fetch and save the default roles
672                 String springAdminRoleCSID = null;
673         Statement stmt = null;
674         PreparedStatement pstmt = null;
675         try {
676                 final String querySpringRole = 
677                                 "SELECT csid from roles WHERE rolename='"+AuthN.ROLE_SPRING_ADMIN_NAME+"'";
678                 stmt = conn.createStatement();
679                 ResultSet rs = stmt.executeQuery(querySpringRole);
680                 if(rs.next()) {
681                         springAdminRoleCSID = rs.getString(1);
682                         if (logger.isDebugEnabled()) {
683                                 logger.debug("createDefaultAccounts found Spring Admin role: "
684                                                 +springAdminRoleCSID);
685                         }
686                 } else {
687                         final String insertSpringAdminRoleSQL =
688                                         "INSERT INTO roles (csid, rolename, displayName, rolegroup, created_at, tenant_id) "
689                                                         + "VALUES ('-1', 'ROLE_SPRING_ADMIN', 'SPRING_ADMIN', 'Spring Security Administrator', now(), '0')";
690                         stmt.executeUpdate(insertSpringAdminRoleSQL);
691                         springAdminRoleCSID = "-1";
692                         if (logger.isDebugEnabled()) {
693                                 logger.debug("createDefaultAccounts CREATED Spring Admin role: "
694                                                 +springAdminRoleCSID);
695                         }
696                 }
697                 rs.close();
698                 final String getRoleCSIDSql =
699                                 "SELECT csid from roles WHERE tenant_id=? and rolename=?";
700                 pstmt = conn.prepareStatement(getRoleCSIDSql); // create a statement
701                 rs = null;
702                 for(String tId : tenantInfo.keySet()) {
703                         pstmt.setString(1, tId);                                                // set tenant_id param
704                         pstmt.setString(2, getDefaultAdminRole(tId));   // set rolename param
705                         rs = pstmt.executeQuery();
706                         // extract data from the ResultSet
707                         if(!rs.next()) {
708                                 throw new RuntimeException("Cannot find role: "+getDefaultAdminRole(tId)
709                                                 +" for tenant id: "+tId+" in roles!");
710                         }
711                         String tenantAdminRoleCSID = rs.getString(1);
712                         if (logger.isDebugEnabled()) {
713                                 logger.debug("createDefaultAccounts found role: "
714                                                 +getDefaultAdminRole(tId)+"("+tenantAdminRoleCSID
715                                                 +") for tenant id: "+tId);
716                         }
717                         tenantAdminRoleCSIDs.put(tId, tenantAdminRoleCSID);
718                         pstmt.setString(1, tId);                                                // set tenant_id param
719                         pstmt.setString(2, getDefaultReaderRole(tId));  // set rolename param
720                         rs.close();
721                         rs = pstmt.executeQuery();
722                         // extract data from the ResultSet
723                         if(!rs.next()) {
724                                 throw new RuntimeException("Cannot find role: "+getDefaultReaderRole(tId)
725                                                 +" for tenant id: "+tId+" in roles!");
726                         }
727                         String tenantReaderRoleCSID = rs.getString(1);
728                         if (logger.isDebugEnabled()) {
729                                 logger.debug("createDefaultAccounts found role: "
730                                                 +getDefaultReaderRole(tId)+"("+tenantReaderRoleCSID
731                                                 +") for tenant id: "+tId);
732                         }
733                         tenantReaderRoleCSIDs.put(tId, tenantReaderRoleCSID);
734                         rs.close();
735                 }
736                 pstmt.close();
737         } catch(Exception e) {
738                 throw e;
739         } finally {
740                 if(stmt!=null)
741                         stmt.close();
742                 if(pstmt!=null)
743                         pstmt.close();
744         }
745         return springAdminRoleCSID;
746     }
747
748     private static String findTenantManagerRole(Connection conn ) 
749                         throws SQLException, RuntimeException, Exception {
750                 String tenantMgrRoleCSID = null;
751         PreparedStatement pstmt = null;
752         try {
753                 String rolename = getQualifiedRoleName(AuthN.ALL_TENANTS_MANAGER_TENANT_ID, 
754                                 AuthN.ROLE_ALL_TENANTS_MANAGER);                
755                 pstmt = conn.prepareStatement(GET_TENANT_MGR_ROLE_SQL); // create a statement
756                 ResultSet rs = null;
757                 pstmt.setString(1, rolename);   // set rolename param
758                 rs = pstmt.executeQuery();
759                 if(rs.next()) {
760                         tenantMgrRoleCSID = rs.getString(1);
761                         if (logger.isDebugEnabled()) {
762                                 logger.debug("findTenantManagerRole found Tenant Mgr role: "
763                                                 +tenantMgrRoleCSID);
764                         }
765                 }
766                 rs.close();
767         } catch(Exception e) {
768                 throw e;
769         } finally {
770                 if(pstmt!=null)
771                         pstmt.close();
772         }
773         if(tenantMgrRoleCSID==null)
774                 throw new RuntimeException("findTenantManagerRole: Cound not find tenant Manager Role!");
775         return tenantMgrRoleCSID;
776     }
777
778     private static void bindAccountsToRoles(Connection conn,  DatabaseProductType databaseProductType,
779                 Hashtable<String, String> tenantInfo, ArrayList<String> usersInRepo,
780                 String springAdminRoleCSID,
781                 Hashtable<String, String> tenantAdminRoleCSIDs, Hashtable<String, String> tenantReaderRoleCSIDs,
782                 Hashtable<String, String> tenantAdminAcctCSIDs, Hashtable<String, String> tenantReaderAcctCSIDs) 
783                         throws SQLException, Exception {
784         // Sixth, bind the accounts to roles. If the users already existed,
785         // we'll assume they were set up correctly.
786         PreparedStatement pstmt = null;
787         try {
788                 String insertAccountRoleSQL;
789                 if (databaseProductType == DatabaseProductType.MYSQL) {
790                         insertAccountRoleSQL = INSERT_ACCOUNT_ROLE_SQL_MYSQL;
791                 } else if (databaseProductType == DatabaseProductType.POSTGRESQL) {
792                         insertAccountRoleSQL = INSERT_ACCOUNT_ROLE_SQL_POSTGRES;
793                 } else {
794                         throw new Exception("Unrecognized database system.");
795                 }
796                 if (logger.isDebugEnabled()) {
797                         logger.debug("createDefaultAccounts binding accounts to roles with SQL:\n"
798                                         +insertAccountRoleSQL);
799                 }
800                 pstmt = conn.prepareStatement(insertAccountRoleSQL); // create a statement
801                 for(String tId : tenantInfo.keySet()) {
802                         String adminUserId =  getDefaultAdminUserID(tenantInfo.get(tId));
803                         if(!usersInRepo.contains(adminUserId)) {
804                                 String adminAcct = tenantAdminAcctCSIDs.get(tId);
805                                 String adminRoleId = tenantAdminRoleCSIDs.get(tId);
806                                 pstmt.setString(1, adminAcct);          // set acct CSID param
807                                 pstmt.setString(2, adminUserId);        // set user_id param
808                                 pstmt.setString(3, adminRoleId);        // set role_id param
809                                 pstmt.setString(4, getDefaultAdminRole(tId));   // set rolename param
810                                 if (logger.isDebugEnabled()) {
811                                         logger.debug("createDefaultAccounts binding account: "
812                                                         +adminUserId+" to Admin role("+adminRoleId
813                                                         +") for tenant id: "+tId);
814                                 }
815                                 pstmt.executeUpdate();
816                                 // Now add the Spring Admin Role to the admin accounts
817                                 pstmt.setString(3, springAdminRoleCSID);        // set role_id param
818                                 pstmt.setString(4, AuthN.ROLE_SPRING_ADMIN_NAME);               // set rolename param
819                                 if (logger.isDebugEnabled()) {
820                                         logger.debug("createDefaultAccounts binding account: "
821                                                         +adminUserId+" to Spring Admin role: "+springAdminRoleCSID);
822                                 }
823                                 pstmt.executeUpdate();
824                         }
825                         String readerUserId = getDefaultReaderUserID(tenantInfo.get(tId));
826                         if(!usersInRepo.contains(readerUserId)) {
827                                 String readerAcct = tenantReaderAcctCSIDs.get(tId);
828                                 String readerRoleId = tenantReaderRoleCSIDs.get(tId);
829                                 pstmt.setString(1, readerAcct);         // set acct CSID param
830                                 pstmt.setString(2, readerUserId);       // set user_id param
831                                 pstmt.setString(3, readerRoleId);       // set role_id param
832                                 pstmt.setString(4, getDefaultReaderRole(tId));  // set rolename param
833                                 if (logger.isDebugEnabled()) {
834                                         logger.debug("createDefaultAccounts binding account: "
835                                                         +readerUserId+" to Reader role("+readerRoleId
836                                                         +") for tenant id: "+tId);
837                                 }
838                                 pstmt.executeUpdate();
839                         }
840                 }
841                 pstmt.close();
842         } catch(Exception e) {
843                 throw e;
844         } finally {
845                 if(pstmt!=null)
846                         pstmt.close();
847         }
848     }
849     
850     private static void bindTenantManagerAccountRole(Connection conn,  DatabaseProductType databaseProductType,
851                 String tenantManagerUserID, String tenantManagerAccountID, String tenantManagerRoleID, String tenantManagerRoleName ) 
852                         throws SQLException, Exception {
853         PreparedStatement pstmt = null;
854         try {
855                 String insertAccountRoleSQL;
856                 if (databaseProductType == DatabaseProductType.MYSQL) {
857                         insertAccountRoleSQL = INSERT_ACCOUNT_ROLE_SQL_MYSQL;
858                 } else if (databaseProductType == DatabaseProductType.POSTGRESQL) {
859                         insertAccountRoleSQL = INSERT_ACCOUNT_ROLE_SQL_POSTGRES;
860                 } else {
861                         throw new Exception("Unrecognized database system.");
862                 }
863                 if (logger.isDebugEnabled()) {
864                         logger.debug("bindTenantManagerAccountRole binding account to role with SQL:\n"
865                                         +insertAccountRoleSQL);
866                 }
867                 pstmt = conn.prepareStatement(insertAccountRoleSQL); // create a statement
868                 pstmt.setString(1, tenantManagerAccountID);             // set acct CSID param
869                 pstmt.setString(2, tenantManagerUserID);        // set user_id param
870                 pstmt.setString(3, tenantManagerRoleID);        // set role_id param
871                 pstmt.setString(4, tenantManagerRoleName);      // set rolename param
872                 if (logger.isDebugEnabled()) {
873                         logger.debug("bindTenantManagerAccountRole binding user: "
874                                         +tenantManagerUserID+" to Admin role("+tenantManagerRoleName+")");
875                 }
876                 pstmt.executeUpdate();
877                 /* At this point, tenant manager should not need the Spring Admin Role
878                 pstmt.setString(3, springAdminRoleCSID);        // set role_id param
879                 pstmt.setString(4, SPRING_ADMIN_ROLE);          // set rolename param
880                 if (logger.isDebugEnabled()) {
881                         logger.debug("createDefaultAccounts binding account: "
882                                         +adminUserId+" to Spring Admin role: "+springAdminRoleCSID);
883                 }
884                 pstmt.executeUpdate();
885                 */
886                 pstmt.close();
887         } catch(Exception e) {
888                 throw e;
889         } finally {
890                 if(pstmt!=null)
891                         pstmt.close();
892         }
893     }
894     
895     /*
896      * Using the tenant bindings, ensure there are corresponding Tenant records (db columns).
897      */
898     //FIXME: This code should be using JPA objects and JPATransactionContext, not raw SQL.
899     public static void createTenants(
900                 TenantBindingConfigReaderImpl tenantBindingConfigReader,
901                 DatabaseProductType databaseProductType,
902                         String cspaceDatabaseName) throws Exception {
903                 logger.debug("ServiceMain.createTenants starting...");
904                 Hashtable<String, String> tenantInfo = getTenantNamesFromConfig(tenantBindingConfigReader);
905                 Connection conn = null;
906                 try {
907                         conn = getConnection(cspaceDatabaseName);
908                         ArrayList<String> existingTenants = compileExistingTenants(conn, tenantInfo);
909
910                         // Note that this only creates tenants not marked as "createDisabled"
911                         createMissingTenants(conn, tenantInfo, existingTenants);
912                 } catch (Exception e) {
913                         logger.debug("Exception in createTenants: " + e.getLocalizedMessage());
914                         throw e;
915                 } finally {
916                         try {
917                                 if (conn != null)
918                                         conn.close();
919                         } catch (SQLException sqle) {
920                                 if (logger.isDebugEnabled()) {
921                                         logger.debug("SQL Exception closing statement/connection: " + sqle.getLocalizedMessage());
922                                 }
923                         }
924                 }
925         }
926     
927     /**
928      * 
929      * @param tenantBindingConfigReader
930      * @param databaseProductType
931      * @param cspaceDatabaseName
932      * @throws Exception
933      */
934     //FIXME: This code should be using the JPA objects and JPATransactionContext, not raw SQL.
935     public static void createDefaultAccounts(
936                 TenantBindingConfigReaderImpl tenantBindingConfigReader,
937                 DatabaseProductType databaseProductType,
938                 String cspaceDatabaseName) throws Exception {
939
940         logger.debug("ServiceMain.createDefaultAccounts starting...");
941         
942         Hashtable<String, String> tenantInfo = getTenantNamesFromConfig(tenantBindingConfigReader);
943         Connection conn = null;
944         // TODO - need to put in tests for existence first.
945         // We could just look for the accounts per tenant up front, and assume that
946         // the rest is there if the accounts are.
947         // Could add a sql script to remove these if need be - Spring only does roles, 
948         // and we're not touching that, so we could safely toss the 
949         // accounts, users, account-tenants, account-roles, and start over.
950         try {
951                 conn = getConnection(cspaceDatabaseName);
952                 
953                 ArrayList<String> usersInRepo = findOrCreateDefaultUsers(conn, tenantInfo);
954                 
955                 Hashtable<String, String> tenantAdminAcctCSIDs = new Hashtable<String, String>();
956                 Hashtable<String, String> tenantReaderAcctCSIDs = new Hashtable<String, String>();
957                 findOrCreateDefaultAccounts(conn, tenantInfo, usersInRepo,
958                                 tenantAdminAcctCSIDs, tenantReaderAcctCSIDs);
959
960                 bindDefaultAccountsToTenants(conn, databaseProductType, tenantInfo, usersInRepo,
961                                 tenantAdminAcctCSIDs, tenantReaderAcctCSIDs);
962                 
963                 Hashtable<String, String> tenantAdminRoleCSIDs = new Hashtable<String, String>();
964                 Hashtable<String, String> tenantReaderRoleCSIDs = new Hashtable<String, String>();
965                 String springAdminRoleCSID = findOrCreateDefaultRoles(conn, tenantInfo,
966                                 tenantAdminRoleCSIDs, tenantReaderRoleCSIDs);
967                 
968                 bindAccountsToRoles(conn,  databaseProductType,
969                                 tenantInfo, usersInRepo, springAdminRoleCSID,
970                                 tenantAdminRoleCSIDs, tenantReaderRoleCSIDs,
971                                 tenantAdminAcctCSIDs, tenantReaderAcctCSIDs);
972                 
973                 boolean createdTenantMgrAccount = findOrCreateTenantManagerUserAndAccount(conn);
974                 if(createdTenantMgrAccount) {
975                         // If we created the account, we need to create the bindings. Otherwise, assume they
976                         // are all set (from previous initialization).
977                         String tenantManagerRoleCSID = findTenantManagerRole(conn);
978                         bindTenantManagerAccountRole(conn, databaseProductType, 
979                                         TENANT_MANAGER_USER, AuthN.TENANT_MANAGER_ACCT_ID, 
980                                         tenantManagerRoleCSID, AuthN.ROLE_ALL_TENANTS_MANAGER);
981                 }
982         } catch (Exception e) {
983                         logger.debug("Exception in createDefaultAccounts: " + e.getLocalizedMessage());
984                 throw e;
985                 } finally {
986                         try {
987                                 if (conn != null)
988                                         conn.close();
989                         } catch (SQLException sqle) {
990                                 if (logger.isDebugEnabled()) {
991                                         logger.debug("SQL Exception closing statement/connection: " + sqle.getLocalizedMessage());
992                                 }
993                         }
994                 }       
995     }
996     
997     private static String getDefaultAdminRole(String tenantId) {
998         return ROLE_PREFIX+tenantId+TENANT_ADMIN_ROLE_SUFFIX;
999     }
1000     
1001     private static String getDefaultReaderRole(String tenantId) {
1002         return ROLE_PREFIX+tenantId+TENANT_READER_ROLE_SUFFIX;
1003     }
1004     
1005     private static String getDefaultAdminUserID(String tenantName) {
1006         return TENANT_ADMIN_ACCT_PREFIX+tenantName;
1007     }
1008     
1009     private static String getDefaultReaderUserID(String tenantName) {
1010         return TENANT_READER_ACCT_PREFIX+tenantName;
1011     }
1012     
1013         static public PermissionAction createPermissionAction(Permission perm,
1014                         ActionType actionType) {
1015         PermissionAction pa = new PermissionAction();
1016
1017             CSpaceAction action = URIResourceImpl.getAction(actionType);
1018             URIResourceImpl uriRes = new URIResourceImpl(perm.getTenantId(),
1019                     perm.getResourceName(), action);
1020             pa.setName(actionType);
1021             pa.setObjectIdentity(uriRes.getHashedId().toString());
1022             pa.setObjectIdentityResource(uriRes.getId());
1023             
1024             return pa;
1025         }
1026
1027         static public PermissionAction update(Permission perm, PermissionAction permAction) {
1028         PermissionAction pa = new PermissionAction();
1029
1030             CSpaceAction action = URIResourceImpl.getAction(permAction.getName());
1031             URIResourceImpl uriRes = new URIResourceImpl(perm.getTenantId(),
1032                     perm.getResourceName(), action);
1033             pa.setObjectIdentity(uriRes.getHashedId().toString());
1034             pa.setObjectIdentityResource(uriRes.getId());
1035             
1036             return pa;
1037         }
1038         
1039         private static HashSet<String> getTransitionVerbList(TenantBindingType tenantBinding, ServiceBindingType serviceBinding) {
1040                 HashSet<String> result = new HashSet<String>();
1041                 
1042                 TransitionDefList transitionDefList = getTransitionDefList(tenantBinding, serviceBinding);
1043         for (TransitionDef transitionDef : transitionDefList.getTransitionDef()) {
1044                 String transitionVerb = transitionDef.getName();
1045                 String[] tokens = transitionVerb.split("_");  // Split the verb into words.  The workflow verbs are compound words combined with the '_' character.
1046                 result.add(tokens[0]); // We only care about the first word.
1047         }
1048
1049         return result;
1050         }
1051         
1052         private static TransitionDefList getTransitionDefList(TenantBindingType tenantBinding, ServiceBindingType serviceBinding) {
1053                 TransitionDefList result = null;
1054                 try {
1055                         String serviceObjectName = serviceBinding.getObject().getName();
1056                 DocumentHandler docHandler = ServiceConfigUtils.createDocumentHandlerInstance(
1057                                 tenantBinding, serviceBinding);
1058                 Lifecycle lifecycle = docHandler.getLifecycle(serviceObjectName);
1059                 if (lifecycle != null) {
1060                         result = lifecycle.getTransitionDefList();
1061                 }
1062                 } catch (Exception e) {
1063                         // Ignore this exception and return an empty non-null TransitionDefList
1064                 }
1065                 
1066                 if (result == null) {
1067                         if (serviceBinding.getType().equalsIgnoreCase(ServiceBindingUtils.SERVICE_TYPE_SECURITY) == false) {
1068                                 logger.debug("Could not retrieve a lifecycle transition definition list from: "
1069                                                 + serviceBinding.getName()
1070                                                 + " with tenant ID = "
1071                                                 + tenantBinding.getId());
1072                         }
1073                         // return an empty list                 
1074                         result = new TransitionDefList();
1075                 } else {
1076                         logger.debug("Successfully retrieved a lifecycle transition definition list from: "
1077                                         + serviceBinding.getName()
1078                                         + " with tenant ID = "
1079                                         + tenantBinding.getId());
1080                 }
1081                 
1082                 return result;
1083         }
1084         
1085         /**
1086          * 
1087          * @param tenantBindingConfigReader
1088          * @param databaseProductType
1089          * @param cspaceDatabaseName
1090          * @throws Exception
1091          */
1092     public static void createDefaultWorkflowPermissions(
1093                 JPATransactionContext jpaTransactionContext,
1094                 TenantBindingConfigReaderImpl tenantBindingConfigReader,
1095                 DatabaseProductType databaseProductType, 
1096                 String cspaceDatabaseName) throws Exception //FIXME: REM - 4/11/2012 - Rename to createWorkflowPermissions
1097     {
1098         java.util.logging.Logger logger = java.util.logging.Logger.getAnonymousLogger();
1099
1100         AuthZ.get().login(); //login to Spring Security manager
1101         
1102         try {
1103                 Hashtable<String, TenantBindingType> tenantBindings = tenantBindingConfigReader.getTenantBindings();
1104                 for (String tenantId : tenantBindings.keySet()) {
1105                         logger.info(String.format("Creating/verifying workflow permissions for tenant ID=%s.", tenantId));
1106                         TenantBindingType tenantBinding = tenantBindings.get(tenantId);
1107                         if (tenantBinding.isConfigChangedSinceLastStart() == false) {
1108                                 continue; // skip the rest of the loop and go to the next tenant
1109                         }
1110                         
1111                         Role adminRole = AuthorizationCommon.getRole(jpaTransactionContext, tenantBinding.getId(), ROLE_TENANT_ADMINISTRATOR);
1112                         Role readonlyRole = AuthorizationCommon.getRole(jpaTransactionContext, tenantBinding.getId(), ROLE_TENANT_READER);
1113                         
1114                         if (adminRole == null || readonlyRole == null) {
1115                                 String msg = String.format("One or more of the required default CollectionSpace administrator roles is missing or was never created.  If you're setting up a new instance of CollectionSpace, shutdown the Tomcat server and run the 'ant import' command from the root/top level CollectionSpace 'Services' source directory.  Then try restarting Tomcat.");
1116                                 logger.info(msg);
1117                                 throw new RuntimeException("One or more of the required default CollectionSpace administrator roles is missing or was never created.");
1118                         }
1119                         
1120                         for (ServiceBindingType serviceBinding : tenantBinding.getServiceBindings()) {
1121                                 String prop = ServiceBindingUtils.getPropertyValue(serviceBinding, REFRESH_AUTHZ_PROP);
1122                                 if (prop == null ? true : Boolean.parseBoolean(prop)) {
1123                                         try {
1124                                                 jpaTransactionContext.beginTransaction();
1125                                                 TransitionDefList transitionDefList = getTransitionDefList(tenantBinding, serviceBinding);
1126                                                 HashSet<String> transitionVerbList = getTransitionVerbList(tenantBinding, serviceBinding);
1127                                                 for (String transitionVerb : transitionVerbList) {
1128                                                         //
1129                                                         // Create the permission for the admin role
1130                                                         Permission adminPerm = createWorkflowPermission(tenantBinding, serviceBinding, transitionVerb, ACTIONGROUP_CRUDL);
1131                                                         persist(jpaTransactionContext, adminPerm, adminRole, true, ACTIONGROUP_CRUDL);
1132                                                         //
1133                                                         // Create the permission for the read-only role
1134                                                         Permission readonlyPerm = createWorkflowPermission(tenantBinding, serviceBinding, transitionVerb, ACTIONGROUP_RL);                                                      
1135                                                         persist(jpaTransactionContext, readonlyPerm, readonlyRole, true, ACTIONGROUP_RL); // Persist/store the permission and permrole records and related Spring Security info
1136                                                 }
1137                                                 jpaTransactionContext.commitTransaction();
1138                                         } catch (IllegalStateException e) {
1139                                                 logger.fine(e.getLocalizedMessage()); //We end up here if there is no document handler for the service -this is ok for some of the services.
1140                                         } catch (Exception x) {
1141                                                 jpaTransactionContext.markForRollback();
1142                                         }
1143                                 } else {
1144                                         logger.warning("AuthZ refresh service binding property is set to FALSE so default permissions will NOT be refreshed for: "
1145                                                         + serviceBinding.getName());
1146                                 }
1147                         }
1148                 }
1149         } catch (Exception e) {
1150                 jpaTransactionContext.markForRollback();
1151             logger.fine("Caught exception and rolling back permission creation: " + e.getMessage());
1152             throw e;
1153         }
1154     }
1155     
1156         private static void createMissingTenants(Connection conn, Hashtable<String, String> tenantInfo,
1157                 ArrayList<String> existingTenants) throws SQLException, Exception {
1158                 // Need to define and look for a createDisabled attribute in tenant config
1159         final String insertTenantSQL = 
1160                 "INSERT INTO tenants (id,name,authorities_initialized,disabled,created_at) VALUES (?,?,FALSE,FALSE,now())";
1161         PreparedStatement pstmt = null;
1162         try {
1163                 pstmt = conn.prepareStatement(insertTenantSQL); // create a statement
1164                 for(String tId : tenantInfo.keySet()) {
1165                         if(existingTenants.contains(tId)) {
1166                                 if (logger.isDebugEnabled()) {
1167                                         logger.debug("createMissingTenants: tenant exists (skipping): "
1168                                                         +tenantInfo.get(tId));
1169                                 }
1170                                 continue;
1171                         }
1172                         pstmt.setString(1, tId);                                        // set id param
1173                         pstmt.setString(2, tenantInfo.get(tId));        // set name param
1174                         if (logger.isDebugEnabled()) {
1175                                 logger.debug("createMissingTenants adding entry for tenant: "+tId);
1176                         }
1177                         pstmt.executeUpdate();
1178                 }
1179                 pstmt.close();
1180         } catch(Exception e) {
1181                 throw e;
1182         } finally {
1183                 if(pstmt!=null)
1184                         pstmt.close();
1185         }
1186     }
1187     
1188     public static String getPersistedMD5Hash(String tenantId, String cspaceDatabaseName) throws Exception {
1189         String result = null;
1190         
1191         ArrayList<String> existingTenants = new ArrayList<String>();
1192         // First find or create the tenants
1193         final String queryTenantSQL = String.format("SELECT id, name, config_md5hash FROM tenants WHERE id = '%s'", tenantId);
1194         
1195         Statement stmt = null;
1196         Connection conn;
1197         int rowCount = 0;
1198         try {
1199                         conn = getConnection(cspaceDatabaseName);
1200                 stmt = conn.createStatement();
1201                 ResultSet rs = stmt.executeQuery(queryTenantSQL);
1202                 while (rs.next()) {
1203                         if (rowCount > 0) {
1204                                 String errMsg = String.format("Unable to configure tenant ID='%s'.  There appears to be more than one tenant with that ID in the AuthN/AuthZ database named '%s'.",
1205                                                 tenantId, cspaceDatabaseName);
1206                                 throw new Exception(errMsg);
1207                         }
1208                         String tId = rs.getString("id");                // for debugging only
1209                         String tName = rs.getString("name");    // for debugging only
1210                         result = rs.getString("config_md5hash");
1211                         rowCount++;
1212                 }
1213                 rs.close();
1214         } catch(Exception e) {
1215                 throw e;
1216         } finally {
1217                 if (stmt != null) stmt.close();
1218         }
1219         
1220         return result;
1221     }
1222
1223         private static PermissionRoleRel findPermRoleRel(
1224                         JPATransactionContext jpaTransactionContext,
1225                         String permissionId,
1226                         String RoleId) {
1227         PermissionRoleRel result = null;
1228         
1229         try {
1230                 String whereClause = "where permissionId = :id and roleId = :roleId";
1231                 HashMap<String, Object> params = new HashMap<String, Object>();
1232                 params.put("id", permissionId);
1233                 params.put("roleId", RoleId);        
1234         
1235                 result = (PermissionRoleRel) JpaStorageUtils.getEntity(jpaTransactionContext,
1236                                 PermissionRoleRel.class.getCanonicalName(), whereClause, params);
1237         } catch (Exception e) {
1238                 //Do nothing. Will return null;
1239         }
1240                 
1241         return result;
1242     }
1243     
1244     /*
1245      * Persists the Permission, PermissionRoleRel, and Spring Security table entries all in one transaction
1246      */
1247     private static void persist(JPATransactionContext jpaTransactionContext, Permission permission, Role role, boolean enforceTenancy, ActionGroup actionGroup) throws Exception {
1248                 AuthorizationStore authzStore = new AuthorizationStore();
1249                 // First persist the Permission record
1250                 authzStore.store(jpaTransactionContext, permission);
1251                 
1252                 // If the PermRoleRel doesn't already exists then relate the permission and the role in a new PermissionRole (the service payload)
1253                 // Create a PermissionRoleRel (the database relation table for the permission and role)
1254                 PermissionRoleRel permRoleRel = findPermRoleRel(jpaTransactionContext, permission.getCsid(), role.getCsid());
1255                 if (permRoleRel == null) {
1256                         PermissionRole permRole = createPermissionRole(permission, role, enforceTenancy);
1257                 List<PermissionRoleRel> permRoleRels = new ArrayList<PermissionRoleRel>();
1258                 PermissionRoleUtil.buildPermissionRoleRel(jpaTransactionContext, permRole, SubjectType.ROLE, permRoleRels,
1259                                 false /*not for delete*/, role.getTenantId());
1260                 for (PermissionRoleRel prr : permRoleRels) {
1261                     authzStore.store(jpaTransactionContext, prr);
1262                 }
1263                         Profiler profiler = new Profiler(AuthorizationCommon.class, 2);
1264                         profiler.start();
1265                         // Add a corresponding entry in the Spring Security Tables
1266                         addPermissionsForUri(permission, permRole);
1267                         profiler.stop();
1268                         logger.debug("Finished full perm generation for "
1269                                         + ":" + permission.getTenantId()
1270                                         + ":" + permission.getResourceName()
1271                                         + ":" + actionGroup.getName()
1272                                         + ":" + profiler.getCumulativeTime());
1273                 }
1274         
1275     }
1276         
1277         public static boolean hasTokenExpired(EmailConfig emailConfig, Token token) throws NoSuchAlgorithmException {
1278                 boolean result = false;
1279                 
1280                 int maxConfigSeconds = emailConfig.getPasswordResetConfig().getTokenExpirationSeconds().intValue();
1281                 int maxTokenSeconds = token.getExpireSeconds().intValue();
1282                 
1283                 long createdTime = token.getCreatedAtItem().getTime();          
1284                 long configExpirationTime = createdTime + maxConfigSeconds * 1000;              // the current tenant config for how long a token stays valid
1285                 long tokenDefinedExirationTime = createdTime + maxTokenSeconds * 1000;  // the tenant config for how long a token stays valid when the token was created.
1286                 
1287                 if (configExpirationTime != tokenDefinedExirationTime) {
1288                         String msg = String.format("The configured expiration time for the token = '%s' changed from when the token was created.",
1289                                         token.getId());
1290                         logger.warn(msg);
1291                 }
1292                 //
1293                 // Note: the current tenant bindings config for expiration takes precedence over the config used to create the token.
1294                 //
1295                 if (System.currentTimeMillis() >= configExpirationTime) {
1296                         result = true;
1297                 }
1298                 
1299                 return result;
1300         }
1301                 
1302         /*
1303          * Validate that the password reset configuration is correct.
1304          */
1305         private static String validatePasswordResetConfig(PasswordResetConfig passwordResetConfig) {
1306                 String result = null;
1307                 
1308                 if (passwordResetConfig != null) {
1309                         result = passwordResetConfig.getMessage();
1310                         if (result == null || result.length() == 0) {
1311                                 result = DEFAULT_PASSWORD_RESET_EMAIL_MESSAGE;
1312                                 logger.warn("Could not find a password reset message in the tenant's configuration.  Using the default one");
1313                         }
1314                         
1315                         if (result.contains("{{link}}") == false) {
1316                                 logger.warn("The tenant's password reset message does not contain a required '{{link}}' marker.");
1317                                 result = null;
1318                         }
1319                         
1320                         if (passwordResetConfig.getLoginpage() == null || passwordResetConfig.getLoginpage().trim().isEmpty()) {
1321                                 logger.warn("The tenant's password reset configuration is missing a 'loginpage' value.  It should be set to something like '/collectionspace/ui/core/html/index.html'.");
1322                                 result = null;
1323                         }
1324                         
1325                     String subject = passwordResetConfig.getSubject();
1326                     if (subject == null || subject.trim().isEmpty()) {
1327                         passwordResetConfig.setSubject(DEFAULT_PASSWORD_RESET_EMAIL_SUBJECT);
1328                     }
1329
1330                 }
1331                 
1332                 return result;
1333         }
1334         
1335         /*
1336          * Generate a password reset message. Embeds an authorization token to reset a user's password.
1337          */
1338         public static String generatePasswordResetEmailMessage(EmailConfig emailConfig, AccountListItem accountListItem, Token token) throws Exception {
1339                 String result = null;
1340                 
1341                 result = validatePasswordResetConfig(emailConfig.getPasswordResetConfig());
1342                 if (result == null) {
1343                         String errMsg = String.format("The password reset configuration for the tenant ID='%s' is missing or malformed.  Could not initiate a password reset for user ID='%s. See the log files for more details.",
1344                                         token.getTenantId(), accountListItem.getEmail());
1345                         throw new Exception(errMsg);
1346                 }
1347                 
1348                 String link = emailConfig.getBaseurl() + emailConfig.getPasswordResetConfig().getLoginpage() + "?token=" + token.getId();
1349                 result = result.replaceAll("\\{\\{link\\}\\}", link);
1350                 
1351                 if (result.contains("{{greeting}}")) {
1352                         String greeting = accountListItem.getScreenName();
1353                         result = result.replaceAll("\\{\\{greeting\\}\\}", greeting);
1354                         result = result.replaceAll("\\\\n", "\\\n");
1355                         result = result.replaceAll("\\\\r", "\\\r");
1356                 }                       
1357                 
1358                 return result;
1359         }
1360
1361         public static void persistTenantBindingsMD5Hash(TenantBindingConfigReaderImpl tenantBindingConfigReader,
1362                         DatabaseProductType databaseProductType, String cspaceDatabaseName) throws Exception {
1363                 // Need to define and look for a createDisabled attribute in tenant config
1364                 String updateTableSQL = "UPDATE tenants SET config_md5hash = ? WHERE id = ?";
1365
1366         Connection conn;
1367         PreparedStatement pstmt = null;
1368         try {
1369                         conn = getConnection(cspaceDatabaseName);
1370                 pstmt = conn.prepareStatement(updateTableSQL); // create a statement
1371                 for (String tId : AuthorizationCommon.tenantConfigMD5HashTable.keySet()) {
1372                         pstmt.setString(1, AuthorizationCommon.getTenantConfigMD5Hash(tId));
1373                         pstmt.setString(2, tId);
1374                         if (logger.isDebugEnabled()) {
1375                                 logger.debug("createMissingTenants adding entry for tenant: " + tId);
1376                         }
1377                         pstmt.executeUpdate();
1378                 }
1379                 pstmt.close();
1380         } catch(Exception e) {
1381                 throw e;
1382         } finally {
1383                 if (pstmt!=null) pstmt.close();
1384         }
1385     }
1386 }