001package com.identityworksllc.iiq.common; 002 003import sailpoint.api.ObjectUtil; 004import sailpoint.api.RequestManager; 005import sailpoint.api.SailPointContext; 006import sailpoint.api.Workflower; 007import sailpoint.object.*; 008import sailpoint.request.WorkflowRequestExecutor; 009import sailpoint.tools.GeneralException; 010import sailpoint.tools.MapUtil; 011import sailpoint.tools.Message; 012import sailpoint.transformer.IdentityTransformer; 013import sailpoint.workflow.StandardWorkflowHandler; 014import sailpoint.workflow.WorkflowContext; 015 016import java.time.Instant; 017import java.time.LocalDate; 018import java.time.LocalDateTime; 019import java.time.ZoneId; 020import java.time.temporal.ChronoUnit; 021import java.util.*; 022import java.util.concurrent.TimeUnit; 023 024/** 025 * Workflow utilities 026 */ 027@SuppressWarnings("unused") 028public class WorkflowUtilities extends AbstractBaseUtility { 029 030 /** 031 * An enumeration of available LCM workflow types and the system config property required to access them 032 */ 033 @SuppressWarnings("javadoc") 034 public enum LCMWorkflowType { 035 ACCESS_REQUEST("AccessRequest"), 036 ACCOUNT_REQUEST("AccountsRequest"), 037 ENTITLEMENT_REQUEST("EntitlementsRequest"), 038 EXPIRE_PASSWORD("ExpirePassword"), 039 FORGOT_PASSWORD("ForgotPassword"), 040 IDENTITY_CREATE("IdentityCreateRequest"), 041 IDENTITY_EDIT("IdentityEditRequest"), 042 PASSWORD("PasswordsRequest"), 043 ROLES_REQUEST("RolesRequest"), 044 SELF_SERVICE_REGISTRATION("SelfServiceRegistrationRequest"), 045 UNLOCK_ACCOUNT("UnlockAccount"); 046 047 /** 048 * The workflow configuration name 049 */ 050 private final String workflowFlowName; 051 052 /** 053 * Constructs an enum constant 054 * 055 * @param flow The LCM flow name 056 */ 057 LCMWorkflowType(String flow) { 058 this.workflowFlowName = flow; 059 } 060 061 /** 062 * Retrieves the workflow object associated with the given action 063 * 064 * @param context SailPoint context used to do the retrieval 065 * @return The workflow object to use for this LCM action 066 * @throws GeneralException if any failures occur 067 */ 068 public Workflow getConfiguredWorkflow(SailPointContext context) throws GeneralException { 069 String workflowName = ObjectUtil.getLCMWorkflowName(context, getFlowName()); 070 Workflow wf = null; 071 if (workflowName != null) { 072 wf = context.getObjectByName(Workflow.class, workflowName); 073 } 074 return wf; 075 } 076 077 /** 078 * Retrieves the workflow name associated with the given action 079 * 080 * @param context SailPoint context used to do the retrieval 081 * @return The workflow name to use for this LCM action 082 * @throws GeneralException if any failures occur 083 */ 084 public String getConfiguredWorkflowName(SailPointContext context) throws GeneralException { 085 String wfName = ObjectUtil.getLCMWorkflowName(context, getFlowName()); 086 if (wfName == null || wfName.isEmpty()) { 087 return "LCM Provisioning"; 088 } 089 return wfName; 090 } 091 092 /** 093 * Retrieves the flow name associated with this workflow type 094 * 095 * @return The flow name 096 */ 097 public String getFlowName() { 098 return this.workflowFlowName; 099 } 100 } 101 102 /** 103 * An enumeration of available non-LCM workflow types and the system property required to access them 104 * <p> 105 * TODO Finish this 106 */ 107 @SuppressWarnings("javadoc") 108 public enum WorkflowType { 109 IDENTITY_REFRESH("IdentityRefresh"), 110 IDENTITY_UPDATE("IdentityUpdate"); 111 112 private final String workflowFlowName; 113 114 WorkflowType(String flow) { 115 this.workflowFlowName = flow; 116 } 117 118 public String getFlowName() { 119 return this.workflowFlowName; 120 } 121 } 122 123 /** 124 * Constructor for workflow utilities 125 * 126 * @param c The current SailPointContext 127 */ 128 public WorkflowUtilities(SailPointContext c) { 129 super(c); 130 } 131 132 /** 133 * Creates a new form model map with the required parameters set. If you 134 * need to add complex values to the Map, using {@link MapUtil#put(Map, String, Object)} 135 * is a great option. 136 * 137 * @return A new form model with the required parameters 138 */ 139 public static Map<String, Object> createFormModel() { 140 Map<String, Object> model = new HashMap<>(); 141 model.put(IdentityTransformer.ATTR_TRANSFORMER_CLASS, IdentityTransformer.class.getName()); 142 model.put(IdentityTransformer.ATTR_TRANSFORMER_OPTIONS, ""); 143 return model; 144 } 145 146 /** 147 * Launches the given workflow asynchronously, using a Request to launch the 148 * Workflow and associated TaskResult at a later time. If the launch time is 149 * not in the future, it will be set to the current System timestamp. 150 * 151 * @param wf The workflow to launch 152 * @param caseName The case name ot use 153 * @param parameters The parameters to the workflow 154 * @param launchTime The time at which the workflow will be launched (in milliseconds since epoch) 155 * @throws GeneralException if any IIQ error occurs 156 */ 157 public void launchAsync(Workflow wf, String caseName, Map<String, Object> parameters, long launchTime) throws GeneralException { 158 long launchTimeFinal = Math.max(launchTime, System.currentTimeMillis()); 159 160 Attributes<String, Object> reqArgs = new Attributes<>(); 161 reqArgs.put(StandardWorkflowHandler.ARG_REQUEST_DEFINITION, WorkflowRequestExecutor.DEFINITION_NAME); 162 reqArgs.put(StandardWorkflowHandler.ARG_WORKFLOW, wf.getName()); 163 reqArgs.put(StandardWorkflowHandler.ARG_REQUEST_NAME, caseName); 164 reqArgs.putAll(parameters); 165 166 Request req = new Request(); 167 RequestDefinition reqdef = context.getObject(RequestDefinition.class, WorkflowRequestExecutor.DEFINITION_NAME); 168 req.setDefinition(reqdef); 169 req.setEventDate(new Date(launchTimeFinal)); 170 req.setOwner(context.getObjectByName(Identity.class, "spadmin")); 171 req.setName(caseName); 172 req.setAttributes(reqdef, reqArgs); 173 174 RequestManager.addRequest(context, req); 175 } 176 177 /** 178 * Launches the given workflow asynchronously, using a Request to launch the 179 * Workflow and associated TaskResult at a later time. If the launch time is 180 * not in the future, it will be set to the current System timestamp. 181 * 182 * @param wf The workflow to launch 183 * @param caseName The case name ot use 184 * @param parameters The parameters to the workflow 185 * @param launchTime The time at which the workflow will be launched 186 * @throws GeneralException if any IIQ error occurs 187 */ 188 public void launchAsync(Workflow wf, String caseName, Map<String, Object> parameters, LocalDate launchTime) throws GeneralException { 189 long launchTimeMillis = launchTime.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli(); 190 launchAsync(wf, caseName, parameters, launchTimeMillis); 191 } 192 193 /** 194 * Launches the given workflow asynchronously, using a Request to launch the 195 * Workflow and associated TaskResult at a later time. If the launch time is 196 * not in the future, it will be set to the current System timestamp. 197 * 198 * @param wf The workflow to launch 199 * @param caseName The case name ot use 200 * @param parameters The parameters to the workflow 201 * @param launchTime The time at which the workflow will be launched 202 * @throws GeneralException if any IIQ error occurs 203 */ 204 public void launchAsync(Workflow wf, String caseName, Map<String, Object> parameters, LocalDateTime launchTime) throws GeneralException { 205 long launchTimeMillis = launchTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); 206 launchAsync(wf, caseName, parameters, launchTimeMillis); 207 } 208 209 /** 210 * Launches the given workflow with the given delay 211 * 212 * @param wf The workflow to launch 213 * @param caseName The case name ot use 214 * @param parameters The parameters to the workflow 215 * @param howMuch How much to delay the launch of the workflow 216 * @param timeUnit The time unit represented by the howMuch parameter 217 * @throws GeneralException if any IIQ error occurs 218 */ 219 public void launchDelayed(Workflow wf, String caseName, Map<String, Object> parameters, long howMuch, TimeUnit timeUnit) throws GeneralException { 220 if (howMuch < 0) { 221 throw new IllegalArgumentException("The delay must be a positive number"); 222 } 223 long launchTime = System.currentTimeMillis(); 224 long delayMs = TimeUnit.MILLISECONDS.convert(howMuch, timeUnit); 225 long delayDays = TimeUnit.DAYS.convert(howMuch, timeUnit); 226 if (delayDays > 30) { 227 log.warn("Submitting workflow " + caseName + " with a delay greater than 30 days; is this what you want?"); 228 } 229 launchTime += delayMs; 230 launchAsync(wf, caseName, parameters, launchTime); 231 } 232 233 /** 234 * Launches the given workflow with the given delay 235 * 236 * @param wf The workflow to launch 237 * @param caseName The case name ot use 238 * @param parameters The parameters to the workflow 239 * @param howMuch How much to delay the launch of the workflow 240 * @param timeUnit The time unit represented by the howMuch parameter 241 * @throws GeneralException if any IIQ error occurs 242 */ 243 public void launchDelayed(Workflow wf, String caseName, Map<String, Object> parameters, long howMuch, ChronoUnit timeUnit) throws GeneralException { 244 if (howMuch < 0) { 245 throw new IllegalArgumentException("The delay must be a positive number"); 246 } 247 long launchTime = System.currentTimeMillis(); 248 Instant when = Instant.now().plus(howMuch, timeUnit); 249 long delayMs = when.toEpochMilli() - launchTime; 250 long delayDays = TimeUnit.DAYS.convert(delayMs, TimeUnit.MILLISECONDS); 251 if (delayDays > 30) { 252 log.warn("Submitting workflow " + caseName + " with a delay greater than 30 days; is this what you want?"); 253 } 254 launchTime += delayMs; 255 launchAsync(wf, caseName, parameters, launchTime); 256 } 257 258 /** 259 * Internal method to launch a workflow using the RequestManager. Used for testing 260 * by overriding this method to do something different. 261 * 262 * @param context The SailPointContext to use for launching the workflow 263 * @param request The Request to launch 264 * @throws GeneralException if any IIQ error occurs 265 */ 266 protected void launchInternal(SailPointContext context, Request request) throws GeneralException { 267 RequestManager.addRequest(context, request); 268 } 269 270 271 /** 272 * Launches the given workflow quietly (i.e. absorbs errors if there are any) 273 * 274 * @param wf The workflow to launch 275 * @param caseName The case name ot use 276 * @param parameters The parameters to the workflow 277 */ 278 public void launchQuietly(Workflow wf, String caseName, Map<String, Object> parameters) { 279 try { 280 Workflower workflower = new Workflower(context); 281 workflower.launchSafely(wf, caseName, parameters); 282 } catch (GeneralException e) { 283 /* TODO log and ignore */ 284 } 285 } 286 287 /** 288 * Sets the target workflow reference of a step to another workflow at runtime. This can be used for polymorphic behavior. 289 * 290 * @param wfcontext The workflow to modify 291 * @param stepName The step name to modify 292 * @param targetWorkflow The new target workflow for that step 293 * @throws GeneralException if any IIQ error occurs 294 */ 295 public void setStepTargetWorkflow(WorkflowContext wfcontext, String stepName, String targetWorkflow) throws GeneralException { 296 Workflow.Step step = wfcontext.getWorkflow().getStep(stepName); 297 step.setSubProcess(context.getObjectByName(Workflow.class, targetWorkflow)); 298 299 List<Workflow.Arg> arguments = new ArrayList<>(); 300 Attributes<String, Object> variables = wfcontext.getWorkflow().getVariables(); 301 302 for (String key : variables.getKeys()) { 303 if (!key.equals("targetWorkflow")) { 304 Workflow.Arg arg = new Workflow.Arg(); 305 arg.setName(key); 306 arg.setValue("ref:" + key); 307 arguments.add(arg); 308 } 309 } 310 311 step.setArgs(arguments); 312 } 313 314 /** 315 * Appends an error message to the given workflow context / task result with a timestamp 316 * 317 * @param wfcontext The Workflow Context (wfcontext from any workflow step) 318 * @param message The message to append 319 */ 320 public void wfError(WorkflowContext wfcontext, String message) { 321 WorkflowCase wfcase = wfcontext.getWorkflowCase(); 322 if (wfcase != null) { 323 wfcase.addMessage(Message.error(Utilities.timestamp() + ": " + message)); 324 } 325 } 326 327 /** 328 * Appends a message to the given workflow context / task result with a timestamp 329 * 330 * @param wfcontext The Workflow Context (wfcontext from any workflow step) 331 * @param message The message to append 332 */ 333 public void wfMessage(WorkflowContext wfcontext, String message) { 334 WorkflowCase wfcase = wfcontext.getWorkflowCase(); 335 if (wfcase != null) { 336 wfcase.addMessage(Utilities.timestamp() + ": " + message); 337 } 338 } 339 340 /** 341 * Appends a warning message to the given workflow context / task result with a timestamp 342 * 343 * @param wfcontext The Workflow Context (wfcontext from any workflow step) 344 * @param message The message to append 345 */ 346 public void wfWarn(WorkflowContext wfcontext, String message) { 347 WorkflowCase wfcase = wfcontext.getWorkflowCase(); 348 if (wfcase != null) { 349 wfcase.addMessage(Message.warn(Utilities.timestamp() + ": " + message)); 350 } 351 } 352}