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                deleteIdentity.setString("id", id);
160                deleteIdentity.addBatch();
161
162                deleteAttrs.setString("id", id);
163                deleteAttrs.addBatch();
164
165                deleteRolesStatement.setString("id", id);
166                deleteRolesStatement.addBatch();
167
168                insertIdentityStatement.setString("id", id);
169                insertIdentityStatement.setString("name", name);
170                insertIdentityStatement.setString("type", type);
171                insertIdentityStatement.setString("firstname", firstname);
172                insertIdentityStatement.setString("lastname", lastname);
173                insertIdentityStatement.setString("email", email);
174                if (managerId != null) {
175                    insertIdentityStatement.setString("managerId", managerId);
176                } else {
177                    insertIdentityStatement.setNull("managerId", Types.VARCHAR);
178                }
179                if (administratorId != null) {
180                    insertIdentityStatement.setString("administratorId", administratorId);
181                } else {
182                    insertIdentityStatement.setNull("administratorId", Types.VARCHAR);
183                }
184
185                addCommonDateFields(insertIdentityStatement, exportDate, created, modified, lastRefresh);
186
187                insertIdentityStatement.addBatch();
188
189                if (attributes != null) {
190                    for (ObjectAttribute attribute : identityConfig.getObjectAttributes()) {
191                        String attrName = attribute.getName();
192                        Object value = attributes.get(attrName);
193                        if (!Utilities.isNothing(value)) {
194                            // TODO - do we want to filter more precisely here? e.g., exclude SSN for certain people?
195                            if (!excludeIdentityCols.contains(attrName)) {
196                                insertAttributeStatement.setString("id", id);
197                                insertAttributeStatement.setString("attributeName", attrName);
198
199                                if (attribute.isMulti()) {
200                                    Set<String> uniqueValues = new ListOrderedSet<>();
201                                    uniqueValues.addAll(Util.otol(value));
202                                    for (String val : uniqueValues) {
203                                        insertAttributeStatement.setString("attributeValue", Utilities.truncateStringToBytes(val, 4000, StandardCharsets.UTF_8));
204                                        insertAttributeStatement.addBatch();
205                                    }
206                                } else {
207                                    insertAttributeStatement.setString("attributeValue", Utilities.truncateStringToBytes(Util.otoa(value), 4000, StandardCharsets.UTF_8));
208                                    insertAttributeStatement.addBatch();
209                                }
210                            }
211                        }
212                    }
213                }
214
215                if (preferences != null) {
216                    List<RoleAssignment> assignments = (List<RoleAssignment>) preferences.get("roleAssignments");
217                    for(RoleAssignment ra : Util.safeIterable(assignments)) {
218                        if (!excludeRoles.contains(ra.getRoleName())) {
219                            Bundle role = cachedRoles.get(ra.getRoleName());
220
221                            if (role == null) {
222                                logger.warn("Identity " + id + " appears to have non-real RoleAssignment to " + ra.getRoleName());
223                            } else if (!excludeRoleTypes.contains(role.getType())) {
224                                insertRolesStatement.setString("id", id);
225                                insertRolesStatement.setString("roleName", role.getName());
226                                insertRolesStatement.setString("roleType", role.getType());
227                                insertRolesStatement.setDate("assignedOn", ra.getDate());
228                                insertRolesStatement.addBatch();
229                            }
230                        }
231                    }
232
233                    List<RoleDetection> detections = (List<RoleDetection>) preferences.get("roleDetections");
234                    for(RoleDetection rd : Util.safeIterable(detections)) {
235                        if (!excludeRoles.contains(rd.getRoleName())) {
236                            Bundle role = cachedRoles.get(rd.getRoleName());
237
238                            if (role == null) {
239                                logger.warn("Identity " + id + " appears to have non-real RoleDetection of " + rd.getRoleName());
240                            } else if (!excludeRoleTypes.contains(role.getType())) {
241                                insertRolesStatement.setString("id", id);
242                                insertRolesStatement.setString("roleName", role.getName());
243                                insertRolesStatement.setString("roleType", role.getType());
244                                insertRolesStatement.setDate("assignedOn", rd.getDate());
245                                insertRolesStatement.addBatch();
246                            }
247                        }
248                    }
249                }
250
251                if (batchCount++ > IDENTITY_BATCH_SIZE) {
252                    deleteAttrs.executeBatch();
253                    deleteRolesStatement.executeBatch();
254                    deleteIdentity.executeBatch();
255                    insertIdentityStatement.executeBatch();
256                    insertAttributeStatement.executeBatch();
257                    insertRolesStatement.executeBatch();
258
259                    connection.commit();
260                    batchCount = 0;
261                }
262
263                int currentCount = totalCount.incrementAndGet();
264                if ((currentCount % 100) == 0) {
265                    TaskUtil.withLockedPartitionResult(monitor, (partitionResult) -> {
266                        monitor.updateProgress(partitionResult, "Processed " + currentCount + " of " + count + " identities", -1);
267                        partitionResult.setInt("exportedIdentities", currentCount);
268                    });
269                }
270            }
271
272            deleteAttrs.executeBatch();
273            deleteRolesStatement.executeBatch();
274            deleteIdentity.executeBatch();
275            insertIdentityStatement.executeBatch();
276            insertAttributeStatement.executeBatch();
277            insertRolesStatement.executeBatch();
278
279            connection.commit();
280
281        } catch(SQLException e) {
282            throw new GeneralException(e);
283        }
284    }
285
286}