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