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