]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
ed28f4766678eb556f94ca8d41e0e1ce65e83338
[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,salt, 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 salt = UUID.randomUUID().toString();
470                                 String secEncPasswd = SecurityUtils.createPasswordHash(
471                                                 adminAcctName, DEFAULT_ADMIN_PASSWORD, salt);
472                                 pstmt.setString(1, adminAcctName);      // set username param
473                                 pstmt.setString(2, secEncPasswd);       // set passwd param
474                                 pstmt.setString(3, salt);
475                                 if (logger.isDebugEnabled()) {
476                                         logger.debug("createDefaultUsersAndAccounts adding user: "
477                                                         +adminAcctName+" for tenant: "+tName);
478                                 }
479                                 pstmt.executeUpdate();
480                         } else if (logger.isDebugEnabled()) {
481                                 logger.debug("createDefaultUsersAndAccounts: user: "+adminAcctName
482                                                 +" already exists - skipping.");
483                         }
484
485
486                         String readerAcctName =  getDefaultReaderUserID(tName);
487                         if(!usersInRepo.contains(readerAcctName)) {
488                                 String salt = UUID.randomUUID().toString();
489                                 String secEncPasswd = SecurityUtils.createPasswordHash(
490                                                 readerAcctName, DEFAULT_READER_PASSWORD, salt);
491                                 pstmt.setString(1, readerAcctName);     // set username param
492                                 pstmt.setString(2, secEncPasswd);       // set passwd param
493                                 pstmt.setString(3, salt);
494                                 if (logger.isDebugEnabled()) {
495                                         logger.debug("createDefaultUsersAndAccounts adding user: "
496                                                         +readerAcctName+" for tenant: "+tName);
497                                 }
498                                 pstmt.executeUpdate();
499                         } else if (logger.isDebugEnabled()) {
500                                 logger.debug("createDefaultUsersAndAccounts: user: "+readerAcctName
501                                                 +" already exists - skipping.");
502                         }
503                 }
504                 pstmt.close();
505         } catch(Exception e) {
506                 throw e;
507         } finally {
508                 if(stmt!=null)
509                         stmt.close();
510                 if(pstmt!=null)
511                         pstmt.close();
512         }
513         return usersInRepo;
514     }
515     
516     private static void findOrCreateDefaultAccounts(Connection conn, Hashtable<String, String> tenantInfo,
517                 ArrayList<String> usersInRepo,
518                 Hashtable<String, String> tenantAdminAcctCSIDs, Hashtable<String, String> tenantReaderAcctCSIDs) 
519                         throws SQLException, Exception {
520         // Third, create the accounts. Assume that if the users were already there,
521         // then the accounts were as well
522         PreparedStatement pstmt = null;
523         try {
524                 pstmt = conn.prepareStatement(INSERT_ACCOUNT_SQL); // create a statement
525                 for(String tId : tenantInfo.keySet()) {
526                         String tName = tenantInfo.get(tId);
527                         String adminCSID = UUID.randomUUID().toString();
528                         tenantAdminAcctCSIDs.put(tId, adminCSID);
529                         String adminAcctName =  getDefaultAdminUserID(tName);
530                         if(!usersInRepo.contains(adminAcctName)) {
531                                 pstmt.setString(1, adminCSID);                  // set csid param
532                                 pstmt.setString(2, adminAcctName);      // set email param (bogus)
533                                 pstmt.setString(3, adminAcctName);      // set userid param
534                                 pstmt.setString(4, "Administrator");// set screen name param
535                                 if (logger.isDebugEnabled()) {
536                                         logger.debug("createDefaultAccounts adding account: "
537                                                         +adminAcctName+" for tenant: "+tName);
538                                 }
539                                 pstmt.executeUpdate();
540                         } else if (logger.isDebugEnabled()) {
541                                 logger.debug("createDefaultAccounts: user: "+adminAcctName
542                                                 +" already exists - skipping account generation.");
543                         }
544
545                         String readerCSID = UUID.randomUUID().toString();       
546                         tenantReaderAcctCSIDs.put(tId, readerCSID);
547                         String readerAcctName =  getDefaultReaderUserID(tName);
548                         if(!usersInRepo.contains(readerAcctName)) {
549                                 pstmt.setString(1, readerCSID);         // set csid param
550                                 pstmt.setString(2, readerAcctName);     // set email param (bogus)
551                                 pstmt.setString(3, readerAcctName);     // set userid param
552                                 pstmt.setString(4, "Reader");           // set screen name param
553                                 if (logger.isDebugEnabled()) {
554                                         logger.debug("createDefaultAccounts adding account: "
555                                                         +readerAcctName+" for tenant: "+tName);
556                                 }
557                                 pstmt.executeUpdate();
558                         } else if (logger.isDebugEnabled()) {
559                                 logger.debug("createDefaultAccounts: user: "+readerAcctName
560                                                 +" already exists - skipping account creation.");
561                         }
562                 }
563                 pstmt.close();
564         } catch(Exception e) {
565                 throw e;
566         } finally {
567                 if(pstmt!=null)
568                         pstmt.close();
569         }
570     }
571     
572     private static boolean findOrCreateTenantManagerUserAndAccount(Connection conn) 
573                         throws SQLException, Exception {
574         // Find or create the special tenant manager account.
575         // Later can make the user name for tenant manager be configurable, settable.
576         Statement stmt = null;
577         PreparedStatement pstmt = null;
578         boolean created = false;
579         try {
580                 boolean foundTMgrUser = false;
581                 stmt = conn.createStatement();
582                 ResultSet rs = stmt.executeQuery(QUERY_TENANT_MGR_USER_SQL);
583                 // Should only find one - only consider it
584                 if(rs.next()) {
585                         String uName = rs.getString("username");
586                         foundTMgrUser = uName.equals(TENANT_MANAGER_USER);
587                 }
588                 rs.close();
589                 if(!foundTMgrUser) {
590                         String salt = UUID.randomUUID().toString();
591                         pstmt = conn.prepareStatement(INSERT_USER_SQL); // create a statement
592                         String secEncPasswd = SecurityUtils.createPasswordHash(
593                                         TENANT_MANAGER_USER, DEFAULT_TENANT_MANAGER_PASSWORD, salt);
594                         pstmt.setString(1, TENANT_MANAGER_USER);        // set username param
595                         pstmt.setString(2, secEncPasswd);       // set passwd param
596                         pstmt.setString(3, salt);
597                         if (logger.isDebugEnabled()) {
598                                 logger.debug("findOrCreateTenantManagerUserAndAccount adding tenant manager user: "
599                                                 +TENANT_MANAGER_USER);
600                         }
601                         pstmt.executeUpdate();
602                 pstmt.close();
603                 // Now create the account to match
604                         pstmt = conn.prepareStatement(INSERT_ACCOUNT_SQL); // create a statement
605                                 pstmt.setString(1, AuthN.TENANT_MANAGER_ACCT_ID);                // set csid param
606                                 pstmt.setString(2, DEFAULT_TENANT_MANAGER_EMAIL);       // set email param (bogus)
607                                 pstmt.setString(3, TENANT_MANAGER_USER);        // set userid param
608                                 pstmt.setString(4, TENANT_MANAGER_SCREEN_NAME);// set screen name param
609                                 if (logger.isDebugEnabled()) {
610                                         logger.debug("findOrCreateTenantManagerUserAndAccount adding tenant manager account: "
611                                                         +TENANT_MANAGER_USER);
612                                 }
613                                 pstmt.executeUpdate();
614                         pstmt.close();
615                         created = true;
616                 } else if (logger.isDebugEnabled()) {
617                         logger.debug("findOrCreateTenantManagerUserAndAccount: tenant manager: "+TENANT_MANAGER_USER
618                                         +" already exists.");
619                 }
620         } catch(Exception e) {
621                 throw e;
622         } finally {
623                 if(stmt!=null)
624                         stmt.close();
625                 if(pstmt!=null)
626                         pstmt.close();
627         }
628         return created;
629     }
630     
631     private static void bindDefaultAccountsToTenants(Connection conn, DatabaseProductType databaseProductType,
632                 Hashtable<String, String> tenantInfo, ArrayList<String> usersInRepo,
633                 Hashtable<String, String> tenantAdminAcctCSIDs, Hashtable<String, String> tenantReaderAcctCSIDs) 
634                         throws SQLException, Exception {
635         // Fourth, bind accounts to tenants. Assume that if the users were already there,
636         // then the accounts were bound to tenants correctly
637         PreparedStatement pstmt = null;
638         try {
639                 String insertAccountTenantSQL;
640                 if (databaseProductType == DatabaseProductType.MYSQL) {
641                         insertAccountTenantSQL =
642                                         "INSERT INTO accounts_tenants (TENANTS_ACCOUNTS_COMMON_CSID,tenant_id) "
643                                                         + " VALUES(?, ?)";
644                 } else if (databaseProductType == DatabaseProductType.POSTGRESQL) {
645                         insertAccountTenantSQL =
646                                         "INSERT INTO accounts_tenants (HJID, TENANTS_ACCOUNTS_COMMON_CSID,tenant_id) "
647                                                         + " VALUES(nextval('hibernate_sequence'), ?, ?)";
648                 } else {
649                         throw new Exception("Unrecognized database system.");
650                 }
651                 pstmt = conn.prepareStatement(insertAccountTenantSQL); // create a statement
652                 for(String tId : tenantInfo.keySet()) {
653                         String tName = tenantInfo.get(tId);
654                         if(!usersInRepo.contains(getDefaultAdminUserID(tName))) {
655                                 String adminAcct = tenantAdminAcctCSIDs.get(tId);
656                                 pstmt.setString(1, adminAcct);          // set acct CSID param
657                                 pstmt.setString(2, tId);                        // set tenant_id param
658                                 if (logger.isDebugEnabled()) {
659                                         logger.debug("createDefaultAccounts binding account id: "
660                                                         +adminAcct+" to tenant id: "+tId);
661                                 }
662                                 pstmt.executeUpdate();
663                         }
664                         if(!usersInRepo.contains(getDefaultReaderUserID(tName))) {
665                                 String readerAcct = tenantReaderAcctCSIDs.get(tId);
666                                 pstmt.setString(1, readerAcct);         // set acct CSID param
667                                 pstmt.setString(2, tId);                        // set tenant_id param
668                                 if (logger.isDebugEnabled()) {
669                                         logger.debug("createDefaultAccounts binding account id: "
670                                                         +readerAcct+" to tenant id: "+tId);
671                                 }
672                                 pstmt.executeUpdate();
673                         }
674                 }
675                 pstmt.close();
676         } catch(Exception e) {
677                 throw e;
678         } finally {
679                 if(pstmt!=null)
680                         pstmt.close();
681         }
682     }
683     
684     /**
685      * Creates the default Admin and Reader roles for all the configured tenants.
686      * 
687      * Returns the CSID of the Spring Admin role.
688      * 
689      * @param conn
690      * @param tenantInfo
691      * @param tenantAdminRoleCSIDs
692      * @param tenantReaderRoleCSIDs
693      * @return
694      * @throws SQLException
695      * @throws Exception
696      */
697     private static String findOrCreateDefaultRoles(Connection conn, Hashtable<String, String> tenantInfo,
698                 Hashtable<String, String> tenantAdminRoleCSIDs, Hashtable<String, String> tenantReaderRoleCSIDs) 
699                         throws SQLException, Exception {
700
701                 String springAdminRoleCSID = null;
702         Statement stmt = null;
703         PreparedStatement pstmt = null;
704         try {
705                 //
706                 // Look for the Spring Security admin role.  If not found, create it.
707                 //
708                 final String querySpringRole = String.format("SELECT csid from roles WHERE rolename='%s'", AuthN.ROLE_SPRING_ADMIN_NAME);
709                 stmt = conn.createStatement();
710                 ResultSet rs = stmt.executeQuery(querySpringRole);
711                 if (rs.next()) {
712                         springAdminRoleCSID = rs.getString(1);
713                         if (logger.isDebugEnabled()) {
714                                 logger.debug("createDefaultAccounts found Spring Admin role: " + springAdminRoleCSID);
715                         }
716                 } else {
717                         final String insertSpringAdminRoleSQL = String.format(
718                                         "INSERT INTO roles (csid, rolename, displayName, rolegroup, created_at, tenant_id) VALUES ('%s', '%s', '%s', '%s', now(), '%s')",
719                                         AuthN.ROLE_SPRING_ADMIN_ID, AuthN.ROLE_SPRING_ADMIN_NAME, AuthN.SPRING_ADMIN_USER, AuthN.ROLE_SPRING_GROUP_NAME, AuthN.ADMIN_TENANT_ID);
720                         stmt.executeUpdate(insertSpringAdminRoleSQL);
721                         springAdminRoleCSID = AuthN.ROLE_SPRING_ADMIN_ID;
722                 }
723                 rs.close();
724                 rs = null;
725                 
726                 //
727                 // Look for and save each tenants default Admin and Reader roles
728                 //
729                 final String getRoleCSIDSql = "SELECT csid from roles WHERE tenant_id=? and rolename=?";
730                 pstmt = conn.prepareStatement(getRoleCSIDSql); // create a statement
731                 for (String tenantId : tenantInfo.keySet()) {
732                         //
733                         // Look for the default Admin role
734                         //
735                         pstmt.setString(1, tenantId);
736                         pstmt.setString(2, getDefaultAdminRole(tenantId));
737                         rs = pstmt.executeQuery();
738                         // extract data from the ResultSet
739                         if (!rs.next()) {
740                                 throw new RuntimeException("Cannot find role: " + getDefaultAdminRole(tenantId)
741                                         + " for tenant id: " + tenantId + " in roles!");
742                         }
743                         String tenantAdminRoleCSID = rs.getString(1); // First column (#1) is the CSID
744                         tenantAdminRoleCSIDs.put(tenantId, tenantAdminRoleCSID);
745                         rs.close();
746                         rs = null;
747                         //
748                         // Look for the default Reader role
749                         //
750                         pstmt.setString(1, tenantId);                                           // set tenant_id param
751                         pstmt.setString(2, getDefaultReaderRole(tenantId));     // set rolename param
752                         rs = pstmt.executeQuery();
753                         // extract data from the ResultSet
754                         if (!rs.next()) {
755                                 throw new RuntimeException("Cannot find role: " + getDefaultReaderRole(tenantId)
756                                                 + " for tenant id: " + tenantId + " in roles!");
757                         }
758                         String tenantReaderRoleCSID = rs.getString(1);
759                         tenantReaderRoleCSIDs.put(tenantId, tenantReaderRoleCSID);
760                         rs.close();
761                 }
762                 pstmt.close();
763         } catch(Exception e) {
764                 throw e;
765         } finally {
766                 if (stmt != null) stmt.close();
767                 if (pstmt != null) pstmt.close();
768         }
769         
770         return springAdminRoleCSID;
771     }
772
773     private static String findTenantManagerRole(Connection conn ) 
774                         throws SQLException, RuntimeException, Exception {
775                 String tenantMgrRoleCSID = null;
776         PreparedStatement pstmt = null;
777         try {
778                 String rolename = getQualifiedRoleName(AuthN.ALL_TENANTS_MANAGER_TENANT_ID, 
779                                 AuthN.ROLE_ALL_TENANTS_MANAGER);                
780                 pstmt = conn.prepareStatement(GET_TENANT_MGR_ROLE_SQL); // create a statement
781                 ResultSet rs = null;
782                 pstmt.setString(1, rolename);   // set rolename param
783                 rs = pstmt.executeQuery();
784                 if(rs.next()) {
785                         tenantMgrRoleCSID = rs.getString(1);
786                         if (logger.isDebugEnabled()) {
787                                 logger.debug("findTenantManagerRole found Tenant Mgr role: "
788                                                 +tenantMgrRoleCSID);
789                         }
790                 }
791                 rs.close();
792         } catch(Exception e) {
793                 throw e;
794         } finally {
795                 if(pstmt!=null)
796                         pstmt.close();
797         }
798         if(tenantMgrRoleCSID==null)
799                 throw new RuntimeException("findTenantManagerRole: Cound not find tenant Manager Role!");
800         return tenantMgrRoleCSID;
801     }
802
803     private static void bindAccountsToRoles(Connection conn,  DatabaseProductType databaseProductType,
804                 Hashtable<String, String> tenantInfo, ArrayList<String> usersInRepo,
805                 String springAdminRoleCSID,
806                 Hashtable<String, String> tenantAdminRoleCSIDs, Hashtable<String, String> tenantReaderRoleCSIDs,
807                 Hashtable<String, String> tenantAdminAcctCSIDs, Hashtable<String, String> tenantReaderAcctCSIDs) 
808                         throws SQLException, Exception {
809         // Sixth, bind the accounts to roles. If the users already existed,
810         // we'll assume they were set up correctly.
811         PreparedStatement pstmt = null;
812         try {
813                 String insertAccountRoleSQL;
814                 if (databaseProductType == DatabaseProductType.POSTGRESQL) {
815                         insertAccountRoleSQL = INSERT_ACCOUNT_ROLE_SQL_POSTGRES;
816                 } else {
817                         throw new Exception("Unrecognized database system.");
818                 }
819                 
820                 pstmt = conn.prepareStatement(insertAccountRoleSQL); // create a statement
821                 for (String tId : tenantInfo.keySet()) {
822                         String adminUserId = getDefaultAdminUserID(tenantInfo.get(tId));
823                         if (!usersInRepo.contains(adminUserId)) {
824                                 String adminAcct = tenantAdminAcctCSIDs.get(tId);
825                                 String adminRoleId = tenantAdminRoleCSIDs.get(tId);
826                                 pstmt.setString(1, adminAcct);          // set acct CSID param
827                                 pstmt.setString(2, adminUserId);        // set user_id param
828                                 pstmt.setString(3, adminRoleId);        // set role_id param
829                                 pstmt.setString(4, getDefaultAdminRole(tId));   // set rolename param
830                                 pstmt.executeUpdate();
831                                 //
832                                 // Now add the Spring Admin Role to the admin accounts
833                                 //
834                                 pstmt.setString(3, springAdminRoleCSID);        // set role_id param
835                                 pstmt.setString(4, AuthN.ROLE_SPRING_ADMIN_NAME);               // set rolename param
836                                 pstmt.executeUpdate();
837                         }
838                         String readerUserId = getDefaultReaderUserID(tenantInfo.get(tId));
839                         if (!usersInRepo.contains(readerUserId)) {
840                                 String readerAcct = tenantReaderAcctCSIDs.get(tId);
841                                 String readerRoleId = tenantReaderRoleCSIDs.get(tId);
842                                 pstmt.setString(1, readerAcct);         // set acct CSID param
843                                 pstmt.setString(2, readerUserId);       // set user_id param
844                                 pstmt.setString(3, readerRoleId);       // set role_id param
845                                 pstmt.setString(4, getDefaultReaderRole(tId));  // set rolename param
846                                 pstmt.executeUpdate();
847                         }
848                 }
849                 pstmt.close();
850         } catch(Exception e) {
851                 throw e;
852         } finally {
853                 if (pstmt!=null) {
854                         pstmt.close();
855                 }
856         }
857     }
858     
859     private static void bindTenantManagerAccountRole(Connection conn,  DatabaseProductType databaseProductType,
860                 String tenantManagerUserID, String tenantManagerAccountID, String tenantManagerRoleID, String tenantManagerRoleName ) 
861                         throws SQLException, Exception {
862         PreparedStatement pstmt = null;
863         try {
864                 String insertAccountRoleSQL;
865                 if (databaseProductType == DatabaseProductType.MYSQL) {
866                         insertAccountRoleSQL = INSERT_ACCOUNT_ROLE_SQL_MYSQL;
867                 } else if (databaseProductType == DatabaseProductType.POSTGRESQL) {
868                         insertAccountRoleSQL = INSERT_ACCOUNT_ROLE_SQL_POSTGRES;
869                 } else {
870                         throw new Exception("Unrecognized database system.");
871                 }
872                 if (logger.isDebugEnabled()) {
873                         logger.debug("bindTenantManagerAccountRole binding account to role with SQL:\n"
874                                         +insertAccountRoleSQL);
875                 }
876                 pstmt = conn.prepareStatement(insertAccountRoleSQL); // create a statement
877                 pstmt.setString(1, tenantManagerAccountID);             // set acct CSID param
878                 pstmt.setString(2, tenantManagerUserID);        // set user_id param
879                 pstmt.setString(3, tenantManagerRoleID);        // set role_id param
880                 pstmt.setString(4, tenantManagerRoleName);      // set rolename param
881                 pstmt.executeUpdate();
882                 
883                 /* At this point, tenant manager should not need the Spring Admin Role
884                         pstmt.setString(3, springAdminRoleCSID);        // set role_id param
885                         pstmt.setString(4, SPRING_ADMIN_ROLE);          // set rolename param
886                         if (logger.isDebugEnabled()) {
887                                 logger.debug("createDefaultAccounts binding account: "
888                                                 +adminUserId+" to Spring Admin role: "+springAdminRoleCSID);
889                         }
890                         pstmt.executeUpdate();
891                 */
892                 
893                 pstmt.close();
894         } catch(Exception e) {
895                 throw e;
896         } finally {
897                 if(pstmt!=null)
898                         pstmt.close();
899         }
900     }
901     
902     /*
903      * Using the tenant bindings, ensure there are corresponding Tenant records (db columns).
904      */
905     //FIXME: This code should be using JPA objects and JPATransactionContext, not raw SQL.
906     public static void createTenants(
907                 TenantBindingConfigReaderImpl tenantBindingConfigReader,
908                 DatabaseProductType databaseProductType,
909                         String cspaceDatabaseName) throws Exception {
910                 logger.debug("ServiceMain.createTenants starting...");
911                 Hashtable<String, String> tenantInfo = getTenantNamesFromConfig(tenantBindingConfigReader);
912                 Connection conn = null;
913                 try {
914                         conn = getConnection(cspaceDatabaseName);
915                         ArrayList<String> existingTenants = compileExistingTenants(conn, tenantInfo);
916
917                         // Note that this only creates tenants not marked as "createDisabled"
918                         createMissingTenants(conn, tenantInfo, existingTenants);
919                 } catch (Exception e) {
920                         logger.debug("Exception in createTenants: " + e.getLocalizedMessage());
921                         throw e;
922                 } finally {
923                         try {
924                                 if (conn != null) {
925                                         conn.close();
926                                 }
927                         } catch (SQLException sqle) {
928                                 if (logger.isDebugEnabled()) {
929                                         logger.debug("SQL Exception closing statement/connection: " + sqle.getLocalizedMessage());
930                                 }
931                         }
932                 }
933         }
934     
935     /**
936      * 
937      * @param tenantBindingConfigReader
938      * @param databaseProductType
939      * @param cspaceDatabaseName
940      * @throws Exception
941      */
942     //FIXME: This code should be using the JPA objects and JPATransactionContext, not raw SQL.
943     public static void createDefaultAccounts(
944                 TenantBindingConfigReaderImpl tenantBindingConfigReader,
945                 DatabaseProductType databaseProductType,
946                 String cspaceDatabaseName) throws Exception {
947
948         logger.debug("ServiceMain.createDefaultAccounts starting...");
949         
950         Hashtable<String, String> tenantInfo = getTenantNamesFromConfig(tenantBindingConfigReader);
951         Connection conn = null;
952         // TODO - need to put in tests for existence first.
953         // We could just look for the accounts per tenant up front, and assume that
954         // the rest is there if the accounts are.
955         // Could add a sql script to remove these if need be - Spring only does roles, 
956         // and we're not touching that, so we could safely toss the 
957         // accounts, users, account-tenants, account-roles, and start over.
958         try {
959                 conn = getConnection(cspaceDatabaseName);
960                 
961                 ArrayList<String> usersInRepo = findOrCreateDefaultUsers(conn, tenantInfo);
962                 
963                 Hashtable<String, String> tenantAdminAcctCSIDs = new Hashtable<String, String>();
964                 Hashtable<String, String> tenantReaderAcctCSIDs = new Hashtable<String, String>();
965                 findOrCreateDefaultAccounts(conn, tenantInfo, usersInRepo,
966                                 tenantAdminAcctCSIDs, tenantReaderAcctCSIDs);
967
968                 bindDefaultAccountsToTenants(conn, databaseProductType, tenantInfo, usersInRepo,
969                                 tenantAdminAcctCSIDs, tenantReaderAcctCSIDs);
970                 
971                 Hashtable<String, String> tenantAdminRoleCSIDs = new Hashtable<String, String>();
972                 Hashtable<String, String> tenantReaderRoleCSIDs = new Hashtable<String, String>();
973                 String springAdminRoleCSID = findOrCreateDefaultRoles(conn, tenantInfo,
974                                 tenantAdminRoleCSIDs, tenantReaderRoleCSIDs);
975                 
976                 bindAccountsToRoles(conn,  databaseProductType,
977                                 tenantInfo, usersInRepo, springAdminRoleCSID,
978                                 tenantAdminRoleCSIDs, tenantReaderRoleCSIDs,
979                                 tenantAdminAcctCSIDs, tenantReaderAcctCSIDs);
980                 
981                 boolean createdTenantMgrAccount = findOrCreateTenantManagerUserAndAccount(conn);
982                 if (createdTenantMgrAccount) {
983                         // If we created the account, we need to create the bindings. Otherwise, assume they
984                         // are all set (from previous initialization).
985                         String tenantManagerRoleCSID = findTenantManagerRole(conn);
986                         bindTenantManagerAccountRole(conn, databaseProductType, 
987                                         TENANT_MANAGER_USER, AuthN.TENANT_MANAGER_ACCT_ID, 
988                                         tenantManagerRoleCSID, AuthN.ROLE_ALL_TENANTS_MANAGER);
989                 }
990         } catch (Exception e) {
991                         logger.debug("Exception in createDefaultAccounts: " + e.getLocalizedMessage());
992                 throw e;
993                 } finally {
994                         try {
995                                 if (conn != null) {
996                                         conn.close();
997                                 }
998                         } catch (SQLException sqle) {
999                                 if (logger.isDebugEnabled()) {
1000                                         logger.debug("SQL Exception closing statement/connection: " + sqle.getLocalizedMessage());
1001                                 }
1002                         }
1003                 }       
1004     }
1005     
1006     private static String getDefaultAdminRole(String tenantId) {
1007         return ROLE_PREFIX + tenantId + TENANT_ADMIN_ROLE_SUFFIX;
1008     }
1009     
1010     private static String getDefaultReaderRole(String tenantId) {
1011         return ROLE_PREFIX+tenantId+TENANT_READER_ROLE_SUFFIX;
1012     }
1013     
1014     private static String getDefaultAdminUserID(String tenantName) {
1015         return TENANT_ADMIN_ACCT_PREFIX + tenantName;
1016     }
1017     
1018     private static String getDefaultReaderUserID(String tenantName) {
1019         return TENANT_READER_ACCT_PREFIX + tenantName;
1020     }
1021     
1022         static private PermissionAction createPermissionAction(Permission perm,
1023                         ActionType actionType) {
1024         PermissionAction pa = new PermissionAction();
1025
1026             CSpaceAction action = URIResourceImpl.getAction(actionType);
1027             URIResourceImpl uriRes = new URIResourceImpl(perm.getTenantId(),
1028                     perm.getResourceName(), action);
1029             pa.setName(actionType);
1030             pa.setObjectIdentity(uriRes.getHashedId().toString());
1031             pa.setObjectIdentityResource(uriRes.getId());
1032             
1033             return pa;
1034         }
1035         
1036         private static HashSet<String> getTransitionVerbList(TenantBindingType tenantBinding, ServiceBindingType serviceBinding) {
1037                 HashSet<String> result = new HashSet<String>();
1038                 
1039                 TransitionDefList transitionDefList = getTransitionDefList(tenantBinding, serviceBinding);
1040         for (TransitionDef transitionDef : transitionDefList.getTransitionDef()) {
1041                 String transitionVerb = transitionDef.getName();
1042                 String[] tokens = transitionVerb.split("_");  // Split the verb into words.  The workflow verbs are compound words combined with the '_' character.
1043                 result.add(tokens[0]); // We only care about the first word.
1044         }
1045
1046         return result;
1047         }
1048         
1049         private static TransitionDefList getTransitionDefList(TenantBindingType tenantBinding, ServiceBindingType serviceBinding) {
1050                 TransitionDefList result = null;
1051                 try {
1052                         String serviceObjectName = serviceBinding.getObject().getName();
1053                         
1054                 @SuppressWarnings("rawtypes")
1055                         DocumentHandler docHandler = ServiceConfigUtils.createDocumentHandlerInstance(
1056                                 tenantBinding, serviceBinding);
1057                 Lifecycle lifecycle = docHandler.getLifecycle(serviceObjectName);
1058                 if (lifecycle != null) {
1059                         result = lifecycle.getTransitionDefList();
1060                 }
1061                 } catch (Exception e) {
1062                         // Ignore this exception and return an empty non-null TransitionDefList
1063                 }
1064                 
1065                 if (result == null) {
1066                         if (serviceBinding.getType().equalsIgnoreCase(ServiceBindingUtils.SERVICE_TYPE_SECURITY) == false) {
1067                                 logger.debug("Could not retrieve a lifecycle transition definition list from: "
1068                                                 + serviceBinding.getName()
1069                                                 + " with tenant ID = "
1070                                                 + tenantBinding.getId());
1071                         }
1072                         // return an empty list                 
1073                         result = new TransitionDefList();
1074                 } else {
1075                         logger.debug("Successfully retrieved a lifecycle transition definition list from: "
1076                                         + serviceBinding.getName()
1077                                         + " with tenant ID = "
1078                                         + tenantBinding.getId());
1079                 }
1080                 
1081                 return result;
1082         }
1083         
1084         /**
1085          * Creates the immutable workflow permission sets for the default admin and reader roles.
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
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                                                 HashSet<String> transitionVerbList = getTransitionVerbList(tenantBinding, serviceBinding);
1126                                                 for (String transitionVerb : transitionVerbList) {
1127                                                         //
1128                                                         // Create the permission for the admin role
1129                                                         Permission adminPerm = createWorkflowPermission(tenantBinding, serviceBinding, transitionVerb, ACTIONGROUP_CRUDL, true);
1130                                                         persist(jpaTransactionContext, adminPerm, adminRole, true, ACTIONGROUP_CRUDL);
1131                                                         //
1132                                                         // Create the permission for the read-only role
1133                                                         Permission readonlyPerm = createWorkflowPermission(tenantBinding, serviceBinding, transitionVerb, ACTIONGROUP_RL, true);                                                        
1134                                                         persist(jpaTransactionContext, readonlyPerm, readonlyRole, true, ACTIONGROUP_RL); // Persist/store the permission and permrole records and related Spring Security info
1135                                                 }
1136                                                 jpaTransactionContext.commitTransaction();
1137                                         } catch (IllegalStateException e) {
1138                                                 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.
1139                                         } catch (Exception x) {
1140                                                 jpaTransactionContext.markForRollback();
1141                                         }
1142                                 } else {
1143                                         logger.warning("AuthZ refresh service binding property is set to FALSE so default permissions will NOT be refreshed for: "
1144                                                         + serviceBinding.getName());
1145                                 }
1146                         }
1147                 }
1148         } catch (Exception e) {
1149                 jpaTransactionContext.markForRollback();
1150             logger.fine("Caught exception and rolling back permission creation: " + e.getMessage());
1151             throw e;
1152         }
1153     }
1154     
1155         private static void createMissingTenants(Connection conn, Hashtable<String, String> tenantInfo,
1156                 ArrayList<String> existingTenants) throws SQLException, Exception {
1157                 // Need to define and look for a createDisabled attribute in tenant config
1158         final String insertTenantSQL = 
1159                 "INSERT INTO tenants (id,name,authorities_initialized,disabled,created_at) VALUES (?,?,FALSE,FALSE,now())";
1160         PreparedStatement pstmt = null;
1161         try {
1162                 pstmt = conn.prepareStatement(insertTenantSQL); // create a statement
1163                 for(String tId : tenantInfo.keySet()) {
1164                         if(existingTenants.contains(tId)) {
1165                                 if (logger.isDebugEnabled()) {
1166                                         logger.debug("createMissingTenants: tenant exists (skipping): "
1167                                                         +tenantInfo.get(tId));
1168                                 }
1169                                 continue;
1170                         }
1171                         pstmt.setString(1, tId);                                        // set id param
1172                         pstmt.setString(2, tenantInfo.get(tId));        // set name param
1173                         if (logger.isDebugEnabled()) {
1174                                 logger.debug("createMissingTenants adding entry for tenant: "+tId);
1175                         }
1176                         pstmt.executeUpdate();
1177                 }
1178                 pstmt.close();
1179         } catch(Exception e) {
1180                 throw e;
1181         } finally {
1182                 if(pstmt!=null)
1183                         pstmt.close();
1184         }
1185     }
1186     
1187     public static String getPersistedMD5Hash(String tenantId, String cspaceDatabaseName) throws Exception {
1188         String result = null;
1189         
1190         // First find or create the tenants
1191         final String queryTenantSQL = String.format("SELECT id, name, config_md5hash FROM tenants WHERE id = '%s'", tenantId);
1192         
1193         Statement stmt = null;
1194         Connection conn;
1195         int rowCount = 0;
1196         try {
1197                         conn = getConnection(cspaceDatabaseName);
1198                 stmt = conn.createStatement();
1199                 ResultSet rs = stmt.executeQuery(queryTenantSQL);
1200                 while (rs.next()) {
1201                         if (rowCount > 0) {
1202                                 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'.",
1203                                                 tenantId, cspaceDatabaseName);
1204                                 throw new Exception(errMsg);
1205                         }
1206                         String tId = rs.getString("id");                // for debugging only
1207                         String tName = rs.getString("name");    // for debugging only
1208                         result = rs.getString("config_md5hash");
1209                         rowCount++;
1210                 }
1211                 rs.close();
1212         } catch(Exception e) {
1213                 throw e;
1214         } finally {
1215                 if (stmt != null) stmt.close();
1216         }
1217         
1218         return result;
1219     }
1220
1221         private static PermissionRoleRel findPermRoleRel(
1222                         JPATransactionContext jpaTransactionContext,
1223                         String permissionId,
1224                         String RoleId) {
1225         PermissionRoleRel result = null;
1226         
1227         try {
1228                 String whereClause = "where permissionId = :id and roleId = :roleId";
1229                 HashMap<String, Object> params = new HashMap<String, Object>();
1230                 params.put("id", permissionId);
1231                 params.put("roleId", RoleId);        
1232         
1233                 result = (PermissionRoleRel) JpaStorageUtils.getEntity(jpaTransactionContext,
1234                                 PermissionRoleRel.class.getCanonicalName(), whereClause, params);
1235         } catch (Exception e) {
1236                 //Do nothing. Will return null;
1237         }
1238                 
1239         return result;
1240     }
1241     
1242     /*
1243      * Persists the Permission, PermissionRoleRel, and Spring Security table entries all in one transaction
1244      */
1245     private static void persist(JPATransactionContext jpaTransactionContext, Permission permission, Role role, boolean enforceTenancy, ActionGroup actionGroup) throws Exception {
1246                 AuthorizationStore authzStore = new AuthorizationStore();
1247                 // First persist the Permission record
1248                 authzStore.store(jpaTransactionContext, permission);
1249                 
1250                 // If the PermRoleRel doesn't already exists then relate the permission and the role in a new PermissionRole (the service payload)
1251                 // Create a PermissionRoleRel (the database relation table for the permission and role)
1252                 PermissionRoleRel permRoleRel = findPermRoleRel(jpaTransactionContext, permission.getCsid(), role.getCsid());
1253                 if (permRoleRel == null) {
1254                         PermissionRole permRole = createPermissionRole(permission, role, enforceTenancy);
1255                 List<PermissionRoleRel> permRoleRels = new ArrayList<PermissionRoleRel>();
1256                 PermissionRoleUtil.buildPermissionRoleRel(jpaTransactionContext, permRole, SubjectType.ROLE, permRoleRels,
1257                                 false /*not for delete*/, role.getTenantId());
1258                 for (PermissionRoleRel prr : permRoleRels) {
1259                     authzStore.store(jpaTransactionContext, prr);
1260                 }
1261                         Profiler profiler = new Profiler(AuthorizationCommon.class, 2);
1262                         profiler.start();
1263                         // Add a corresponding entry in the Spring Security Tables
1264                         addPermissionsForUri(jpaTransactionContext, permission, permRole);
1265                         profiler.stop();
1266                         logger.debug("Finished full perm generation for "
1267                                         + ":" + permission.getTenantId()
1268                                         + ":" + permission.getResourceName()
1269                                         + ":" + actionGroup.getName()
1270                                         + ":" + profiler.getCumulativeTime());
1271                 }
1272         
1273     }
1274         
1275         public static boolean hasTokenExpired(EmailConfig emailConfig, Token token) throws NoSuchAlgorithmException {
1276                 boolean result = false;
1277                 
1278                 int maxConfigSeconds = emailConfig.getPasswordResetConfig().getTokenExpirationSeconds().intValue();
1279                 int maxTokenSeconds = token.getExpireSeconds().intValue();
1280                 
1281                 long createdTime = token.getCreatedAtItem().getTime();          
1282                 long configExpirationTime = createdTime + maxConfigSeconds * 1000;              // the current tenant config for how long a token stays valid
1283                 long tokenDefinedExirationTime = createdTime + maxTokenSeconds * 1000;  // the tenant config for how long a token stays valid when the token was created.
1284                 
1285                 if (configExpirationTime != tokenDefinedExirationTime) {
1286                         String msg = String.format("The configured expiration time for the token = '%s' changed from when the token was created.",
1287                                         token.getId());
1288                         logger.warn(msg);
1289                 }
1290                 //
1291                 // Note: the current tenant bindings config for expiration takes precedence over the config used to create the token.
1292                 //
1293                 if (System.currentTimeMillis() >= configExpirationTime) {
1294                         result = true;
1295                 }
1296                 
1297                 return result;
1298         }
1299                 
1300         /*
1301          * Validate that the password reset configuration is correct.
1302          */
1303         private static String validatePasswordResetConfig(PasswordResetConfig passwordResetConfig) {
1304                 String result = null;
1305                 
1306                 if (passwordResetConfig != null) {
1307                         result = passwordResetConfig.getMessage();
1308                         if (result == null || result.length() == 0) {
1309                                 result = DEFAULT_PASSWORD_RESET_EMAIL_MESSAGE;
1310                                 logger.warn("Could not find a password reset message in the tenant's configuration.  Using the default one");
1311                         }
1312                         
1313                         if (result.contains("{{link}}") == false) {
1314                                 logger.warn("The tenant's password reset message does not contain a required '{{link}}' marker.");
1315                                 result = null;
1316                         }
1317                         
1318                         if (passwordResetConfig.getLoginpage() == null || passwordResetConfig.getLoginpage().trim().isEmpty()) {
1319                                 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'.");
1320                                 result = null;
1321                         }
1322                         
1323                     String subject = passwordResetConfig.getSubject();
1324                     if (subject == null || subject.trim().isEmpty()) {
1325                         passwordResetConfig.setSubject(DEFAULT_PASSWORD_RESET_EMAIL_SUBJECT);
1326                     }
1327
1328                 }
1329                 
1330                 return result;
1331         }
1332         
1333         /*
1334          * Generate a password reset message. Embeds an authorization token to reset a user's password.
1335          */
1336         public static String generatePasswordResetEmailMessage(EmailConfig emailConfig, AccountListItem accountListItem, Token token) throws Exception {
1337                 String result = null;
1338                 
1339                 result = validatePasswordResetConfig(emailConfig.getPasswordResetConfig());
1340                 if (result == null) {
1341                         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.",
1342                                         token.getTenantId(), accountListItem.getEmail());
1343                         throw new Exception(errMsg);
1344                 }
1345                 
1346                 String link = emailConfig.getBaseurl() + emailConfig.getPasswordResetConfig().getLoginpage() + "?token=" + token.getId();
1347                 result = result.replaceAll("\\{\\{link\\}\\}", link);
1348                 
1349                 if (result.contains("{{greeting}}")) {
1350                         String greeting = accountListItem.getScreenName();
1351                         result = result.replaceAll("\\{\\{greeting\\}\\}", greeting);
1352                         result = result.replaceAll("\\\\n", "\\\n");
1353                         result = result.replaceAll("\\\\r", "\\\r");
1354                 }                       
1355                 
1356                 return result;
1357         }
1358
1359         public static void persistTenantBindingsMD5Hash(TenantBindingConfigReaderImpl tenantBindingConfigReader,
1360                         DatabaseProductType databaseProductType, String cspaceDatabaseName) throws Exception {
1361                 // Need to define and look for a createDisabled attribute in tenant config
1362                 String updateTableSQL = "UPDATE tenants SET config_md5hash = ? WHERE id = ?";
1363
1364         Connection conn;
1365         PreparedStatement pstmt = null;
1366         try {
1367                         conn = getConnection(cspaceDatabaseName);
1368                 pstmt = conn.prepareStatement(updateTableSQL); // create a statement
1369                 for (String tId : AuthorizationCommon.tenantConfigMD5HashTable.keySet()) {
1370                         pstmt.setString(1, AuthorizationCommon.getTenantConfigMD5Hash(tId));
1371                         pstmt.setString(2, tId);
1372                         if (logger.isDebugEnabled()) {
1373                                 logger.debug("createMissingTenants adding entry for tenant: " + tId);
1374                         }
1375                         pstmt.executeUpdate();
1376                 }
1377                 pstmt.close();
1378         } catch(Exception e) {
1379                 throw e;
1380         } finally {
1381                 if (pstmt!=null) pstmt.close();
1382         }
1383     }
1384 }