001package com.identityworksllc.iiq.common.task.export; 002 003import com.identityworksllc.iiq.common.TaskUtil; 004import com.identityworksllc.iiq.common.Utilities; 005import com.identityworksllc.iiq.common.query.NamedParameterStatement; 006import org.apache.commons.collections4.set.ListOrderedSet; 007import org.apache.commons.logging.Log; 008import sailpoint.api.IncrementalObjectIterator; 009import sailpoint.api.IncrementalProjectionIterator; 010import sailpoint.api.SailPointContext; 011import sailpoint.object.*; 012import sailpoint.tools.GeneralException; 013import sailpoint.tools.Util; 014 015import java.sql.Connection; 016import java.sql.SQLException; 017import java.sql.Types; 018import java.util.*; 019import java.util.concurrent.atomic.AtomicInteger; 020 021public class ExportIdentitiesPartition extends ExportPartition { 022 023 private static final String DELETE_IDENTITY = 024 "delete from de_identity where id = :id"; 025 026 private static final String DELETE_IDENTITY_ATTRS = 027 "delete from de_identity_attr where id = :id"; 028 029 private static final String DELETE_IDENTITY_ROLES = 030 "delete from de_identity_roles where id = :id"; 031 032 public static final int IDENTITY_BATCH_SIZE = 50; 033 034 private static final String INSERT_IDENTITY = 035 "insert into de_identity " + 036 "( id, name, type, firstname, lastname, email, manager_id, administrator_id, created, modified, last_refresh, de_timestamp ) " + 037 "values ( :id, :name, :type, :firstname, :lastname, :email, :managerId, :administratorId, :created, :modified, :lastRefresh, :now )"; 038 039 private static final String INSERT_IDENTITY_ATTR = 040 "insert into de_identity_attr ( id, attribute_name, attribute_value ) values ( :id, :attributeName, :attributeValue )"; 041 042 private static final String INSERT_IDENTITY_ROLES = 043 "insert into de_identity_roles ( id, role_name, role_type, role_date ) values ( :id, :roleName, :roleType, :assignedOn )"; 044 045 046 /** 047 * Exports the identities identified by the filters 048 * 049 * @param context The context 050 * @param connection The connection to the target database 051 * @param logger The logger 052 * @throws GeneralException if anything fails during execution 053 */ 054 protected void export(SailPointContext context, Connection connection, Log logger) throws GeneralException { 055 Set<String> excludeRoles = new HashSet<>(); 056 if (configuration.containsAttribute("excludeRoles")) { 057 List<String> cols = configuration.getStringList("excludeRoles"); 058 if (cols != null) { 059 excludeRoles.addAll(cols); 060 } 061 } 062 063 Set<String> excludeRoleTypes = new HashSet<>(); 064 if (configuration.containsAttribute("excludeRoleTypes")) { 065 List<String> cols = configuration.getStringList("excludeRoleTypes"); 066 if (cols != null) { 067 excludeRoleTypes.addAll(cols); 068 } 069 } 070 071 TaskUtil.withLockedPartitionResult(monitor, (partitionResult) -> { 072 monitor.updateProgress(partitionResult, "Caching role data", -1); 073 }); 074 075 Map<String, Bundle> cachedRoles = new HashMap<>(); 076 077 Iterator<Bundle> allRoles = context.search(Bundle.class, new QueryOptions()); 078 while(allRoles.hasNext()) { 079 Bundle b = allRoles.next(); 080 cachedRoles.put(b.getName(), Utilities.detach(context, b)); 081 } 082 083 Set<String> excludeIdentityCols = new HashSet<>(); 084 if (configuration.containsAttribute("excludeIdentityColumns")) { 085 List<String> cols = configuration.getStringList("excludeIdentityColumns"); 086 if (cols != null) { 087 excludeIdentityCols.addAll(cols); 088 } 089 } 090 091 Date exportDate = new Date(exportTimestamp); 092 093 QueryOptions qo = new QueryOptions(); 094 qo.addFilter(Filter.compile(filterString)); 095 qo.addFilter(Filter.or(Filter.gt("created", new Date(cutoffDate)), Filter.gt("modified", new Date(cutoffDate)))); 096 qo.setCacheResults(false); 097 qo.setTransactionLock(false); 098 099 TaskUtil.withLockedPartitionResult(monitor, (partitionResult) -> { 100 monitor.updateProgress(partitionResult, "Executing query", -1); 101 }); 102 103 int batchCount = 0; 104 final AtomicInteger totalCount = new AtomicInteger(); 105 106 ObjectConfig identityConfig = Identity.getObjectConfig(); 107 108 List<String> fields = new ArrayList<>(); 109 fields.add("id"); // 0 110 fields.add("name"); 111 fields.add("type"); 112 fields.add("firstname"); 113 fields.add("lastname"); 114 fields.add("displayName"); // 5 115 fields.add("email"); 116 fields.add("manager.id"); 117 fields.add("administrator.id"); 118 fields.add("created"); 119 fields.add("modified"); // 10 120 fields.add("lastRefresh"); 121 fields.add("attributes"); 122 fields.add("preferences"); 123 124 long count = context.countObjects(Identity.class, qo); 125 126 IncrementalProjectionIterator identities = new IncrementalProjectionIterator(context, Identity.class, qo, fields); 127 128 try (NamedParameterStatement deleteAttrs = new NamedParameterStatement(connection, DELETE_IDENTITY_ATTRS); NamedParameterStatement deleteIdentity = new NamedParameterStatement(connection, DELETE_IDENTITY); NamedParameterStatement insertIdentityStatement = new NamedParameterStatement(connection, INSERT_IDENTITY); NamedParameterStatement insertAttributeStatement = new NamedParameterStatement(connection, INSERT_IDENTITY_ATTR); NamedParameterStatement deleteRolesStatement = new NamedParameterStatement(connection, DELETE_IDENTITY_ROLES); NamedParameterStatement insertRolesStatement = new NamedParameterStatement(connection, INSERT_IDENTITY_ROLES)) { 129 while (identities.hasNext()) { 130 if (isTerminated()) { 131 logger.info("Thread has been terminated; exiting cleanly"); 132 break; 133 } 134 135 Object[] identity = identities.next(); 136 137 String id = Util.otoa(identity[0]); 138 String name = Util.otoa(identity[1]); 139 String type = Util.otoa(identity[2]); 140 String firstname = Util.otoa(identity[3]); 141 String lastname = Util.otoa(identity[4]); 142 String displayName = Util.otoa(identity[5]); 143 String email = Util.otoa(identity[6]); 144 String managerId = Util.otoa(identity[7]); 145 String administratorId = Util.otoa(identity[8]); 146 147 Date created = (Date) identity[9]; 148 Date modified = (Date) identity[10]; 149 Date lastRefresh = (Date) identity[11]; 150 151 Attributes<String, Object> attributes = (Attributes<String, Object>) identity[12]; 152 Map<String, Object> preferences = (Map<String, Object>) identity[13]; 153 154 if (logger.isTraceEnabled()) { 155 logger.trace("Exporting Identity " + id + ": " + displayName); 156 } 157 158 deleteIdentity.setString("id", id); 159 deleteIdentity.addBatch(); 160 161 deleteAttrs.setString("id", id); 162 deleteAttrs.addBatch(); 163 164 deleteRolesStatement.setString("id", id); 165 deleteRolesStatement.addBatch(); 166 167 insertIdentityStatement.setString("id", id); 168 insertIdentityStatement.setString("name", name); 169 insertIdentityStatement.setString("type", type); 170 insertIdentityStatement.setString("firstname", firstname); 171 insertIdentityStatement.setString("lastname", lastname); 172 insertIdentityStatement.setString("email", email); 173 if (managerId != null) { 174 insertIdentityStatement.setString("managerId", managerId); 175 } else { 176 insertIdentityStatement.setNull("managerId", Types.VARCHAR); 177 } 178 if (administratorId != null) { 179 insertIdentityStatement.setString("administratorId", administratorId); 180 } else { 181 insertIdentityStatement.setNull("administratorId", Types.VARCHAR); 182 } 183 184 addCommonDateFields(insertIdentityStatement, exportDate, created, modified, lastRefresh); 185 186 insertIdentityStatement.addBatch(); 187 188 if (attributes != null) { 189 for (ObjectAttribute attribute : identityConfig.getObjectAttributes()) { 190 String attrName = attribute.getName(); 191 Object value = attributes.get(attrName); 192 if (!Utilities.isNothing(value)) { 193 // TODO - do we want to filter more precisely here? e.g., exclude SSN for certain people? 194 if (!excludeIdentityCols.contains(attrName)) { 195 insertAttributeStatement.setString("id", id); 196 insertAttributeStatement.setString("attributeName", attrName); 197 198 if (attribute.isMulti()) { 199 Set<String> uniqueValues = new ListOrderedSet<>(); 200 uniqueValues.addAll(Util.otol(value)); 201 for (String val : uniqueValues) { 202 insertAttributeStatement.setString("attributeValue", Util.truncate(val, 4000)); 203 insertAttributeStatement.addBatch(); 204 } 205 } else { 206 insertAttributeStatement.setString("attributeValue", Util.truncate(Util.otoa(value), 4000)); 207 insertAttributeStatement.addBatch(); 208 } 209 } 210 } 211 } 212 } 213 214 if (preferences != null) { 215 List<RoleAssignment> assignments = (List<RoleAssignment>) preferences.get("roleAssignments"); 216 for(RoleAssignment ra : Util.safeIterable(assignments)) { 217 if (!excludeRoles.contains(ra.getRoleName())) { 218 Bundle role = cachedRoles.get(ra.getRoleName()); 219 220 if (!excludeRoleTypes.contains(role.getType())) { 221 insertRolesStatement.setString("id", id); 222 insertRolesStatement.setString("roleName", role.getName()); 223 insertRolesStatement.setString("roleType", role.getType()); 224 insertRolesStatement.setDate("assignedOn", ra.getDate()); 225 insertRolesStatement.addBatch(); 226 } 227 } 228 } 229 230 List<RoleDetection> detections = (List<RoleDetection>) preferences.get("roleDetections"); 231 for(RoleDetection rd : Util.safeIterable(detections)) { 232 if (!excludeRoles.contains(rd.getRoleName())) { 233 Bundle role = cachedRoles.get(rd.getRoleName()); 234 235 if (!excludeRoleTypes.contains(role.getType())) { 236 insertRolesStatement.setString("id", id); 237 insertRolesStatement.setString("roleName", role.getName()); 238 insertRolesStatement.setString("roleType", role.getType()); 239 insertRolesStatement.setDate("assignedOn", rd.getDate()); 240 insertRolesStatement.addBatch(); 241 } 242 } 243 } 244 } 245 246 if (batchCount++ > IDENTITY_BATCH_SIZE) { 247 deleteAttrs.executeBatch(); 248 deleteRolesStatement.executeBatch(); 249 deleteIdentity.executeBatch(); 250 insertIdentityStatement.executeBatch(); 251 insertAttributeStatement.executeBatch(); 252 insertRolesStatement.executeBatch(); 253 254 connection.commit(); 255 batchCount = 0; 256 } 257 258 int currentCount = totalCount.incrementAndGet(); 259 if ((currentCount % 100) == 0) { 260 TaskUtil.withLockedPartitionResult(monitor, (partitionResult) -> { 261 monitor.updateProgress(partitionResult, "Processed " + currentCount + " of " + count + " identities", -1); 262 partitionResult.setInt("exportedIdentities", currentCount); 263 }); 264 } 265 } 266 267 deleteAttrs.executeBatch(); 268 deleteRolesStatement.executeBatch(); 269 deleteIdentity.executeBatch(); 270 insertIdentityStatement.executeBatch(); 271 insertAttributeStatement.executeBatch(); 272 insertRolesStatement.executeBatch(); 273 274 connection.commit(); 275 276 } catch(SQLException e) { 277 throw new GeneralException(e); 278 } 279 } 280 281}