001package com.identityworksllc.iiq.common.task;
002
003import com.identityworksllc.iiq.common.iterators.BatchingIterator;
004import org.apache.commons.logging.Log;
005import org.apache.commons.logging.LogFactory;
006import sailpoint.api.Identitizer;
007import sailpoint.api.SailPointContext;
008import sailpoint.object.Attributes;
009import sailpoint.object.Filter;
010import sailpoint.object.GroupDefinition;
011import sailpoint.object.Identity;
012import sailpoint.object.QueryOptions;
013import sailpoint.object.Source;
014import sailpoint.object.TaskResult;
015import sailpoint.object.TaskSchedule;
016import sailpoint.task.AbstractTaskExecutor;
017import sailpoint.task.TaskMonitor;
018import sailpoint.tools.GeneralException;
019import sailpoint.tools.Message;
020import sailpoint.tools.Util;
021
022import java.util.ArrayList;
023import java.util.HashMap;
024import java.util.HashSet;
025import java.util.Iterator;
026import java.util.List;
027import java.util.Map;
028import java.util.Set;
029import java.util.concurrent.atomic.AtomicBoolean;
030import java.util.concurrent.atomic.AtomicInteger;
031
032import static com.identityworksllc.iiq.common.CommonConstants.ARG_STRING_FALSE;
033import static com.identityworksllc.iiq.common.CommonConstants.ARG_STRING_TRUE;
034
035/**
036 * Assigned role differences detection task. It will report on changes only,
037 * refresh all users with the changes, or save a big list of IDs to a
038 * specific population (or all three).
039 */
040public class AssignedDetectedRoleDifferenceCleanup extends AbstractTaskExecutor {
041
042    /**
043     * The HQL to run to detect differences
044     */
045    private final static String HQL = "select i.id, iar.name, req.name \n" +
046            "from Identity i\n" +
047            "inner join i.assignedRoles iar\n" +
048            "inner join iar.requirements req\n" +
049            "where\n" +
050            "not exists (\n" +
051            "      select 1 from i.bundles b where b.name = req.name\n" +
052            ")\n";
053
054    /**
055     * Logger
056     */
057    private final Log log;
058
059    /**
060     * Flag indicating that the task is terminated
061     */
062    private final AtomicBoolean terminated;
063
064    /**
065     * Constructor
066     */
067    public AssignedDetectedRoleDifferenceCleanup() {
068        this.terminated = new AtomicBoolean(false);
069        this.log = LogFactory.getLog(AssignedDetectedRoleDifferenceCleanup.class);
070    }
071
072    /**
073     * Executor for this task: scans for users missing required IT roles and either
074     * refreshes them or reports on them.
075     *
076     * @param context The context from IIQ
077     * @param taskSchedule The task schedule
078     * @param taskResult The task result
079     * @param attributes The task attributes
080     * @throws Exception if anything fails
081     */
082    @Override
083    public void execute(SailPointContext context, TaskSchedule taskSchedule, TaskResult taskResult, Attributes<String, Object> attributes) throws Exception {
084        boolean doRefresh = attributes.getBoolean("doRefresh", false);
085        boolean doReport = attributes.getBoolean("doReport", true);
086
087        String populationName = attributes.getString("savePopulationName");
088
089        TaskMonitor monitor = new TaskMonitor(context, taskResult);
090
091        AtomicInteger missingRoles = new AtomicInteger();
092
093        log.info("Running HQL: " + HQL);
094        QueryOptions qo = new QueryOptions();
095        Iterator<Object[]> results = context.search(Identity.class, qo, HQL);
096
097        Set<String> identitiesToRefresh = new HashSet<>();
098
099        List<Map<String, Object>> resultList = new ArrayList<>();
100
101        while(results.hasNext()) {
102            Object[] row = results.next();
103
104            String id = Util.otoa(row[0]);
105            String assignedRoleName = Util.otoa(row[1]);
106            String missingRequirement = Util.otoa(row[2]);
107
108            Map<String, Object> logData = new HashMap<>();
109            logData.put("identity", id);
110            logData.put("businessRole", assignedRoleName);
111            logData.put("requiredRoleMissing", missingRequirement);
112
113            if (log.isDebugEnabled()) {
114                log.debug("Missing IT role for assigned Business role: " + logData);
115            }
116
117            resultList.add(logData);
118
119            identitiesToRefresh.add(id);
120
121            monitor.updateProgress("Processing Identity " + id);
122
123            missingRoles.incrementAndGet();
124
125            if (terminated.get()) {
126                Util.flushIterator(results);
127                break;
128            }
129        }
130
131        // Constructs a big gross population containing all of the IDs of all users with
132        // missing entitlements.
133        if (Util.isNotNullOrEmpty(populationName)) {
134            log.info("Creating ID List population: " + populationName);
135
136            GroupDefinition groupDefinition = context.getObjectByName(GroupDefinition.class, populationName);
137            if (groupDefinition == null) {
138                groupDefinition = new GroupDefinition();
139                groupDefinition.setName(populationName);
140                groupDefinition.setPrivate(false);
141            }
142
143            try (BatchingIterator<String> iterator = new BatchingIterator<>(identitiesToRefresh.iterator(), 250)) {
144                List<Filter> idFilters = new ArrayList<>();
145
146                while (iterator.hasNext()) {
147                    List<String> batch = iterator.next();
148                    idFilters.add(Filter.in("id", batch));
149                }
150
151                groupDefinition.setFilter(Filter.or(idFilters));
152            }
153
154            if (!terminated.get()) {
155                context.saveObject(groupDefinition);
156            }
157        }
158
159        // Do refreshes if that's enabled
160        if (doRefresh && !terminated.get()) {
161            Attributes<String, Object> args = new Attributes<>();
162            args.put(Identitizer.ARG_PROVISION, ARG_STRING_TRUE);
163            args.put(Identitizer.ARG_CHECK_HISTORY, ARG_STRING_TRUE);
164            args.put(Identitizer.ARG_CORRELATE_ENTITLEMENTS, ARG_STRING_TRUE);
165            args.put(Identitizer.ARG_REFRESH_IDENTITY_ENTITLEMENTS, ARG_STRING_TRUE);
166            args.put(Identitizer.ARG_PROCESS_TRIGGERS, ARG_STRING_FALSE);
167            args.put(Identitizer.ARG_REFRESH_PROVISIONING_REQUESTS, ARG_STRING_TRUE);
168            args.put(Identitizer.ARG_REFRESH_ROLE_METADATA, ARG_STRING_TRUE);
169            args.put(Identitizer.ARG_REFRESH_SOURCE, Source.Task);
170            args.put(Identitizer.ARG_REFRESH_SOURCE_WHO, context.getUserName());
171
172            Identitizer identitizer = new Identitizer(context, args);
173
174            for(String id : identitiesToRefresh) {
175                if (terminated.get()) {
176                    log.warn("Task was terminated");
177                    break;
178                }
179
180                Identity identity = context.getObject(Identity.class, id);
181
182                if (log.isDebugEnabled()) {
183                    log.debug("Refreshing Identity " + identity.getDisplayableName());
184                }
185
186                monitor.updateProgress("Refreshing Identity " + identity.getDisplayableName());
187
188                try {
189                    identitizer.refresh(identity);
190                } catch(GeneralException e) {
191                    TaskResult primaryResult = monitor.lockMasterResult();
192                    try {
193                        primaryResult.addException(e);
194                    } finally {
195                        monitor.commitMasterResult();
196                    }
197
198                    log.error("Caught an error refreshing Identity", e);
199                }
200            }
201
202            identitizer.cleanup();
203        }
204
205        TaskResult primaryResult = monitor.lockMasterResult();
206        try {
207            if (terminated.get()) {
208                primaryResult.addMessage(Message.warn("Task was terminated by user request"));
209            }
210            if (doReport) {
211                primaryResult.setAttribute("report", resultList);
212            }
213            primaryResult.setInt("affectedIdentities", identitiesToRefresh.size());
214            primaryResult.setInt("missingRoles", missingRoles.get());
215        } finally {
216            monitor.commitMasterResult();
217        }
218    }
219
220    /**
221     * Invoked by IIQ on termination. Sets the terminated flag to true so that
222     * the task knows to stop.
223     *
224     * @return always boolean true
225     */
226    @Override
227    public boolean terminate() {
228        terminated.getAndSet(true);
229        return true;
230    }
231}