001package com.identityworksllc.iiq.common; 002 003import sailpoint.api.IdentityService; 004import sailpoint.api.ObjectUtil; 005import sailpoint.api.Provisioner; 006import sailpoint.api.SailPointContext; 007import sailpoint.api.Workflower; 008import sailpoint.object.*; 009import sailpoint.object.ProvisioningPlan.AccountRequest; 010import sailpoint.object.ProvisioningPlan.AttributeRequest; 011import sailpoint.provisioning.PlanCompiler; 012import sailpoint.tools.GeneralException; 013import sailpoint.tools.Message; 014import sailpoint.tools.Util; 015 016import java.time.Instant; 017import java.time.format.DateTimeFormatter; 018import java.util.ArrayList; 019import java.util.HashMap; 020import java.util.List; 021import java.util.Map; 022import java.util.Objects; 023import java.util.Optional; 024import java.util.function.Consumer; 025import java.util.function.Predicate; 026import java.util.stream.Collectors; 027 028/** 029 * Utilities to wrap the several provisioning APIs available in SailPoint. 030 */ 031@SuppressWarnings("unused") 032public class ProvisioningUtilities extends AbstractBaseUtility { 033 034 /** 035 * The attribute for provisioning assigned roles 036 */ 037 public static final String ASSIGNED_ROLES_ATTR = "assignedRoles"; 038 039 /** 040 * The constant to use for no approvals 041 */ 042 public static final String NO_APPROVAL_SCHEME = "none"; 043 044 /** 045 * The approval scheme workflow parameter 046 */ 047 public static final String PLAN_PARAM_APPROVAL_SCHEME = "approvalScheme"; 048 049 /** 050 * The notification scheme workflow parameter 051 */ 052 public static final String PLAN_PARAM_NOTIFICATION_SCHEME = "notificationScheme"; 053 054 /** 055 * Modifies the plan to add the given user to the given role, associating it statically 056 * with the given target accounts (or new accounts if none are specified). 057 * 058 * @param context the IIQ context 059 * @param identityName The identity name to add to the given role 060 * @param roleName The role to add 061 * @param targets These Links will be used as a provisioning target for the plan. If a value is null, a new account create will be requested. 062 * @param provisioningPlan The provisioning plan to modify 063 * @throws GeneralException if a failure occurs while looking up required objects 064 */ 065 public static void addUserRolePlan(SailPointContext context, String identityName, String roleName, Map<String, Link> targets, ProvisioningPlan provisioningPlan) throws GeneralException { 066 Objects.requireNonNull(identityName); 067 Objects.requireNonNull(roleName); 068 069 // We have to generate our own assignment ID in this case 070 String assignmentKey = Util.uuid(); 071 Bundle role = context.getObjectByName(Bundle.class, roleName); 072 073 if (role == null) { 074 throw new IllegalArgumentException("Role " + roleName + " does not exist"); 075 } 076 077 Identity planIdentity = provisioningPlan.getIdentity(); 078 079 AccountRequest accountRequest = new AccountRequest(AccountRequest.Operation.Modify, ProvisioningPlan.APP_IIQ, null, planIdentity.getName()); 080 AttributeRequest attributeRequest = new AttributeRequest(ASSIGNED_ROLES_ATTR, ProvisioningPlan.Operation.Add, roleName); 081 attributeRequest.setAssignmentId(assignmentKey); 082 accountRequest.add(attributeRequest); 083 provisioningPlan.add(accountRequest); 084 085 List<ProvisioningTarget> provisioningTargetSelectors = new ArrayList<>(); 086 087 for(String appName : targets.keySet()) { 088 Application application = context.getObjectByName(Application.class, appName); 089 if (application == null) { 090 throw new IllegalArgumentException("Application " + appName + " passed in the target map does not exist"); 091 } 092 AccountSelection selection = new AccountSelection(application);; 093 Link target = targets.get(appName); 094 if (target == null) { 095 selection.setAllowCreate(true); 096 selection.setRoleName(roleName); 097 selection.setDoCreate(true); 098 } else { 099 RoleTarget roleTarget = new RoleTarget(target); 100 selection.setAllowCreate(false); 101 selection.setRoleName(roleName); 102 selection.addAccountInfo(target); 103 selection.setSelection(roleTarget.getNativeIdentity()); 104 } 105 ProvisioningTarget provisioningTarget = new ProvisioningTarget(assignmentKey, role); 106 provisioningTarget.setRole(roleName); 107 provisioningTarget.setApplication(appName); 108 provisioningTarget.addAccountSelection(selection); 109 provisioningTargetSelectors.add(provisioningTarget); 110 } 111 112 provisioningPlan.setProvisioningTargets(provisioningTargetSelectors); 113 } 114 115 /** 116 * Gets the arguments for the given plan, creating one if needed 117 * @param plan The request 118 * @return The arguments for the plan 119 */ 120 public static Attributes<String, Object> getArguments(ProvisioningPlan plan) { 121 Attributes<String, Object> arguments = plan.getArguments(); 122 if (arguments == null) { 123 arguments = new Attributes<>(); 124 plan.setArguments(arguments); 125 } 126 return arguments; 127 } 128 129 /** 130 * Gets the arguments for the given request, creating one if needed 131 * @param request The request 132 * @return The arguments for the request 133 */ 134 public static Attributes<String, Object> getArguments(ProvisioningPlan.AbstractRequest request) { 135 Attributes<String, Object> arguments = request.getArguments(); 136 if (arguments == null) { 137 arguments = new Attributes<>(); 138 request.setArguments(arguments); 139 } 140 return arguments; 141 } 142 143 /** 144 * Gets the IIQ account request from the given plan, creating one if needed 145 * @param plan The plan in question 146 * @return The IIQ account request 147 */ 148 public static AccountRequest getIIQAccountRequest(ProvisioningPlan plan) { 149 AccountRequest accountRequest = plan.getIIQAccountRequest(); 150 if (accountRequest == null) { 151 accountRequest = new AccountRequest(); 152 accountRequest.setOperation(AccountRequest.Operation.Modify); 153 accountRequest.setApplication(ProvisioningPlan.APP_IIQ); 154 plan.add(accountRequest); 155 } 156 return accountRequest; 157 } 158 159 /** 160 * Intended to be used in a pre or post-provision rule, this method will return the given attribute from the AccountRequest if present and otherwise will return it from the Link. The Link will be looked up based on the contents of the AccountRequest. 161 * @param req The AccountRequest modifying this Link 162 * @param name The name of the attribute to return 163 * @return the attribute value 164 * @throws GeneralException if a query failure occurs 165 */ 166 public static Object getLatestValue(SailPointContext context, AccountRequest req, String name) throws GeneralException { 167 Link account = getLinkFromRequest(context, req); 168 return getLatestValue(account, req, name); 169 } 170 171 /** 172 * Intended to be used in a pre or post-provision rule, this method will return the given attribute from the AccountRequest if present and otherwise will return it from the Link 173 * @param account The Link being modified 174 * @param req The AccountRequest modifying this Link 175 * @param name The name of the attribute to return 176 * @return the attribute value 177 */ 178 public static Object getLatestValue(Link account, AccountRequest req, String name) { 179 Object value = null; 180 181 AttributeRequest attr = req.getAttributeRequest(name); 182 if (attr != null) { 183 value = attr.getValue(); 184 } else if (account != null) { 185 value = account.getAttributes().get(name); 186 } 187 return value; 188 } 189 190 /** 191 * Retrieves the Link that is associated with the given AccountRequest, or returns null 192 * if no link can be found. The Application on the request must be set and accurate. 193 * 194 * On create, the outcome will always be null because the Link doesn't exist until after 195 * the operation has completed. 196 * 197 * @param request The request to use to search 198 * @return the matching Link, or null if none can be found 199 * @throws GeneralException if more than one matching Link is found 200 */ 201 public static Link getLinkFromRequest(SailPointContext context, AccountRequest request) throws GeneralException { 202 if (request == null || Util.isNullOrEmpty(request.getApplication()) || Util.nullSafeEq(request.getApplication(), ProvisioningPlan.APP_IIQ)) { 203 return null; 204 } 205 Application app = request.getApplication(context); 206 if (app != null) { 207 Filter filter = Filter.and(Filter.eq("application.name", app.getName()), Filter.eq("nativeIdentity", request.getNativeIdentity())); 208 QueryOptions qo = new QueryOptions(); 209 qo.add(filter); 210 211 List<Link> objects = context.getObjects(Link.class, qo); 212 if (objects.size() == 1) { 213 return objects.get(0); 214 } else if (objects.size() == 0) { 215 return null; 216 } else { 217 String message = "Expected to find 1 Link with application = " + app.getName() + " and native identity = " + request.getNativeIdentity() + ", but found " + objects.size(); 218 throw new GeneralException(message); 219 } 220 } 221 return null; 222 } 223 224 /** 225 * Creates an AttributeRequest to move the given Link to the given target Identity, 226 * either modifying the provided plan or creating a new one. 227 * 228 * A move-account plan can be structured in either direction. It can be an "Add" 229 * plan that focuses on the destination Identity (allowing movement of accounts 230 * from more than one source) or a "Remove" plan that focuses on the source Identity 231 * (allowing movement of accounts to more than one target). You cannot mix these 232 * on a single plan. 233 * 234 * If the plan does not already contain a link move, it will be set up as an Add. 235 * 236 * This method will throw an exception if you pass an existing plan and its structure 237 * does not match the objects you pass in. 238 * 239 * @param theLinkToMove The link to move 240 * @param targetIdentity The Identity to which the link should be moved 241 * @param existingPlan The existing plan to modify, or null to create a new one 242 * @return The plan created or modified by this method 243 * @throws GeneralException if any validation failures occur 244 */ 245 @SuppressWarnings("unused") 246 public static ProvisioningPlan linkMovePlan(Link theLinkToMove, Identity targetIdentity, ProvisioningPlan existingPlan) throws GeneralException { 247 if (theLinkToMove == null) { 248 throw new GeneralException("The link to move cannot be null"); 249 } 250 251 if (targetIdentity == null || Util.isNullOrEmpty(targetIdentity.getId())) { 252 throw new GeneralException("The target Identity must be not be null and must have an ID"); 253 } 254 255 Identity sourceIdentity = theLinkToMove.getIdentity(); 256 if (sourceIdentity == null) { 257 throw new GeneralException("Link " + theLinkToMove.getId() + " does not appear to have a valid Identity attached??"); 258 } 259 260 if (Util.nullSafeEq(sourceIdentity.getId(), targetIdentity.getId())) { 261 throw new GeneralException("Source and target Identity are the same"); 262 } 263 264 // Add or Remove, depending on how the existing plan is structured (default Add) 265 ProvisioningPlan.Operation op; 266 267 // The identity ID we expect to find in the existing IIQ AccountRequest, if one exists 268 String expectedAccountRequestIdentity; 269 270 ProvisioningPlan thePlan = existingPlan; 271 if (thePlan == null) { 272 thePlan = new ProvisioningPlan(); 273 thePlan.setComments("Move account " + theLinkToMove.getId() + " via API"); 274 } 275 276 if (thePlan.getIdentity() == null) { 277 // Move-to plan 278 thePlan.setIdentity(targetIdentity); 279 op = ProvisioningPlan.Operation.Add; 280 expectedAccountRequestIdentity = targetIdentity.getId(); 281 } else { 282 Identity existingIdentity = thePlan.getIdentity(); 283 if (Util.nullSafeEq(existingIdentity.getId(), sourceIdentity.getId())) { 284 // Move-from plan 285 op = ProvisioningPlan.Operation.Remove; 286 expectedAccountRequestIdentity = sourceIdentity.getId(); 287 } else if (Util.nullSafeEq(existingIdentity.getId(), targetIdentity.getId())) { 288 // Move-to plan 289 op = ProvisioningPlan.Operation.Add; 290 expectedAccountRequestIdentity = targetIdentity.getId(); 291 } else { 292 // We don't know who this is, error 293 throw new GeneralException("The ProvisioningPlan's associated Identity is neither the specified source nor target; cannot construct a link move plan"); 294 } 295 } 296 297 AccountRequest iiqRequest = thePlan.getIIQAccountRequest(); 298 if (iiqRequest == null) { 299 iiqRequest = new AccountRequest(); 300 iiqRequest.setOperation(AccountRequest.Operation.Modify); 301 iiqRequest.setApplication(ProvisioningPlan.APP_IIQ); 302 iiqRequest.setNativeIdentity(expectedAccountRequestIdentity); 303 304 thePlan.add(iiqRequest); 305 } else { 306 String nativeIdentity = iiqRequest.getNativeIdentity(); 307 if (!Util.nullSafeEq(nativeIdentity, expectedAccountRequestIdentity)) { 308 // We don't know who this is, error 309 throw new GeneralException("The plan's 'IIQ' AccountRequest already has target Identity [" + nativeIdentity + "], which is not the same as the expected Identity ID [" + expectedAccountRequestIdentity + "]. A move-account plan can only move to one Identity or from one Identity."); 310 } 311 } 312 313 AttributeRequest attributeRequest = new AttributeRequest(ProvisioningPlan.ATT_IIQ_LINKS, op, theLinkToMove.getId()); 314 if (op == ProvisioningPlan.Operation.Add) { 315 // Move from this source 316 attributeRequest.put(ProvisioningPlan.ARG_SOURCE_IDENTITY, sourceIdentity.getId()); 317 } else { 318 // Move to this target 319 attributeRequest.put(ProvisioningPlan.ARG_DESTINATION_IDENTITY, targetIdentity.getId()); 320 } 321 iiqRequest.add(attributeRequest); 322 323 return thePlan; 324 } 325 326 /** 327 * Modifies the plan to add a role removal request for the given role 328 * @param roleName The role to remove from the identity 329 * @param revoke If true, the role will be revoked and not removed 330 * @param provisioningPlan The plan to add the role removal to 331 * @throws GeneralException If a failure occurs 332 */ 333 public static void removeUserRolePlan(String roleName, boolean revoke, ProvisioningPlan provisioningPlan) throws GeneralException { 334 Objects.requireNonNull(roleName); 335 if (roleName.trim().isEmpty()) { 336 throw new IllegalArgumentException("A non-empty role name must be provided"); 337 } 338 Objects.requireNonNull(provisioningPlan); 339 AccountRequest accountRequest = provisioningPlan.getIIQAccountRequest(); 340 if (accountRequest == null) { 341 accountRequest = new AccountRequest(); 342 provisioningPlan.add(accountRequest); 343 } 344 accountRequest.setOperation(AccountRequest.Operation.Modify); 345 accountRequest.setApplication(ProvisioningPlan.APP_IIQ); 346 AttributeRequest attributeRequest = new AttributeRequest(); 347 attributeRequest.setOperation(revoke ? ProvisioningPlan.Operation.Revoke : ProvisioningPlan.Operation.Remove); 348 attributeRequest.setName(ASSIGNED_ROLES_ATTR); 349 attributeRequest.setValue(roleName); 350 List<AttributeRequest> requestList = new ArrayList<>(); 351 requestList.add(attributeRequest); 352 accountRequest.setAttributeRequests(requestList); 353 provisioningPlan.add(accountRequest); 354 } 355 356 /** 357 * Creates a AccountSelection object from the given account selection 358 * @param target The target to transform 359 * @return An {@link AccountSelection} with the given RoleTarget parameters 360 */ 361 public static AccountSelection roleTargetToAccountSelection(RoleTarget target) { 362 AccountSelection selection = new AccountSelection(); 363 selection.addAccountInfo(target); 364 return selection; 365 } 366 367 /** 368 * Creates a Provisioning Target from account 369 * @param role The role being provisioned 370 * @param target The target account 371 * @return A ProvisioningTarget object for the given role / account combination 372 */ 373 public static ProvisioningTarget toProvisioningTarget(Bundle role, Link target) { 374 String assignmentKey = Util.uuid(); 375 AccountSelection selection = new AccountSelection(target.getApplication()); 376 RoleTarget roleTarget = new RoleTarget(target); 377 selection.setAllowCreate(false); 378 selection.setRoleName(role.getName()); 379 selection.addAccountInfo(target); 380 selection.setSelection(roleTarget.getNativeIdentity()); 381 ProvisioningTarget provisioningTarget = new ProvisioningTarget(assignmentKey, role); 382 provisioningTarget.setRole(role.getName()); 383 provisioningTarget.setApplication(target.getApplicationName()); 384 provisioningTarget.addAccountSelection(selection); 385 return provisioningTarget; 386 } 387 388 /** 389 * Creates a Provisioning Target from the given application and nativeIdentity name 390 * @param role The role being provisioned 391 * @param application The application to target 392 * @param nativeIdentity The native identity to target 393 * @return A ProvisioningTarget object for the given role / account combination 394 * @throws GeneralException if any failures occur 395 */ 396 public static ProvisioningTarget toProvisioningTarget(SailPointContext context, Bundle role, String application, String nativeIdentity) throws GeneralException { 397 String assignmentKey = Util.uuid(); 398 Application applicationObj = context.getObjectByName(Application.class, application); 399 AccountSelection selection = new AccountSelection(applicationObj); 400 RoleTarget roleTarget = new RoleTarget(); 401 roleTarget.setApplicationName(application); 402 roleTarget.setRoleName(role.getName()); 403 if (Util.isNotNullOrEmpty(nativeIdentity)) { 404 roleTarget.setNativeIdentity(nativeIdentity); 405 } else { 406 selection.setAllowCreate(true); 407 } 408 selection.setRoleName(role.getName()); 409 selection.addAccountInfo(roleTarget); 410 selection.setSelection(nativeIdentity); 411 ProvisioningTarget provisioningTarget = new ProvisioningTarget(assignmentKey, role); 412 provisioningTarget.setRole(role.getName()); 413 provisioningTarget.setApplication(application); 414 provisioningTarget.addAccountSelection(selection); 415 return provisioningTarget; 416 } 417 /** 418 * Invoked in the final tier of doProvisioning if present, mainly for testing purposes 419 */ 420 private Consumer<ProvisioningPlan> beforeProvisioningConsumer; 421 /** 422 * If not null, this ticket ID will be attached to any provisioning operation 423 */ 424 private String externalTicketId; 425 /** 426 * Any plan arguments passed to the provisioner 427 */ 428 private final Attributes<String, Object> planArguments; 429 /** 430 * Invoked with the compiled project just before provisioning 431 */ 432 private Consumer<ProvisioningProject> projectDebugger; 433 /** 434 * The workflow configuration container 435 */ 436 private final ProvisioningArguments provisioningArguments; 437 /** 438 * Invoked with the compiled project just before provisioning 439 */ 440 private Consumer<WorkflowLaunch> workflowDebugger; 441 442 /** 443 * Constructs a workflow-based Provisioning Utilities that will use the default 444 * LCM Provisioning workflow for all operations. 445 * 446 * @param c The SailPoint context 447 */ 448 public ProvisioningUtilities(SailPointContext c) { 449 super(Objects.requireNonNull(c)); 450 provisioningArguments = new ProvisioningArguments(); 451 planArguments = new Attributes<>(); 452 } 453 454 public ProvisioningUtilities(SailPointContext context, ProvisioningArguments arguments) { 455 this(context); 456 457 if (arguments != null) { 458 this.provisioningArguments.merge(arguments); 459 } 460 } 461 462 @SuppressWarnings("unchecked") 463 public ProvisioningUtilities(SailPointContext context, Map<String, Object> arguments) throws GeneralException { 464 this(context); 465 466 if (arguments == null) { 467 throw new IllegalArgumentException("Invalid input to ProvisioningUtilities: cannot provide a null Map for arguments"); 468 } 469 470 Attributes<String, Object> map = new Attributes<>(arguments); 471 472 provisioningArguments.setErrorOnAccountSelection(map.getBoolean("errorOnAccountSelection")); 473 provisioningArguments.setErrorOnManualTask(map.getBoolean("errorOnManualTask")); 474 provisioningArguments.setErrorOnNewAccount(map.getBoolean("errorOnNewAccount")); 475 provisioningArguments.setErrorOnProvisioningForms(map.getBoolean("errorOnProvisioningForms")); 476 477 this.externalTicketId = map.getString("externalTicketId"); 478 479 provisioningArguments.setPlanFieldName(map.getString("workflowPlanField")); 480 provisioningArguments.setIdentityFieldName(map.getString("workflowIdentityField")); 481 provisioningArguments.setCaseNameTemplate(map.getString("caseNameTemplate")); 482 if (map.get("provisioningWorkflow") instanceof String) { 483 provisioningArguments.setWorkflowName(map.getString("provisioningWorkflow")); 484 } 485 provisioningArguments.setUseWorkflow(map.getBoolean("useWorkflow", true)); 486 if (map.get("defaultExtraParameters") instanceof Map) { 487 provisioningArguments.setDefaultExtraParameters((Map<String, Object>) map.get("defaultExtraParameters")); 488 } 489 if (map.get("planArguments") instanceof Map) { 490 Map<String, Object> tempMap = (Map<String, Object>) map.get("planArguments"); 491 this.planArguments.putAll(tempMap); 492 } 493 } 494 495 /** 496 * Constructs a Provisioning Utilities that will optionally directly forward provisioning operations to the Provisioner. 497 * @param c The SailPoint context 498 * @param useWorkflow If true, workflows will be bypassed and provisioning will be sent directly to the provisioner 499 */ 500 public ProvisioningUtilities(SailPointContext c, boolean useWorkflow) { 501 this(c); 502 provisioningArguments.setUseWorkflow(true); 503 } 504 505 /** 506 * Constructs a workflow-based Provisioning Utilities that will use the given workflow instead of the default 507 * @param c The SailPoint context 508 * @param provisioningWorkflowName The name of a provisioning workflow which should expect an 'identityName' and 'plan' attribute 509 */ 510 public ProvisioningUtilities(SailPointContext c, String provisioningWorkflowName) { 511 this(c, true); 512 provisioningArguments.setWorkflowName(provisioningWorkflowName); 513 } 514 515 /** 516 * Constructs a Provisioning Utilities that will optionally directly forward provisioning operations to the Provisioner, or else will use the given provisioning workflow 517 * @param c The SailPoint context 518 * @param provisioningWorkflowName The name of a provisioning workflow which should expect an 'identityName' and 'plan' attribute 519 * @param useWorkflow If true, workflows will be bypassed and provisioning will be sent directly to the provisioner 520 */ 521 public ProvisioningUtilities(SailPointContext c, String provisioningWorkflowName, boolean useWorkflow) { 522 this(c, provisioningWorkflowName); 523 provisioningArguments.setUseWorkflow(useWorkflow); 524 } 525 526 /** 527 * Adds the given entitlement to the given account on the user 528 * @param identityName The identity name 529 * @param account The account to modify 530 * @param entitlement The managed attribute from which to extract the entitlement 531 * @param withApproval If false, approval will be skipped 532 * @throws GeneralException if any failure occurs 533 */ 534 public void addEntitlement(String identityName, Link account, ManagedAttribute entitlement, boolean withApproval) throws GeneralException { 535 ProvisioningPlan plan = new ProvisioningPlan(); 536 Identity identity = findIdentity(identityName); 537 plan.setIdentity(identity); 538 539 AccountRequest accountRequest = new AccountRequest(AccountRequest.Operation.Modify, account.getApplicationName(), account.getInstance(), account.getNativeIdentity()); 540 plan.add(accountRequest); 541 542 AttributeRequest attributeRequest = new AttributeRequest(entitlement.getName(), ProvisioningPlan.Operation.Add, entitlement.getValue()); 543 accountRequest.add(attributeRequest); 544 545 Map<String, Object> options = new HashMap<>(); 546 if (!withApproval) { 547 options.put(PLAN_PARAM_APPROVAL_SCHEME, NO_APPROVAL_SCHEME); 548 } 549 550 doProvisioning(identityName, plan, false, options); 551 } 552 553 /** 554 * Adds the given entitlement to the given account on the user 555 * @param identityName The identity name 556 * @param account The account to modify 557 * @param attribute The attribute to modify 558 * @param value The value to add 559 * @param withApproval If false, approval will be skipped 560 * @throws GeneralException if any failure occurs 561 */ 562 public void addEntitlement(String identityName, Link account, String attribute, String value, boolean withApproval) throws GeneralException { 563 ProvisioningPlan plan = new ProvisioningPlan(); 564 Identity identity = findIdentity(identityName); 565 plan.setIdentity(identity); 566 567 AccountRequest accountRequest = new AccountRequest(AccountRequest.Operation.Modify, account.getApplicationName(), account.getInstance(), account.getNativeIdentity()); 568 plan.add(accountRequest); 569 570 AttributeRequest attributeRequest = new AttributeRequest(attribute, ProvisioningPlan.Operation.Add, value); 571 accountRequest.add(attributeRequest); 572 573 Map<String, Object> options = new HashMap<>(); 574 if (!withApproval) { 575 options.put(PLAN_PARAM_APPROVAL_SCHEME, NO_APPROVAL_SCHEME); 576 } 577 578 doProvisioning(identityName, plan, false, options); 579 } 580 581 /** 582 * Adds an argument to the ProvisioningPlan that will eventually be constructed 583 * on a call to {@link #doProvisioning(ProvisioningPlan)} 584 * @param argument The argument to add to the plan 585 * @param value The value to add to the plan 586 */ 587 public void addPlanArgument(String argument, Object value) { 588 planArguments.put(argument, value); 589 } 590 591 /** 592 * Adds the given user to the given role, guessing the target account by name. If the 593 * plan expands to more than one account selection question, this method will throw 594 * an exception. 595 * 596 * @param identityName The identity name to add to the given role 597 * @param roleName The role to add 598 * @param withApproval If true, default approval will be required 599 * @param accountName The target account to locate 600 * @throws GeneralException if a provisioning failure occurs 601 */ 602 public void addUserRole(String identityName, String roleName, boolean withApproval, String accountName) throws GeneralException { 603 Objects.requireNonNull(identityName, "The passed identityName must not be null"); 604 Objects.requireNonNull(roleName, "The passed roleName must not be null"); 605 if (Util.isNullOrEmpty(accountName)) { 606 // No sense going through the below if we didn't pass an account name 607 addUserRole(identityName, roleName, withApproval); 608 return; 609 } 610 Bundle role = context.getObjectByName(Bundle.class, roleName); 611 if (role == null) { 612 throw new IllegalArgumentException("Role " + roleName + " does not exist"); 613 } 614 // We have to generate our own assignment ID in this case 615 String assignmentKey = Util.uuid(); 616 ProvisioningPlan provisioningPlan = new ProvisioningPlan(); 617 Identity identity = findIdentity(identityName); 618 provisioningPlan.setIdentity(identity); 619 AccountRequest accountRequest = new AccountRequest(AccountRequest.Operation.Modify, ProvisioningPlan.APP_IIQ, null, identity.getName()); 620 AttributeRequest attributeRequest = new AttributeRequest(ASSIGNED_ROLES_ATTR, ProvisioningPlan.Operation.Add, roleName); 621 attributeRequest.setAssignmentId(assignmentKey); 622 accountRequest.add(attributeRequest); 623 provisioningPlan.add(accountRequest); 624 625 PlanCompiler compiler = new PlanCompiler(context); 626 ProvisioningProject project = compiler.compile(new Attributes<>(), provisioningPlan, new Attributes<>()); 627 List<ProvisioningTarget> targets = Utilities.safeStream(project.getProvisioningTargets()).filter(ProvisioningTarget::isAnswered).collect(Collectors.toList()); 628 if (targets.size() > 1) { 629 throw new IllegalStateException("The resulting provisioning project has more than one unanswered account selection"); 630 } else if (targets.size() == 1) { 631 ProvisioningTarget tempTarget = targets.get(0); 632 String appName = tempTarget.getApplication(); 633 Application application = context.getObjectByName(Application.class, appName); 634 IdentityService ids = new IdentityService(context); 635 List<Link> possibleTargets = ids.getLinks(identity, application); 636 Optional<Link> maybeLink = 637 Utilities.safeStream(possibleTargets) 638 .filter(Functions.isNativeIdentity(accountName)) 639 .findAny(); 640 641 List<ProvisioningTarget> provisioningTargetSelectors = new ArrayList<>(); 642 643 AccountSelection selection = new AccountSelection(application); 644 if (!maybeLink.isPresent()) { 645 selection.setAllowCreate(true); 646 selection.setRoleName(roleName); 647 selection.setDoCreate(true); 648 } else { 649 RoleTarget roleTarget = new RoleTarget(maybeLink.get()); 650 selection.setAllowCreate(false); 651 selection.setRoleName(roleName); 652 selection.addAccountInfo(roleTarget); 653 selection.setSelection(roleTarget.getNativeIdentity()); 654 } 655 ProvisioningTarget provisioningTarget = new ProvisioningTarget(assignmentKey, role); 656 provisioningTarget.setRole(roleName); 657 provisioningTarget.setApplication(appName); 658 provisioningTarget.addAccountSelection(selection); 659 provisioningTargetSelectors.add(provisioningTarget); 660 provisioningPlan.setProvisioningTargets(provisioningTargetSelectors); 661 } 662 Map<String, Object> options = new HashMap<>(); 663 if (!withApproval) { 664 options.put(PLAN_PARAM_APPROVAL_SCHEME, NO_APPROVAL_SCHEME); 665 } 666 doProvisioning(identity.getName(), provisioningPlan, false, options); 667 } 668 669 /** 670 * Adds the given user to the given role, associating it statically with the given 671 * target accounts (or new accounts if none are specified). If a target is not supplied 672 * for a given application that is provisioned by this role, the provisioning engine 673 * will automatically run any account selection rule followed by an attempt at heuristic 674 * guessing. 675 * 676 * @param identityName The identity name to add to the given role 677 * @param roleName The role to add 678 * @param withApproval If true, default approval will be required 679 * @param targets These Links will be used as a provisioning target for the plan. If a value is null, a new account create will be requested. 680 * @throws GeneralException if a provisioning failure occurs 681 */ 682 public void addUserRole(String identityName, String roleName, boolean withApproval, Map<String, Link> targets) throws GeneralException { 683 Objects.requireNonNull(identityName); 684 685 ProvisioningPlan provisioningPlan = new ProvisioningPlan(); 686 Identity identity = findIdentity(identityName); 687 provisioningPlan.setIdentity(identity); 688 689 addUserRolePlan(context, identityName, roleName, targets, provisioningPlan); 690 691 Map<String, Object> options = new HashMap<>(); 692 if (!withApproval) { 693 options.put(PLAN_PARAM_APPROVAL_SCHEME, NO_APPROVAL_SCHEME); 694 } 695 doProvisioning(identity.getName(), provisioningPlan, false, options); 696 } 697 698 /** 699 * Adds the given user to the given role 700 * @param identityName The identity name to add to the given role 701 * @param roleName The role to add 702 * @throws GeneralException if a provisioning failure occurs 703 */ 704 public void addUserRole(String identityName, String roleName) throws GeneralException { 705 addUserRole(identityName, roleName, false); 706 } 707 708 /** 709 * Adds the given user to the given role 710 * @param identityName The identity name to add to the given role 711 * @param roleName The role to add 712 * @param withApproval If true, default approval will be required 713 * @throws GeneralException if a provisioning failure occurs 714 */ 715 public void addUserRole(String identityName, String roleName, boolean withApproval) throws GeneralException { 716 ProvisioningPlan provisioningPlan = new ProvisioningPlan(); 717 Identity identity = findIdentity(identityName); 718 provisioningPlan.setIdentity(identity); 719 // This is magical 720 provisioningPlan.add(ProvisioningPlan.APP_IIQ, identity.getName(), ASSIGNED_ROLES_ATTR, ProvisioningPlan.Operation.Add, roleName); 721 Map<String, Object> options = new HashMap<>(); 722 if (!withApproval) { 723 options.put(PLAN_PARAM_APPROVAL_SCHEME, NO_APPROVAL_SCHEME); 724 } 725 doProvisioning(identity.getName(), provisioningPlan, false, options); 726 } 727 728 /** 729 * Adds an argument to the workflow or Provisioner that will be used in a call to 730 * {@link #doProvisioning(ProvisioningPlan)}. If the value provided is null, the 731 * key will be removed from the arguments. 732 * 733 * @param argument The argument to set the value for 734 * @param value The value to set 735 */ 736 public void addWorkflowArgument(String argument, Object value) { 737 if (value == null) { 738 provisioningArguments.getDefaultExtraParameters().remove(argument); 739 } else { 740 provisioningArguments.getDefaultExtraParameters().put(argument, value); 741 } 742 } 743 744 /** 745 * Deletes the given account by submitting a Delete request to IIQ 746 * @param link The Link to disable 747 * @throws GeneralException if any failures occur 748 */ 749 public void deleteAccount(Link link) throws GeneralException { 750 Objects.requireNonNull(link, "Provided Link must not be null"); 751 ProvisioningPlan plan = new ProvisioningPlan(); 752 String username = ObjectUtil.getIdentityFromLink(context, link.getApplication(), link.getInstance(), link.getNativeIdentity()); 753 Identity user = findIdentity(username); 754 plan.setIdentity(user); 755 plan.add(link.getApplicationName(), link.getNativeIdentity(), AccountRequest.Operation.Delete); 756 Map<String, Object> extraParameters = new HashMap<>(); 757 extraParameters.put(PLAN_PARAM_APPROVAL_SCHEME, NO_APPROVAL_SCHEME); 758 doProvisioning(user.getName(), plan, false, extraParameters); 759 } 760 761 /** 762 * Disables the given account by submitting a Disable request to IIQ 763 * @param link The Link to disable 764 * @throws GeneralException if any failures occur 765 */ 766 public void disableAccount(Link link) throws GeneralException { 767 Objects.requireNonNull(link, "Provided Link must not be null"); 768 disableAccount(link, false); 769 } 770 771 /** 772 * Disables the given account by submitting a Disable request to IIQ 773 * @param link The Link to disable 774 * @throws GeneralException if any failures occur 775 */ 776 public void disableAccount(Link link, boolean doRefresh) throws GeneralException { 777 Objects.requireNonNull(link, "Provided Link must not be null"); 778 ProvisioningPlan plan = new ProvisioningPlan(); 779 String username = ObjectUtil.getIdentityFromLink(context, link.getApplication(), link.getInstance(), link.getNativeIdentity()); 780 Identity user = findIdentity(username); 781 plan.setIdentity(user); 782 plan.add(link.getApplicationName(), link.getNativeIdentity(), AccountRequest.Operation.Disable); 783 Map<String, Object> extraParameters = new HashMap<>(); 784 extraParameters.put(PLAN_PARAM_APPROVAL_SCHEME, NO_APPROVAL_SCHEME); 785 doProvisioning(user.getName(), plan, doRefresh, extraParameters); 786 } 787 788 /** 789 * Submits a single request to disable all accounts on the Identity that are not already disabled. 790 * @param identity Who to disable the accounts on 791 * @throws GeneralException if any failures occur 792 */ 793 public void disableAccounts(Identity identity) throws GeneralException { 794 Objects.requireNonNull(identity, "Provided Identity must not be null"); 795 ProvisioningPlan plan = new ProvisioningPlan(); 796 plan.setIdentity(identity); 797 for(Link link : Util.safeIterable(identity.getLinks())) { 798 if (!link.isDisabled()) { 799 plan.add(link.getApplicationName(), link.getNativeIdentity(), AccountRequest.Operation.Disable); 800 } 801 } 802 doProvisioning(plan); 803 } 804 805 /** 806 * Submits a single request to disable all accounts on the Identity that are not already disabled. 807 * @param identity Who to disable the accounts on 808 * @param onlyThese Only applications in this list will be disabled 809 * @throws GeneralException if any failures occur 810 */ 811 public void disableAccounts(Identity identity, List<String> onlyThese) throws GeneralException { 812 Objects.requireNonNull(identity, "Provided Identity must not be null"); 813 ProvisioningPlan plan = new ProvisioningPlan(); 814 plan.setIdentity(identity); 815 for(Link link : Util.safeIterable(identity.getLinks())) { 816 if (!link.isDisabled() && Util.nullSafeContains(onlyThese, link.getApplication())) { 817 plan.add(link.getApplicationName(), link.getNativeIdentity(), AccountRequest.Operation.Disable); 818 } 819 } 820 doProvisioning(plan); 821 } 822 823 /** 824 * Submits a single request to disable all accounts on the Identity that are not already disabled. 825 * @param identity Who to disable the accounts on 826 * @param onlyThese Only Link objects where the Predicate returns true will be disabled 827 * @throws GeneralException if any failures occur 828 */ 829 public void disableAccounts(Identity identity, Predicate<Link> onlyThese) throws GeneralException { 830 Objects.requireNonNull(identity, "Provided Identity must not be null"); 831 ProvisioningPlan plan = new ProvisioningPlan(); 832 plan.setIdentity(identity); 833 for(Link link : Util.safeIterable(identity.getLinks())) { 834 if (!link.isDisabled() && onlyThese.test(link)) { 835 plan.add(link.getApplicationName(), link.getNativeIdentity(), AccountRequest.Operation.Disable); 836 } 837 } 838 doProvisioning(plan); 839 } 840 841 /** 842 * Submits a single request to disable all accounts on the Identity that are not already disabled. 843 * @param identity Who to disable the accounts on 844 * @param onlyThese Only Link objects matching the filter will be disabled. This uses the {@link HybridObjectMatcher}, allowing fields like "application.name" in the filter. 845 * @throws GeneralException if any failures occur 846 */ 847 public void disableAccounts(Identity identity, Filter onlyThese) throws GeneralException { 848 Objects.requireNonNull(identity, "Provided Identity must not be null"); 849 ProvisioningPlan plan = new ProvisioningPlan(); 850 plan.setIdentity(identity); 851 for(Link link : Util.safeIterable(identity.getLinks())) { 852 HybridObjectMatcher matcher = new HybridObjectMatcher(context, onlyThese); 853 if (!link.isDisabled() && matcher.matches(link)) { 854 plan.add(link.getApplicationName(), link.getNativeIdentity(), AccountRequest.Operation.Disable); 855 } 856 } 857 doProvisioning(plan); 858 } 859 860 /** 861 * Submits a provisioning plan using the configured defaults. This plan must have an Identity attached to it using setIdentity(). 862 * @param plan The ProvisioningPlan to execute 863 * @throws GeneralException if any failures occur 864 */ 865 public ProvisioningProject doProvisioning(ProvisioningPlan plan) throws GeneralException { 866 return doProvisioning(plan, false, provisioningArguments.getDefaultExtraParameters()); 867 } 868 869 /** 870 * Submits a provisioning plan using the configured defaults. 871 * @param identityName If the plan does not already have an Identity configured, this one will be used. 872 * @param plan The provisioning plan. 873 * @return The compiled provisioning project, post-provision 874 * @throws GeneralException if any failures occur 875 */ 876 public ProvisioningProject doProvisioning(String identityName, ProvisioningPlan plan) throws GeneralException { 877 return doProvisioning(identityName, plan, false); 878 } 879 880 /** 881 * Submits a provisioning plan using the configured defaults and optionally does a refresh. 882 * @param identityName If the plan does not already have an Identity configured, this one will be used. 883 * @param plan The provisioning plan 884 * @param doRefresh If true, a refresh will be performed by the provisioning handler 885 * @return The compiled provisioning project, post-provision 886 * @throws GeneralException if any IIQ failures occur 887 */ 888 public ProvisioningProject doProvisioning(String identityName, ProvisioningPlan plan, boolean doRefresh) throws GeneralException { 889 return doProvisioning(identityName, plan, doRefresh, provisioningArguments.getDefaultExtraParameters()); 890 } 891 892 /** 893 * Submits a provisioning plan using the configured defaults and optionally does a refresh. Additionally, extra arguments to the workflow can be provided in a Map. 894 * @param identityName If the plan does not already have an Identity configured, this one will be used. 895 * @param plan The provisioning plan 896 * @param doRefresh If true, a refresh will be performed by the provisioning handler 897 * @param extraParameters A Map containing workflow parameters that will be passed to the provisioning workflow or Provisioner 898 * @return The compiled provisioning project, post-provision 899 * @throws GeneralException if any IIQ failures occur 900 */ 901 public ProvisioningProject doProvisioning(String identityName, ProvisioningPlan plan, boolean doRefresh, Map<String, Object> extraParameters) throws GeneralException { 902 if (plan.getIdentity() == null) { 903 plan.setIdentity(context.getObjectByName(Identity.class, identityName)); 904 } 905 return doProvisioning(plan, doRefresh, extraParameters); 906 } 907 908 /** 909 * Submits a provisioning plan using the configured defaults and optionally does a refresh. Additionally, extra arguments to the workflow can be provided in a Map. 910 * @param plan The provisioning plan 911 * @param doRefresh If true, a refresh will be performed by the provisioning handler 912 * @param extraParameters A Map containing workflow parameters that will be passed to the provisioning workflow or Provisioner 913 * @throws GeneralException if any IIQ failures occur 914 */ 915 public ProvisioningProject doProvisioning(ProvisioningPlan plan, boolean doRefresh, Map<String, Object> extraParameters) throws GeneralException { 916 Attributes<String, Object> arguments = getArguments(plan); 917 if (planArguments != null) { 918 arguments.putAll(planArguments); 919 } 920 plan.setArguments(arguments); 921 if (beforeProvisioningConsumer != null) { 922 beforeProvisioningConsumer.accept(plan); 923 } 924 ProvisioningProject outputProject = null; 925 if (isErrorOnAccountSelection() || isErrorOnManualTask() || isErrorOnProvisioningForms() || isErrorOnNewAccount() || projectDebugger != null) { 926 PlanCompiler compiler = new PlanCompiler(context); 927 ProvisioningProject project = compiler.compile(new Attributes<>(extraParameters), plan, new Attributes<>()); 928 if (projectDebugger != null) { 929 projectDebugger.accept(project); 930 } 931 if (isErrorOnManualTask() && project.hasUnmanagedPlan()) { 932 throw new GeneralException("Provisioning request refused because it would result in a manual task"); 933 } 934 if (isErrorOnProvisioningForms() && (project.hasQuestions() || project.hasUnansweredAccountSelections() || project.hasUnansweredProvisioningTargets())) { 935 throw new GeneralException("Provisioning request refused because it would result in an unanswered form"); 936 } 937 if (isErrorOnNewAccount()) { 938 long count = project.getPlans().stream().flatMap(p -> p.getAccountRequests() != null ? p.getAccountRequests().stream() : null).filter(req -> req.getOperation() != null && req.getOperation().equals(AccountRequest.Operation.Create)).count(); 939 if (count > 0) { 940 throw new GeneralException("Provisioning request refused because it would result in a new account creation"); 941 } 942 } 943 if (isErrorOnAccountSelection() && (project.hasUnansweredAccountSelections() || project.hasUnansweredProvisioningTargets())) { 944 throw new GeneralException("Provisioning request refused because we could not identify a target account"); 945 } 946 } 947 if (provisioningArguments.isUseWorkflow()) { 948 String planField = provisioningArguments.getPlanFieldName(); 949 String userField = provisioningArguments.getIdentityFieldName(); 950 951 Map<String, Object> workflowParameters = new HashMap<>(); 952 workflowParameters.put(planField, plan); 953 workflowParameters.put(userField, plan.getIdentity().getName()); 954 955 if (doRefresh) { 956 workflowParameters.put("doRefresh", true); 957 } 958 959 if (extraParameters != null) { 960 workflowParameters.putAll(extraParameters); 961 } 962 963 Workflow wf = context.getObjectByName(Workflow.class, provisioningArguments.getWorkflowName()); 964 Workflower workflower = new Workflower(context); 965 WorkflowLaunch launchOutput = workflower.launch(wf, getCaseName(plan.getIdentity().getName()), workflowParameters); 966 967 if (workflowDebugger != null) { 968 workflowDebugger.accept(launchOutput); 969 } 970 971 if (launchOutput != null && launchOutput.isFailed()) { 972 throw new GeneralException("Workflow launch failed: " + launchOutput.getTaskResult().getMessages()); 973 } 974 975 if (launchOutput != null && launchOutput.getTaskResult() != null) { 976 ProvisioningPlan loggingPlan = ProvisioningPlan.getLoggingPlan(plan); 977 if (loggingPlan != null) { 978 launchOutput.getTaskResult().addMessage(Message.info(loggingPlan.toXml())); 979 } 980 981 String identityRequest = launchOutput.getTaskResult().getString("identityRequestId"); 982 if (Util.isNotNullOrEmpty(identityRequest)) { 983 IdentityRequest ir = context.getObject(IdentityRequest.class, identityRequest); 984 if (ir != null) { 985 boolean save = false; 986 if (Util.isNotNullOrEmpty(plan.getComments())) { 987 ir.addMessage(Message.info(plan.getComments())); 988 save = true; 989 } 990 if (Util.isNotNullOrEmpty(externalTicketId)) { 991 ir.setExternalTicketId(externalTicketId); 992 save = true; 993 } 994 if (save) { 995 context.saveObject(ir); 996 context.commitTransaction(); 997 context.decache(ir); 998 } 999 outputProject = ir.getProvisionedProject(); 1000 } 1001 } 1002 } 1003 } else { 1004 Provisioner provisioner = new Provisioner(context); 1005 if (extraParameters != null) { 1006 extraParameters.forEach(provisioner::setArgument); 1007 } 1008 provisioner.setDoRefresh(doRefresh); 1009 provisioner.execute(plan); 1010 ProvisioningProject project = provisioner.getProject(); 1011 // Do a refresh only if the project is fully committed; otherwise we might break something 1012 if (project.isFullyCommitted() && doRefresh) { 1013 Identity id = context.getObjectByName(Identity.class, plan.getIdentity().getName()); 1014 new BaseIdentityUtilities(context).refresh(id, true); 1015 } 1016 outputProject = project; 1017 } 1018 return outputProject; 1019 } 1020 1021 /** 1022 * Enables the given account by submitting an Enable provisioning action to IIQ 1023 * @param link The Link to enable 1024 * @throws GeneralException if any IIQ errors occur 1025 */ 1026 public void enableAccount(Link link) throws GeneralException { 1027 Objects.requireNonNull(link, "Provided Link must not be null"); 1028 ProvisioningPlan plan = new ProvisioningPlan(); 1029 String username = ObjectUtil.getIdentityFromLink(context, link.getApplication(), link.getInstance(), link.getNativeIdentity()); 1030 Identity user = findIdentity(username); 1031 plan.setIdentity(user); 1032 plan.add(link.getApplicationName(), link.getNativeIdentity(), AccountRequest.Operation.Enable); 1033 doProvisioning(plan); 1034 } 1035 1036 /** 1037 * Finds the identity first by name and then by ID. This is mainly here so that 1038 * it can be overridden by customer-specific subclasses. Otherwise, it does the 1039 * same thing as {@link SailPointContext#getObject(Class, String)}. 1040 * 1041 * @param identityName The identity name to search for 1042 * @return The Identity if found 1043 * @throws GeneralException if any errors occur 1044 */ 1045 public Identity findIdentity(String identityName) throws GeneralException { 1046 Objects.requireNonNull(identityName); 1047 Identity output = context.getObjectByName(Identity.class, identityName); 1048 if (output != null) { 1049 return output; 1050 } 1051 output = context.getObjectById(Identity.class, identityName); 1052 return output; 1053 } 1054 1055 /** 1056 * Force retry on the given transaction. There is an out-of-box API for doing this 1057 * on transactions pending retry (to force them to run 'right now' rather than 'later'), 1058 * but there is none for doing this on failed transactions. 1059 * 1060 * @param pt The transaction to retry 1061 * @param createToModify If true, a Create operation will be transmuted to a Modify 1062 * @throws GeneralException if any failures occur 1063 */ 1064 public ProvisioningProject forceRetry(ProvisioningTransaction pt, boolean createToModify) throws GeneralException { 1065 Objects.requireNonNull(pt, "Provided ProvisioningTransaction must not be null"); 1066 if (pt.getStatus().equals(ProvisioningTransaction.Status.Failed)) { 1067 AccountRequest request = (AccountRequest)pt.getAttributes().get("request"); 1068 if (request != null) { 1069 AccountRequest cloned = (AccountRequest)request.cloneRequest(); 1070 ProvisioningResult originalResult = request.getResult(); 1071 if (originalResult != null && originalResult.getStatus() != null) { 1072 ProvisioningResult newResult = new ProvisioningResult(); 1073 newResult.setWarnings(originalResult.getErrors()); 1074 newResult.setStatus(ProvisioningResult.STATUS_RETRY); 1075 cloned.setResult(newResult); 1076 } 1077 if (createToModify && cloned.getOperation() != null && cloned.getOperation().equals(AccountRequest.Operation.Create)) { 1078 cloned.setOperation(AccountRequest.Operation.Modify); 1079 } 1080 Attributes<String, Object> arguments = getArguments(cloned); 1081 arguments.put("provisioningTransactionId", pt.getId()); 1082 cloned.setArguments(arguments); 1083 ProvisioningPlan retryPlan = new ProvisioningPlan(); 1084 retryPlan.add(cloned); 1085 retryPlan.setIdentity(context.getObject(Identity.class, pt.getIdentityName())); 1086 retryPlan.setTargetIntegration(pt.getIntegration()); 1087 retryPlan.setComments("Retry for Provisioning Transaction ID " + pt.getId()); 1088 setUseWorkflow(false); 1089 return doProvisioning(retryPlan.getIdentity().getName(), retryPlan, false, new HashMap<>()); 1090 } else { 1091 throw new IllegalArgumentException("You can only forceRetry on transaction with an AccountRequest attached"); 1092 } 1093 } else { 1094 throw new IllegalArgumentException("You can only forceRetry on failed transactions; IIQ will redo 'retry' transactions on its own"); 1095 } 1096 } 1097 1098 /** 1099 * Builds the case name based on the template provided 1100 * @param identityName The identity name passed to this case 1101 * @return The case name generated 1102 */ 1103 public String getCaseName(String identityName) { 1104 String caseName = provisioningArguments.getCaseNameTemplate().replace("{Workflow}", provisioningArguments.getWorkflowName()); 1105 caseName = caseName.replace("{IdentityName}", identityName); 1106 caseName = caseName.replace("{Timestamp}", String.valueOf(System.currentTimeMillis())); 1107 if (caseName.contains("{Date}")) { 1108 DateTimeFormatter formatter = DateTimeFormatter.ISO_INSTANT; 1109 Instant now = Instant.now(); 1110 caseName = caseName.replace("{Date}", formatter.format(now)); 1111 } 1112 1113 return caseName; 1114 } 1115 1116 public String getCaseNameTemplate() { 1117 return provisioningArguments.getCaseNameTemplate(); 1118 } 1119 1120 public String getExternalTicketId() { 1121 return externalTicketId; 1122 } 1123 1124 public String getProvisioningWorkflow() { 1125 return provisioningArguments.getWorkflowName(); 1126 } 1127 1128 public boolean isErrorOnAccountSelection() { 1129 return provisioningArguments.isErrorOnAccountSelection(); 1130 } 1131 1132 public boolean isErrorOnManualTask() { 1133 return provisioningArguments.isErrorOnManualTask(); 1134 } 1135 1136 public boolean isErrorOnNewAccount() { 1137 return provisioningArguments.isErrorOnNewAccount(); 1138 } 1139 1140 public boolean isErrorOnProvisioningForms() { 1141 return provisioningArguments.isErrorOnProvisioningForms(); 1142 } 1143 1144 public boolean isUseWorkflow() { 1145 return provisioningArguments.isUseWorkflow(); 1146 } 1147 1148 /** 1149 * Moves the given target account(s) to the given target owner 1150 * @param targetOwner The target owner for the given accounts 1151 * @param accounts One or more accounts to move to the new owner 1152 * @throws GeneralException if anything goes wrong during provisioning 1153 */ 1154 public void moveLinks(Identity targetOwner, Link... accounts) throws GeneralException { 1155 if (accounts != null && accounts.length > 0) { 1156 ProvisioningPlan plan = new ProvisioningPlan(); 1157 for (Link account : accounts) { 1158 linkMovePlan(account, targetOwner, plan); 1159 } 1160 doProvisioning(plan); 1161 } 1162 } 1163 1164 /** 1165 * Removes all entitlements and assigned roles from the given Identity 1166 * 1167 * @param identity The identity from whom to strip access 1168 * @throws GeneralException if anything goes wrong during provisioning 1169 */ 1170 public void removeAllAccess(Identity identity) throws GeneralException { 1171 ProvisioningPlan entitlementPlan = new ProvisioningPlan(); 1172 entitlementPlan.setIdentity(identity); 1173 1174 for(Link account : Util.safeIterable(identity.getLinks())) { 1175 List<String> entitlementAttributes = account.getApplication().getEntitlementAttributeNames(); 1176 for(String attribute : Util.safeIterable(entitlementAttributes)) { 1177 removeAllEntitlements(account, attribute, entitlementPlan); 1178 } 1179 } 1180 1181 Map<String, Object> options = new HashMap<>(); 1182 options.put(PLAN_PARAM_APPROVAL_SCHEME, NO_APPROVAL_SCHEME); 1183 1184 doProvisioning(identity.getName(), entitlementPlan, false, options); 1185 1186 ObjectUtil.saveDecacheAttach(context, identity); 1187 1188 ProvisioningPlan rolePlan = new ProvisioningPlan(); 1189 rolePlan.setIdentity(identity); 1190 1191 for(Bundle role : Util.safeIterable(identity.getAssignedRoles())) { 1192 removeUserRolePlan(role.getName(), true, rolePlan); 1193 } 1194 1195 doProvisioning(identity.getName(), rolePlan, false, options); 1196 1197 ObjectUtil.saveDecacheAttach(context, identity); 1198 } 1199 1200 /** 1201 * Removes all entitlements from all accounts of the given type on the given user 1202 * @param identity The identity to target 1203 * @param target The target application from which to remove accounts 1204 * @throws GeneralException if a failure occurs 1205 */ 1206 public void removeAllEntitlements(Identity identity, Application target) throws GeneralException { 1207 ProvisioningPlan plan = new ProvisioningPlan(); 1208 plan.setIdentity(identity); 1209 removeAllEntitlements(identity, target, plan); 1210 Map<String, Object> options = new HashMap<>(); 1211 options.put(PLAN_PARAM_APPROVAL_SCHEME, NO_APPROVAL_SCHEME); 1212 1213 doProvisioning(identity.getName(), plan, false, options); 1214 } 1215 1216 /** 1217 * Modifies the plan to add entitlement removal requests for all entitlements on 1218 * accounts of the given type 1219 * @param identity The identity from which to extract the entitlements 1220 * @param target The target application 1221 * @param plan The provisioning plan 1222 * @throws GeneralException if any failures occur 1223 */ 1224 public void removeAllEntitlements(Identity identity, Application target, ProvisioningPlan plan) throws GeneralException { 1225 IdentityService identityService = new IdentityService(context); 1226 List<Link> links = identityService.getLinks(identity, target); 1227 List<String> entitlementAttributes = target.getEntitlementAttributeNames(); 1228 for(Link account : Util.safeIterable(links)) { 1229 for(String attribute : Util.safeIterable(entitlementAttributes)) { 1230 removeAllEntitlements(account, attribute, plan); 1231 } 1232 } 1233 } 1234 1235 /** 1236 * Modifies the plan to remove all values from the given attribute on the given 1237 * account. 1238 * @param account The account to modify 1239 * @param attribute The attribute to remove attributes from 1240 * @param plan The provisioning plan 1241 */ 1242 @SuppressWarnings("unchecked") 1243 public void removeAllEntitlements(Link account, String attribute, ProvisioningPlan plan) { 1244 if (account == null || account.getAttributes() == null) { 1245 return; 1246 } 1247 List<String> values = account.getAttributes().getList(attribute); 1248 if (values != null && !values.isEmpty()) { 1249 AccountRequest accountRequest = plan.getAccountRequest(account.getApplicationName(), null, account.getNativeIdentity()); 1250 if (accountRequest == null) { 1251 accountRequest = new AccountRequest(AccountRequest.Operation.Modify, account.getApplicationName(), null, account.getNativeIdentity()); 1252 plan.add(accountRequest); 1253 } 1254 AttributeRequest attributeRequest = new AttributeRequest(attribute, ProvisioningPlan.Operation.Remove, values); 1255 attributeRequest.setAssignment(true); 1256 accountRequest.add(attributeRequest); 1257 } 1258 } 1259 1260 /** 1261 * Removes the given entitlement from the given account on the user 1262 * @param identityName The identity name 1263 * @param account The account to modify 1264 * @param entitlement The managed attribute from which to extract the entitlement 1265 * @param withApproval If false, approval will be skipped 1266 * @throws GeneralException if any failure occurs 1267 */ 1268 public void removeEntitlement(String identityName, Link account, ManagedAttribute entitlement, boolean withApproval) throws GeneralException { 1269 ProvisioningPlan plan = new ProvisioningPlan(); 1270 Identity identity = findIdentity(identityName); 1271 plan.setIdentity(identity); 1272 1273 AccountRequest accountRequest = new AccountRequest(AccountRequest.Operation.Modify, account.getApplicationName(), account.getInstance(), account.getNativeIdentity()); 1274 plan.add(accountRequest); 1275 1276 AttributeRequest attributeRequest = new AttributeRequest(entitlement.getName(), ProvisioningPlan.Operation.Remove, entitlement.getValue()); 1277 attributeRequest.setAssignment(true); 1278 accountRequest.add(attributeRequest); 1279 1280 Map<String, Object> options = new HashMap<>(); 1281 if (!withApproval) { 1282 options.put(PLAN_PARAM_APPROVAL_SCHEME, NO_APPROVAL_SCHEME); 1283 } 1284 1285 doProvisioning(identityName, plan, false, options); 1286 } 1287 1288 /** 1289 * Removes the given user from the given role 1290 * @param targetAssignment The target existing RoleAssignment from an Identity 1291 * @throws GeneralException if a provisioning failure occurs 1292 */ 1293 public void removeUserRole(String identityName, RoleAssignment targetAssignment) throws GeneralException { 1294 Objects.requireNonNull(identityName, "A non-null Identity name is required"); 1295 Objects.requireNonNull(targetAssignment, "The provided RoleAssignment must not be null"); 1296 if (targetAssignment.isNegative()) { 1297 throw new IllegalArgumentException("Cannot remove a negative assignment using this API"); 1298 } 1299 ProvisioningPlan.Operation roleOp = ProvisioningPlan.Operation.Remove; 1300 if (Util.nullSafeEq(targetAssignment.getSource(), "Rule")) { 1301 roleOp = ProvisioningPlan.Operation.Revoke; 1302 } 1303 Identity identity = findIdentity(identityName); 1304 ProvisioningPlan provisioningPlan = new ProvisioningPlan(); 1305 provisioningPlan.setIdentity(identity); 1306 AccountRequest accountRequest = new AccountRequest(AccountRequest.Operation.Modify, ProvisioningPlan.APP_IIQ, null, identity.getName()); 1307 AttributeRequest attributeRequest = new AttributeRequest(ASSIGNED_ROLES_ATTR, roleOp, targetAssignment.getRoleName()); 1308 attributeRequest.setAssignmentId(targetAssignment.getAssignmentId()); 1309 accountRequest.add(attributeRequest); 1310 provisioningPlan.add(accountRequest); 1311 Map<String, Object> options = new HashMap<>(); 1312 options.put(PLAN_PARAM_APPROVAL_SCHEME, NO_APPROVAL_SCHEME); 1313 doProvisioning(identity.getName(), provisioningPlan, false, options); 1314 } 1315 1316 /** 1317 * Removes the given user from the given role. For a detected role, this will 1318 * remove any entitlements provisioned by that role that are not required by 1319 * another role assigned to the user. 1320 * 1321 * @param identityName The name of the Identity to modify 1322 * @param targetDetection The target existing RoleDetection from an Identity 1323 * @throws GeneralException if a provisioning failure occurs 1324 */ 1325 public void removeUserRole(String identityName, RoleDetection targetDetection) throws GeneralException { 1326 Objects.requireNonNull(identityName, "A non-null Identity name is required"); 1327 Objects.requireNonNull(targetDetection, "The provided RoleDetection must not be null"); 1328 if (targetDetection.hasAssignmentIds()) { 1329 throw new IllegalArgumentException("Cannot remove a required detected role using this API"); 1330 } 1331 String tempAssignmentKey = "TEMP:" + Util.uuid(); 1332 Identity identity = findIdentity(identityName); 1333 ProvisioningPlan provisioningPlan = new ProvisioningPlan(); 1334 provisioningPlan.setIdentity(identity); 1335 AccountRequest accountRequest = new AccountRequest(AccountRequest.Operation.Modify, ProvisioningPlan.APP_IIQ, null, identity.getName()); 1336 AttributeRequest attributeRequest = new AttributeRequest("detectedRoles", ProvisioningPlan.Operation.Remove, targetDetection.getRoleName()); 1337 1338 BaseIdentityUtilities identityUtilities = new BaseIdentityUtilities(context); 1339 if (identityUtilities.hasMultiple(identity, targetDetection.getRoleName())) { 1340 ProvisioningTarget target = new ProvisioningTarget(); 1341 target.setAssignmentId(tempAssignmentKey); 1342 target.setRole(targetDetection.getRoleName()); 1343 Utilities.safeStream(targetDetection.getTargets()).map(ProvisioningUtilities::roleTargetToAccountSelection).forEach(target::addAccountSelection); 1344 if (Util.nullSafeSize(target.getAccountSelections()) > 0) { 1345 List<ProvisioningTarget> targets = provisioningPlan.getProvisioningTargets(); 1346 if (targets == null) { 1347 targets = new ArrayList<>(); 1348 } 1349 targets.add(target); 1350 provisioningPlan.setProvisioningTargets(targets); 1351 attributeRequest.setAssignmentId(tempAssignmentKey); 1352 } 1353 } 1354 accountRequest.add(attributeRequest); 1355 provisioningPlan.add(accountRequest); 1356 Map<String, Object> options = new HashMap<>(); 1357 options.put(PLAN_PARAM_APPROVAL_SCHEME, NO_APPROVAL_SCHEME); 1358 doProvisioning(identity.getName(), provisioningPlan, false, options); 1359 } 1360 1361 /** 1362 * Removes the given user from the given role associated with the target provisioned account. 1363 * Note that the role may also be associated with a different account. This is used only to 1364 * locate the RoleAssignment object for deprovisioning by assignment ID. 1365 * @param identityName The identity name to add to the given role 1366 * @param roleName The role to add 1367 * @param withApproval If true, default approval will be required 1368 * @param target If not null, this will be used as a provisioning target for the plan 1369 * @throws GeneralException if a provisioning failure occurs 1370 */ 1371 public void removeUserRole(String identityName, String roleName, boolean withApproval, Link target) throws GeneralException { 1372 // We have to generate our own assignment ID in this case 1373 Bundle role = context.getObjectByName(Bundle.class, roleName); 1374 if (role == null) { 1375 throw new IllegalArgumentException("Role " + roleName + " does not exist"); 1376 } 1377 Identity identity = findIdentity(identityName); 1378 List<RoleAssignment> existingAssignments = identity.getRoleAssignments(role); 1379 RoleAssignment targetAssignment = null; 1380 for(RoleAssignment ra : existingAssignments) { 1381 RoleTarget rt = new RoleTarget(target); 1382 if (ra.hasMatchingRoleTarget(rt)) { 1383 targetAssignment = ra; 1384 break; 1385 } 1386 } 1387 if (targetAssignment == null) { 1388 throw new IllegalArgumentException("The user " + identityName + " does not have a role " + roleName + " targeting " + target.getApplicationName() + " account " + target.getNativeIdentity()); 1389 } 1390 ProvisioningPlan provisioningPlan = new ProvisioningPlan(); 1391 provisioningPlan.setIdentity(identity); 1392 AccountRequest accountRequest = new AccountRequest(AccountRequest.Operation.Modify, ProvisioningPlan.APP_IIQ, null, identity.getName()); 1393 AttributeRequest attributeRequest = new AttributeRequest(ASSIGNED_ROLES_ATTR, ProvisioningPlan.Operation.Remove, roleName); 1394 attributeRequest.setAssignmentId(targetAssignment.getAssignmentId()); 1395 accountRequest.add(attributeRequest); 1396 provisioningPlan.add(accountRequest); 1397 Map<String, Object> options = new HashMap<>(); 1398 if (!withApproval) { 1399 options.put(PLAN_PARAM_APPROVAL_SCHEME, NO_APPROVAL_SCHEME); 1400 } 1401 doProvisioning(provisioningPlan.getIdentity().getName(), provisioningPlan, false, options); 1402 } 1403 1404 /** 1405 * Removes the given role from the given user 1406 * @param identityName The identity to remove the role from 1407 * @param roleName The role to remove from the identity 1408 * @throws GeneralException If a failure occurs 1409 */ 1410 public void removeUserRole(String identityName, String roleName) throws GeneralException { 1411 removeUserRole(identityName, roleName, false, false); 1412 } 1413 1414 /** 1415 * Removes the given role from the given user 1416 * @param identityName The identity to remove the role from 1417 * @param roleName The role to remove from the identity 1418 * @param withApproval If true, a default approval will be required 1419 * @throws GeneralException If a failure occurs 1420 */ 1421 public void removeUserRole(String identityName, String roleName, boolean withApproval) throws GeneralException { 1422 removeUserRole(identityName, roleName, withApproval, false); 1423 } 1424 1425 /** 1426 * Removes the given role from the given user 1427 * @param identityName The identity to remove the role from 1428 * @param roleName The role to remove from the identity 1429 * @param withApproval If true, a default approval will be required 1430 * @param revoke If true, the role will be revoked and not removed 1431 * @throws GeneralException If a failure occurs 1432 */ 1433 public void removeUserRole(String identityName, String roleName, boolean withApproval, boolean revoke) throws GeneralException { 1434 ProvisioningPlan provisioningPlan = new ProvisioningPlan(); 1435 Identity identity = findIdentity(identityName); 1436 provisioningPlan.setIdentity(identity); 1437 removeUserRolePlan(roleName, revoke, provisioningPlan); 1438 Map<String, Object> options = new HashMap<>(); 1439 if (!withApproval) { 1440 options.put(PLAN_PARAM_APPROVAL_SCHEME, NO_APPROVAL_SCHEME); 1441 } 1442 doProvisioning(identity.getName(), provisioningPlan, false, options); 1443 } 1444 1445 public void setBeforeProvisioning(Consumer<ProvisioningPlan> planConsumer) { 1446 this.beforeProvisioningConsumer = planConsumer; 1447 } 1448 1449 public void setCaseNameTemplate(String caseNameTemplate) { 1450 provisioningArguments.setCaseNameTemplate(caseNameTemplate); 1451 } 1452 1453 public void setErrorOnAccountSelection(boolean errorOnAccountSelection) { 1454 provisioningArguments.setErrorOnAccountSelection(errorOnAccountSelection); 1455 } 1456 1457 public void setErrorOnManualTask(boolean errorOnManualTask) { 1458 provisioningArguments.setErrorOnManualTask(errorOnManualTask); 1459 } 1460 1461 public void setErrorOnNewAccount(boolean errorOnNewAccount) { 1462 provisioningArguments.setErrorOnNewAccount(errorOnNewAccount); 1463 } 1464 1465 public void setErrorOnProvisioningForms(boolean errorOnProvisioningForms) { 1466 provisioningArguments.setErrorOnProvisioningForms(errorOnProvisioningForms); 1467 } 1468 1469 public void setExternalTicketId(String externalTicketId) { 1470 this.externalTicketId = externalTicketId; 1471 } 1472 1473 public void setProjectDebugger(Consumer<ProvisioningProject> projectDebugger) { 1474 this.projectDebugger = projectDebugger; 1475 } 1476 1477 public void setLauncher(String who) { 1478 addWorkflowArgument("launcher", who); 1479 } 1480 1481 /** 1482 * Sets workflow configuration items all at once for this utility 1483 * @param config The workflow configuration to use 1484 */ 1485 public void setProvisioningArguments(ProvisioningArguments config) { 1486 this.provisioningArguments.merge(config); 1487 } 1488 1489 public void setProvisioningWorkflow(String provisioningWorkflow) { 1490 provisioningArguments.setWorkflowName(provisioningWorkflow); 1491 } 1492 1493 public void setUseWorkflow(boolean useWorkflow) { 1494 provisioningArguments.setUseWorkflow(useWorkflow); 1495 } 1496 1497 public void setWorkflowDebugger(Consumer<WorkflowLaunch> workflowDebugger) { 1498 this.workflowDebugger = workflowDebugger; 1499 } 1500 1501 /** 1502 * Transforms this object into a Map that can be passed to the constructor 1503 * that takes a Map 1504 * 1505 * @return The resulting map transformation 1506 */ 1507 public Attributes<String, Object> toMap() { 1508 Attributes<String, Object> map = new Attributes<>(); 1509 map.put("caseNameTemplate", this.provisioningArguments.getCaseNameTemplate()); 1510 map.put("defaultExtraParameters", this.provisioningArguments.getDefaultExtraParameters()); 1511 map.put("errorOnAccountSelection", this.provisioningArguments.isErrorOnAccountSelection()); 1512 map.put("errorOnManualTask", this.provisioningArguments.isErrorOnManualTask()); 1513 map.put("errorOnNewAccount", this.provisioningArguments.isErrorOnNewAccount()); 1514 map.put("errorOnProvisioningForms", this.provisioningArguments.isErrorOnProvisioningForms()); 1515 map.put("externalTicketId", this.externalTicketId); 1516 map.put("planArguments", this.planArguments); 1517 map.put("workflowPlanField", this.provisioningArguments.getPlanFieldName()); 1518 map.put("workflowIdentityField", this.provisioningArguments.getIdentityFieldName()); 1519 map.put("provisioningWorkflow", this.provisioningArguments.getWorkflowName()); 1520 map.put("useWorkflow", this.provisioningArguments.isUseWorkflow()); 1521 return map; 1522 } 1523 1524 /** 1525 * Updates the given link with the given values. Field names can also have the form "Operation:Name", e.g. "Add:memberOf", to specify an operation. 1526 * 1527 * Values 'Set' to a multi-value field will be transformed to 'Add' by default. You can override this using the colon syntax above, which will always take priority. 1528 * 1529 * @param link The Link to update 1530 * @param map The values to update (Set by default) 1531 * @throws GeneralException if any provisioning failures occur 1532 */ 1533 public void updateAccount(Link link, Map<String, Object> map) throws GeneralException { 1534 Objects.requireNonNull(link, "The provided Link must not be null"); 1535 if (map == null || map.isEmpty()) { 1536 log.warn("Call made to updateAccount() with a null or empty map of updates"); 1537 return; 1538 } 1539 ProvisioningPlan plan = new ProvisioningPlan(); 1540 String username = ObjectUtil.getIdentityFromLink(context, link.getApplication(), link.getInstance(), link.getNativeIdentity()); 1541 Identity user = findIdentity(username); 1542 1543 plan.setIdentity(user); 1544 1545 for(String key : map.keySet()) { 1546 String provisioningName = key; 1547 ProvisioningPlan.Operation operation = ProvisioningPlan.Operation.Set; 1548 if (key.contains(":")) { 1549 String[] components = key.split(":"); 1550 operation = ProvisioningPlan.Operation.valueOf(components[0]); 1551 provisioningName = components[1]; 1552 } 1553 // For multi-valued attributes, transform set to add by default 1554 if (operation.equals(ProvisioningPlan.Operation.Set) && !key.contains(":")) { 1555 AttributeDefinition attributeDefinition = link.getApplication().getAccountSchema().getAttributeDefinition(provisioningName); 1556 if (attributeDefinition.isMultiValued()) { 1557 operation = ProvisioningPlan.Operation.Add; 1558 } 1559 } 1560 AccountRequest request = plan.add(link.getApplicationName(), link.getNativeIdentity(), provisioningName, operation, map.get(key)); 1561 if (request.getOperation() == null) { 1562 request.setOperation(AccountRequest.Operation.Modify); 1563 } 1564 } 1565 1566 Map<String, Object> extraParameters = new HashMap<>(); 1567 extraParameters.put(PLAN_PARAM_APPROVAL_SCHEME, NO_APPROVAL_SCHEME); 1568 doProvisioning(username, plan, false, extraParameters); 1569 } 1570 1571 /** 1572 * Updates the given link by setting or adding the given values. Multi-value attributes will be transformed to Set. 1573 * 1574 * @param link The Link to update 1575 * @param attribute The name of the attribute to either set or add 1576 * @param value The value(s) to set or add 1577 * @throws GeneralException if any provisioning failures occur 1578 */ 1579 public void updateAccountRemove(Link link, String attribute, Object value) throws GeneralException { 1580 Objects.requireNonNull(link, "The provided Link must not be null"); 1581 ProvisioningPlan plan = new ProvisioningPlan(); 1582 String username = ObjectUtil.getIdentityFromLink(context, link.getApplication(), link.getInstance(), link.getNativeIdentity()); 1583 Identity user = findIdentity(username); 1584 1585 plan.setIdentity(user); 1586 1587 AccountRequest request = plan.add(link.getApplicationName(), link.getNativeIdentity(), attribute, ProvisioningPlan.Operation.Remove, value); 1588 if (request.getOperation() == null) { 1589 request.setOperation(AccountRequest.Operation.Modify); 1590 } 1591 1592 Map<String, Object> extraParameters = new HashMap<>(); 1593 extraParameters.put(PLAN_PARAM_APPROVAL_SCHEME, NO_APPROVAL_SCHEME); 1594 doProvisioning(username, plan, false, extraParameters); 1595 } 1596 1597 /** 1598 * Updates the given link by setting or adding the given values. Multi-value attributes will be transformed to Set. 1599 * 1600 * @param link The Link to update 1601 * @param attribute The name of the attribute to either set or add 1602 * @param value The value(s) to set or add 1603 * @throws GeneralException if any provisioning failures occur 1604 */ 1605 public void updateAccountSet(Link link, String attribute, Object value) throws GeneralException { 1606 Objects.requireNonNull(link, "The provided Link must not be null"); 1607 ProvisioningPlan plan = new ProvisioningPlan(); 1608 String username = ObjectUtil.getIdentityFromLink(context, link.getApplication(), link.getInstance(), link.getNativeIdentity()); 1609 Identity user = findIdentity(username); 1610 1611 plan.setIdentity(user); 1612 1613 ProvisioningPlan.Operation operation = ProvisioningPlan.Operation.Set; 1614 // For multi-valued attributes, transform set to add 1615 AttributeDefinition attributeDefinition = link.getApplication().getAccountSchema().getAttributeDefinition(attribute); 1616 if (attributeDefinition != null && attributeDefinition.isMultiValued()) { 1617 operation = ProvisioningPlan.Operation.Add; 1618 } 1619 AccountRequest request = plan.add(link.getApplicationName(), link.getNativeIdentity(), attribute, operation, value); 1620 if (request.getOperation() == null) { 1621 request.setOperation(AccountRequest.Operation.Modify); 1622 } 1623 Map<String, Object> extraParameters = new HashMap<>(); 1624 extraParameters.put(PLAN_PARAM_APPROVAL_SCHEME, NO_APPROVAL_SCHEME); 1625 doProvisioning(username, plan, false, extraParameters); 1626 } 1627 1628 /** 1629 * Updates the given identity with the given values. Field names can also have the form "Operation:Name", e.g. "Add:memberOf", to specify an operation. 1630 * 1631 * Values 'Set' to a multi-value field will be transformed to 'Add' by default. You can override this using the colon syntax above, which will always take priority. 1632 * 1633 * @param identity The identity to modify 1634 * @param params The parameters to modify 1635 * @throws GeneralException if anything goes wrong 1636 */ 1637 public void updateUser(Identity identity, Map<String, Object> params) throws GeneralException { 1638 updateUser(identity, "Set", params); 1639 } 1640 1641 /** 1642 * Updates the given identity with the given values. Field names can also have the form "Operation:Name", e.g. "Add:memberOf", to specify an operation. 1643 * 1644 * Values 'Set' to a multi-value field will be transformed to 'Add' by default. You can override this using the colon syntax above, which will always take priority. 1645 * 1646 * @param identity The identity to modify 1647 * @param defaultOperation The default operation to update with (Set, Add, Remove, etc) if one is not given 1648 * @param params The parameters to modify 1649 * @throws GeneralException if anything goes wrong 1650 */ 1651 public void updateUser(Identity identity, String defaultOperation, Map<String, Object> params) throws GeneralException { 1652 if (params == null || params.isEmpty()) { 1653 log.warn("Call made to updateAccount() with a null or empty map of updates"); 1654 return; 1655 } 1656 Objects.requireNonNull(identity, "Identity must not be null"); 1657 ProvisioningPlan plan = new ProvisioningPlan(); 1658 plan.setIdentity(identity); 1659 for(String key : params.keySet()) { 1660 String provisioningName = key; 1661 ProvisioningPlan.Operation operation = ProvisioningPlan.Operation.valueOf(defaultOperation); 1662 if (key.contains(":")) { 1663 String[] components = key.split(":"); 1664 operation = ProvisioningPlan.Operation.valueOf(components[0]); 1665 provisioningName = components[1]; 1666 } 1667 // For multi-valued attributes, transform set to add by default 1668 if (operation.equals(ProvisioningPlan.Operation.Set) && !key.contains(":")) { 1669 ObjectConfig identityConfig = Identity.getObjectConfig(); 1670 ObjectAttribute attributeDefinition = identityConfig.getObjectAttribute(provisioningName); 1671 if (attributeDefinition.isMultiValued()) { 1672 operation = ProvisioningPlan.Operation.Add; 1673 } 1674 } 1675 Object value = params.get(key); 1676 if (value instanceof Identity) { 1677 value = ((Identity) value).getName(); 1678 } 1679 plan.add(ProvisioningPlan.APP_IIQ, identity.getName(), provisioningName, operation, value); 1680 } 1681 Map<String, Object> extraParameters = new HashMap<>(); 1682 extraParameters.put(PLAN_PARAM_APPROVAL_SCHEME, NO_APPROVAL_SCHEME); 1683 doProvisioning(identity.getName(), plan, false, extraParameters); 1684 } 1685 1686 /** 1687 * Updates the given user with the given field values 1688 * @param identity The identity in question 1689 * @param field The field to set 1690 * @param operation The operation to use 1691 * @param value The value to update 1692 * @throws GeneralException if any provisioning failures occur 1693 */ 1694 public void updateUser(Identity identity, String field, ProvisioningPlan.Operation operation, Object value) throws GeneralException { 1695 ProvisioningPlan changes = new ProvisioningPlan(); 1696 if (value instanceof Identity) { 1697 value = ((Identity) value).getName(); 1698 } 1699 changes.add(ProvisioningPlan.APP_IIQ, identity.getName(), field, operation, value); 1700 changes.setIdentity(identity); 1701 Map<String, Object> extraParameters = new HashMap<>(); 1702 extraParameters.put(PLAN_PARAM_APPROVAL_SCHEME, NO_APPROVAL_SCHEME); 1703 doProvisioning(identity.getName(), changes, false, extraParameters); 1704 } 1705 1706}