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}