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