001package com.identityworksllc.iiq.common; 002 003import com.identityworksllc.iiq.common.query.ContextConnectionWrapper; 004import sailpoint.api.Identitizer; 005import sailpoint.api.ObjectUtil; 006import sailpoint.api.SailPointContext; 007import sailpoint.api.SailPointFactory; 008import sailpoint.object.Attributes; 009import sailpoint.object.Filter; 010import sailpoint.object.Identity; 011import sailpoint.object.ObjectAttribute; 012import sailpoint.object.ObjectConfig; 013import sailpoint.object.QueryOptions; 014import sailpoint.tools.GeneralException; 015import sailpoint.tools.Util; 016 017import java.sql.Connection; 018import java.sql.PreparedStatement; 019import java.sql.SQLException; 020import java.util.ArrayList; 021import java.util.Iterator; 022import java.util.List; 023import java.util.Map; 024 025/** 026 * Utilities for handling Identity operations 027 * 028 * TODO javadocs 029 */ 030public class BaseIdentityUtilities extends AbstractBaseUtility { 031 032 public BaseIdentityUtilities(SailPointContext context) { 033 super(context); 034 } 035 036 /** 037 * Gets the default set of refresh options, with or without process-events 038 * @param shouldProcessEvents True if we should also process events, false if not 039 * @return A new Attributes with the default set of refresh options 040 */ 041 public Attributes<String, Object> getDefaultRefreshOptions(boolean shouldProcessEvents) { 042 Attributes<String, Object> args = new Attributes<>(); 043 args.put(Identitizer.ARG_PROVISION, true); 044 args.put(Identitizer.ARG_CORRELATE_ENTITLEMENTS, true); 045 args.put(Identitizer.ARG_PROCESS_TRIGGERS, shouldProcessEvents); 046 args.put(Identitizer.ARG_PROMOTE_MANAGED_ATTRIBUTES, true); 047 args.put(Identitizer.ARG_REFRESH_ROLE_METADATA, true); 048 args.put(Identitizer.ARG_PROMOTE_ATTRIBUTES, true); 049 args.put(Identitizer.ARG_SYNCHRONIZE_ATTRIBUTES, true); 050 args.put(Identitizer.ARG_REFRESH_MANAGER_STATUS, true); 051 args.put(Identitizer.ARG_NO_RESET_NEEDS_REFRESH, true); 052 args.put(Identitizer.ARG_REFRESH_PROVISIONING_REQUESTS, true); 053 args.put(Identitizer.ARG_CHECK_HISTORY, true); 054 return args; 055 } 056 057 /** 058 * Returns true if the user has at least one of the detected role 059 * @param identity The identity to check 060 * @param roleName The role name to look for 061 * @return true if the user has at least one detected role of this name 062 */ 063 public boolean hasDetectedRole(Identity identity, String roleName) { 064 long count = Utilities.safeStream(identity.getRoleDetections()).filter(rd -> Util.nullSafeEq(rd.getRoleName(), roleName)).count(); 065 return (count > 0); 066 } 067 068 /** 069 * Returns true if the user has the given role more than one time (either via assignment or detection or both) 070 * @param identity The identity to check 071 * @param roleName The role name to look for 072 * @return true if the user has at least two assigned/detected roles of this name 073 */ 074 public boolean hasMultiple(Identity identity, String roleName) { 075 long assignedCount = Utilities.safeStream(identity.getRoleAssignments()).filter(rd -> Util.nullSafeEq(rd.getRoleName(), roleName)).count(); 076 long detectedCount = Utilities.safeStream(identity.getRoleAssignments()).filter(rd -> Util.nullSafeEq(rd.getRoleName(), roleName)).count(); 077 return (assignedCount + detectedCount) > 1; 078 } 079 080 /** 081 * Transforms the existing Map in place by replacing attributes of type Secret with asterisks 082 * @param attributes The attribute map to modify 083 */ 084 public void maskSecretAttributes(Map<String, Object> attributes) { 085 ObjectConfig identityObjectConfig = Identity.getObjectConfig(); 086 for(ObjectAttribute attribute : identityObjectConfig.getObjectAttributes()) { 087 if (attribute.getType().equals(ObjectAttribute.TYPE_SECRET)) { 088 if (attributes.containsKey(attribute.getName())) { 089 attributes.put(attribute.getName(), "********"); 090 } 091 } 092 } 093 } 094 095 public List<Object[]> recursivelyExplodeHierarchy(Identity parent) throws GeneralException { 096 return recursivelyExplodeHierarchy(parent.getId(), "manager"); 097 } 098 099 public List<Object[]> recursivelyExplodeHierarchy(String parent, String attribute) throws GeneralException { 100 List<Object[]> outputBucket = new ArrayList<>(); 101 QueryOptions qo = new QueryOptions(); 102 qo.addFilter(Filter.eq(attribute, parent)); 103 List<String> props = new ArrayList<>(); 104 props.add("id"); 105 props.add("name"); 106 Iterator<Object[]> subordinates = context.search(Identity.class, qo, props); 107 if (subordinates != null) { 108 try { 109 while (subordinates.hasNext()) { 110 Object[] so = subordinates.next(); 111 outputBucket.add(so); 112 outputBucket.addAll(recursivelyExplodeHierarchy((String)so[0], attribute)); 113 } 114 } finally { 115 Util.flushIterator(subordinates); 116 } 117 } 118 return outputBucket; 119 } 120 121 public List<Identity> recursivelyExplodeWorkgroup(Identity possibleWorkgroup) throws GeneralException { 122 List<Identity> identities = new ArrayList<>(); 123 if (!possibleWorkgroup.isWorkgroup()) { 124 identities.add(possibleWorkgroup); 125 return identities; 126 } 127 List<String> props = new ArrayList<>(); 128 props.add("id"); 129 Iterator<Object[]> members = ObjectUtil.getWorkgroupMembers(context, possibleWorkgroup, props); 130 try { 131 while (members.hasNext()) { 132 Object[] dehydrated = members.next(); 133 Identity hydrated = ObjectUtil.getIdentityOrWorkgroup(context, (String) dehydrated[0]); 134 if (hydrated.isWorkgroup()) { 135 identities.addAll(recursivelyExplodeWorkgroup(hydrated)); 136 } else { 137 identities.add(hydrated); 138 } 139 } 140 } finally { 141 Util.flushIterator(members); 142 } 143 return identities; 144 } 145 146 /** 147 * Performs a refresh with default options on the identity 148 * @param id The identity in question 149 * @throws GeneralException if any IIQ failure occurs 150 */ 151 public void refresh(Identity id) throws GeneralException { 152 refresh(id, false); 153 } 154 155 /** 156 * Performs a refresh with mostly-default options on the identity 157 * @param id The identity to target 158 * @param shouldProcessEvents if true, processEvents will also be added 159 * @throws GeneralException if any IIQ failure occurs 160 */ 161 public void refresh(Identity id, boolean shouldProcessEvents) throws GeneralException { 162 Identity reloaded = context.getObjectById(Identity.class, id.getId()); 163 164 Attributes<String, Object> args = getDefaultRefreshOptions(shouldProcessEvents); 165 166 refresh(reloaded, args); 167 } 168 169 /** 170 * Performs a refresh against the identity with the given arguments 171 * @param id The target identity 172 * @param args the refresh arguments 173 * @throws GeneralException if any IIQ failure occurs 174 */ 175 public void refresh(Identity id, Map<String, Object> args) throws GeneralException { 176 Attributes<String, Object> attributes = new Attributes<>(); 177 attributes.putAll(args); 178 179 Identitizer identitizer = new Identitizer(context, attributes); 180 identitizer.refresh(id); 181 } 182 183 /** 184 * Attempt to do a best effort rename of a user. Note that this will not catch usernames stored in: 185 * 186 * (1) ProvisioningPlan objects 187 * (2) Running workflow variables 188 * (3) 189 * 190 * @param target The Identity object to rename 191 * @param newName The new name of the identity 192 * @throws GeneralException if any renaming failures occur 193 */ 194 public void rename(Identity target, String newName) throws GeneralException { 195 // TODO: CertificationDefinition selfCertificationViolationOwner 196 // TODO there is probably more to do around certifications (e.g. Certification.getCertifiers()) 197 // CertificationItem has a getIdentity() but it depends on CertificationEntity, so we're fine 198 String[] queries = new String[] { 199 "update spt_application_activity set identity_name = ? where identity_name = ?", 200 // This needs to run twice, once for MSMITH and once for Identity:MSMITH 201 "update spt_audit_event set target = ? where target = ?", 202 "update spt_audit_event set source = ? where source = ?", 203 "update spt_identity_request set owner_name = ? where owner_name = ?", // TODO approver_name? 204 "update spt_identity_snapshot set identity_name = ? where identity_name = ?", 205 "update spt_provisioning_transaction set identity_name = ? where identity_name = ?", 206 "update spt_certification set creator = ? where creator = ?", 207 "update spt_certification set manager = ? where manager = ?", 208 // Oddly, this is actually the name, not the ID 209 "update spt_certification_entity set identity_id = ? where identity_id = ?", 210 "update spt_certification_item set target_name = ? where target_name = ?", 211 "update spt_identity_history_item set actor = ? where actor = ?", 212 "update spt_task_result set launcher = ? where launcher = ?", 213 214 }; 215 SailPointContext privateContext = SailPointFactory.createPrivateContext(); 216 try { 217 Identity privateIdentity = privateContext.getObjectById(Identity.class, target.getId()); 218 privateIdentity.setName(newName); 219 privateContext.saveObject(privateIdentity); 220 privateContext.commitTransaction(); 221 222 try (Connection db = ContextConnectionWrapper.getConnection(privateContext)) { 223 try { 224 db.setAutoCommit(false); 225 for (String query : queries) { 226 try (PreparedStatement stmt = db.prepareStatement(query)) { 227 stmt.setString(1, newName); 228 stmt.setString(2, target.getName()); 229 stmt.executeUpdate(); 230 } 231 } 232 db.commit(); 233 } finally { 234 db.setAutoCommit(true); 235 } 236 } catch(SQLException e) { 237 throw new GeneralException(e); 238 } 239 240 } catch(GeneralException e) { 241 privateContext.rollbackTransaction(); 242 } finally { 243 SailPointFactory.releasePrivateContext(privateContext); 244 } 245 246 } 247 248}