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