There are various times when an IdentityIQ installation may need to programmatically request that a Role assignment be removed from an Identity. One example would be specific types of transfer or leaver workflows that must immediately remove certain role assignments. The best practice for how to do this has changed over the years. Significant changes were introduced with the advent of changes in the Role management APIs in the 6.3 and newer releases to support assigning a Role to an Identity multiple times. This all raises an important question: How should you remove (or de-assign) a Role from Identity via the API?
Let's start with what not to do. There are many public methods available on the sailpoint.object.Identity object that look like they would be useful for dealing with role assignment methods. These include:
While these may have worked in earlier releases of the IdentityIQ they are not forward compatible with current or future releases of the product. Please do not call these APIs directly from custom BeanShell code or workflow code. SailPoint reserves the right to remove these methods from the public API or deprecate them in a future release. Many implementations that call these methods fail to lock Identity objects before committing changes.
What method should be used? The best practice is that you should treat a Role addition or removal like an LifeCycleManager (LCM) request. This means conceptually a role addition has a requesting party, a target identity on which the role will be removed, and some other meta data. That means you should construct a provisioning plan requesting the removal of what role(s) you want removed from the virtual application named "IIQ". The "IIQ" application is an application that can be used to make changes to an Identity's assignments inside IdentityIQ itself.
There is a distinction that can be made when removing Role assignments from an Identity. One method would be to "Remove the Role/Bundle assignment, but leave the entitlements that came from the role on the user's accounts". Another method would be to "Remove the Role/Bundle assignment, and walk down the tree of user's accounts and send off provisioning events to remove all of the entitlements supplied by that Role from from the user's accounts". This conceptually a "shallow removal" versus a "deep removal". The former is quicker and happens in IdentityIQ's database and the latter may expand to include many applications and take time for provisioning events to process.
There are a couple of ways to approach the best practice in code. One is short-winded and good for simpler circumstances but provides less visibility in the UI for what change took place. The other is longer-winded and better for circumstances where you want to see more of a paper-trail in the user interface. The longer winded method creates formal IdentityRequest objects that can be seen in the user interface under the Manage -> Access Requests menu screen.
Let's look at the shorter example first. This example is useful if you want to programmatically remove a role without approvals, email notifications, or the record of an IdentityRequest being left in the system. The code block for this approach is fairly easy.
String identityName = “mary.johnson”;
ProvisioningPlan plan = new ProvisioningPlan();
plan.setIdentity(context.getObject(Identity.class, identityName));
plan.add(“IIQ”, identityName, “assignedRoles”, ProvisioningPlan.Operation.Remove, “Some Role Name”);
Provisioner p = new Provisioner(context);
// if you want to remove the assignment without de-provisioning the entitlements set this
p.setNoRoleExpansion(true);
p.execute(plan);
You could think of this as a lightweight implementation. Many installations require a more feature-rich implementation that can be seen as an IdentityRequest much later on, days after the request was submitted. Attached is a fully functional example tested in a lab system that creates a Provisioning Plan for the Role Removals, then uses the Request Processor to launch a Workflow in the background to remove the Role(s) from the target Identity. This example provides all of the features for a completely searchable, viewable, and audit-able paper trail of why a Role was removed from an Identity.
The attachment sets the "approvalMode" to "none", which like the example above, disables approvals for the removal. If you want to require approvals for removing access then you will want to change this feature of the attached example.
Thanks Adam. It's really confusing to see many API related to role addition/removal on the Identity object. When we try it out, it doesn't work.
Your article clearly explains everything. !
Hi,
Can you please advise if the workflow variable can take an Arraylist as an input.
We have a scenario where-in a ticketing application calls an IIQ API/plugin with a list of roles to add/remove. We then need to trigger a workflow to add/remove those set of roles coming as a workflow input.
Basically we need to replicate a manage access sort of functionality with the role data to be added\removed coming in from the ticketing system.
Any pointers on this ?
Regards,
Aditya
Pass the list of Role/Bundle names as String containing the CSV (comma delimited) list. Then inside your Workflow code use sailpoint.tools.util.toList() and toCsv() to switch between the two.
Thanks Adam, Will give it a shot.
Is there any detailed documentation from a workflow variable perspective ?
Regards,
Aditya
Hi Adam,
I tried the sample code that you provided in the xml , everything is working fine except that in the task I am getting an error mentioning the below line.
An unexpected error occurred: Missing required variable: identityName
I have added wfArgs.put("identityName",targetIdentityName); in the map and I am getting the error even after that. Is there any other place that this variable needs to be added apart from the Attributes.The other code that you have included in the document itself is working but I wanted a more auditable format . Any help would be appreciated.
Just to be more clear , this part is working , the attached xml file is giving me some issues.
Regards
Kaustav
Hi Adam,
It is working fine now.
Thanks
Adam,
The above code seems to remove the role from the identity, however what happens if the user have multiple accounts with the same role. Would it be possible via code to select the account?
Thanks.
Alex
Hello Alex,
I'm not sure how this is handled in recent IdentityIQ releases, it has been literally years since I've looked at this part of the product. Let me see if I can find someone who knows more to help.
Originally the concept of role assignment was to an Identity and not to an account. Eventually IdentityIQ was extended with the concept of specifying a "target account", i.e. naming which of multiple accounts correlated to an Identity in a managed Application will receive the entitlements from an assigned role. In 6.3 the product was extended to support the concept of multiple role assignments targeted at different accounts under an Identity; i.e. the concept that an Identity could have a role assigned multiple times, each time with a different target account in the context of the assignment.
Assuming Identities can have a role assigned multiple times in your version of IdentityIQ then I am sure there is an API to specify the account. The IdentityIQ UI supports target account selection for role assignment, but I don't know how the API wires this up to the back end calls. We will need someone with more experience with this feature in recent IdentityIQ editions to look at this.
jason.slavick, mike.klug, andy.dunfee - have any of ya'll come across specifying a target account for role removal?
Cheers,
--Adam
Alex,
You can achieve it by creating a provisioning plan and adding provisioning target. Basically, provisioning target contains the information related to account. The code will look something like this and you can modify it as per your requirement:
List provisioningTargets = new ArrayList();
//Set provisioning targets based on the role assignment.
ProvisioningTarget provisioningTarget = new ProvisioningTarget();
provisioningTarget.setAssignmentId(roleAssignment.getAssignmentId());
provisioningTarget.setRole(attributeRequest.getValue());
List roleTargets = roleAssignment.getTargets();
if(Util.size(roleTargets) > 0){
for(RoleTarget roleTarget : roleTargets){
log.trace("roleTarget: "+roleTarget.toXml());
AccountSelection accountSelection = new AccountSelection(context.getObjectById(Application.class, roleTarget.getApplicationId()));
accountSelection.setRoleName(roleTarget.getRoleName());
accountSelection.setSelection(roleTarget.getNativeIdentity());
AccountInfo accountInfo = new AccountInfo(roleTarget);
accountSelection.addAccountInfo(accountInfo);
provisioningTarget.addAccountSelection(accountSelection);
}
}
provisioningTargets.add(provisioningTarget);
if(Util.size(provisioningTargets) > 0){
plan.setProvisioningTargets(provisioningTargets);
}
Thanks,
Gaurav
Gaurav / Adam,
Thank you for the information. I have created two custom rules, 1. Add Role to Identity 2. Remove Role from Identity, both will pick an account. The test user have 2 application accounts with the same entitlements.
When I execute the Add Role to Identity Rule … The role does appear in the Identity Entitlement Tab but it doesn’t see the Account Name. However in the XML you can see the account is selected. In addition SailPoint thinks the user already got the role, but through front end access request there is no reference. I did perform IdentityRefresh but this make no difference, are example rule missing a step?
ProvisioningPlan XML for Add
<ProvisioningProject identity="TestUser">
<Filtered>
<AccountRequest application="WPG" nativeIdentity="TestUser" op="Modify" sourceRole="Test Role">
<AttributeRequest name="Role Name" op="Add">
<Attributes>
<Map>
<entry key="reason">
<value>
<FilterReason>Exists</FilterReason>
</value>
</entry>
</Map>
</Attributes>
<Value>
<List>
<String>Test Entitlement</String>
</List>
</Value>
</AttributeRequest>
</AccountRequest>
</Filtered>
<MasterPlan>
<ProvisioningPlan nativeIdentity="TestUser">
<AccountRequest application="IIQ" op="Modify" targetIntegration="IIQ">
<AttributeRequest name="assignedRoles" op="Add" value="Test Role"/>
</AccountRequest>
<ProvisioningTargets>
<ProvisioningTarget role="Test Role">
<AccountSelection applicationId="8a28edad5d5a344d015d5a35c69202bc" applicationName="TestApp" roleName="Test Role" selection="TestUser">
<AccountInfo nativeIdentity="TestUser"/>
</AccountSelection>
</ProvisioningTarget>
</ProvisioningTargets>
</ProvisioningPlan>
</MasterPlan>
<ProvisioningPlan targetIntegration="IIQ">
<AccountRequest application="IIQ" op="Modify">
<AttributeRequest name="assignedRoles" op="Add" value="Test Role"/>
</AccountRequest>
</ProvisioningPlan>
<ProvisioningTarget role="Test Role">
<AccountSelection applicationId="8a28edad5d5a344d015d5a35c69202bc" applicationName="TestApp" roleName="Test Role" selection="TestUser">
<AccountInfo nativeIdentity="TestUser"/>
</AccountSelection>
</ProvisioningTarget>
</ProvisioningProject>
As the add rule didn’t work, I raised access via front end before testing the remove rule. The remove rule did not see the selected account but removed role from identity for both accounts.
Add Rule Example
import sailpoint.object.*;
import sailpoint.api.Provisioner;
import sailpoint.object.Bundle;
import sailpoint.object.Link;
import sailpoint.object.AccountSelection;
import sailpoint.object.AccountInfo;
import sailpoint.object.RoleTarget;
String identityName = "TestUser";
String username = "TestUser";
String rolename = "Test Role";
Identity identity = context.getObjectByName(Identity.class, identityName);
Bundle role = context.getObjectByName(Bundle.class,rolename);
// Create new plan
ProvisioningPlan plan = new ProvisioningPlan();
plan.setIdentity(identity); // identity object
plan.setNativeIdentity(identity.getName()); // identity full name
// Create an AccountRequest for IIQ
ProvisioningPlan.AccountRequest acctReq = new ProvisioningPlan.AccountRequest();
acctReq.setTargetIntegration(ProvisioningPlan.APP_IIQ);
acctReq.setApplication(ProvisioningPlan.APP_IIQ);
acctReq.setOperation(ProvisioningPlan.AccountRequest.Operation.Modify);
// Create an Attribute Request for IIQ
ProvisioningPlan.AttributeRequest attrReq = new ProvisioningPlan.AttributeRequest();
attrReq.setName(ProvisioningPlan.ATT_IIQ_ASSIGNED_ROLES);
attrReq.setValue(role.getName());
attrReq.setOp(ProvisioningPlan.Operation.Add);
// Create Account Selector for Provisioning Target
List provisioningTargets = new ArrayList();
ProvisioningTarget pT = new ProvisioningTarget(role);
Set applicationSet = role.getApplications();
// Find the link
for(Application app : applicationSet) {
AccountSelection accountSelector = new AccountSelection(app);
List<Link> links = identity.getLinks(app);
for (Link link : links) {
if ((link.getApplicationName()).equals("TestApp")) {
String appusername = link.getAttribute("UserId"); // get the user name
// return the link of the application username
if (appusername.toUpperCase().equals(username)) {
foundlink = link;
}
}
}
RoleTarget roleTarget = new RoleTarget(foundlink);
AccountSelection.AccountInfo accountInfo = new AccountSelection.AccountInfo(roleTarget);
accountSelector.addAccountInfo(accountInfo);
accountSelector.setSelectedNativeIdentity(username);
accountSelector.setRoleName(role.getName());
accountSelector.setSelection(roleTarget.getNativeIdentity());
pT.addAccountSelection(accountSelector);
}
// add Provisioning Target to plan
provisioningTargets.add(pT);
plan.setProvisioningTargets(provisioningTargets);
// add Attribute Request to IIQ Account Request
acctReq.add(attrReq);
// add IIQ account request to plan
plan.add(acctReq);
// Compile the Plan into a new Project and return this new Project
Provisioner p = new Provisioner(context);
ProvisioningProject proj = p.compile(plan);
p.execute(proj);
Remove Rule Example
import sailpoint.object.*;
import sailpoint.api.Provisioner;
import sailpoint.object.Bundle;
import sailpoint.object.Link;
import sailpoint.object.AccountSelection;
import sailpoint.object.AccountInfo;
import sailpoint.object.RoleTarget;
String identityName = "TestUser";
String username = "TestUser";
String rolename = "Test Role";
Identity identity = context.getObjectByName(Identity.class, identityName);
Bundle role = context.getObjectByName(Bundle.class,rolename);
// Create new plan
ProvisioningPlan plan = new ProvisioningPlan();
plan.setIdentity(identity); // identity object
plan.setNativeIdentity(identity.getName()); // identity full name
// Create an AccountRequest for IIQ
ProvisioningPlan.AccountRequest acctReq = new ProvisioningPlan.AccountRequest();
acctReq.setTargetIntegration(ProvisioningPlan.APP_IIQ);
acctReq.setApplication(ProvisioningPlan.APP_IIQ);
acctReq.setOperation(ProvisioningPlan.AccountRequest.Operation.Modify);
// Create an Attribute Request for IIQ
ProvisioningPlan.AttributeRequest attrReq = new ProvisioningPlan.AttributeRequest();
attrReq.setName(ProvisioningPlan.ATT_IIQ_ASSIGNED_ROLES);
attrReq.setValue(role.getName());
attrReq.setOp(ProvisioningPlan.Operation.Remove);
Attributes args = new Attributes();
args.putClean("deassignEntitlements", new Boolean(true));
args.putClean("negativeAssignment", new Boolean(true));
attrReq.setArguments(args);
// Create Account Selector for Provisioning Target
List provisioningTargets = new ArrayList();
ProvisioningTarget pT = new ProvisioningTarget(role);
Set applicationSet = role.getApplications();
// Find the link
for(Application app : applicationSet) {
AccountSelection accountSelector = new AccountSelection(app);
List<Link> links = identity.getLinks(app);
for (Link link : links) {
if ((link.getApplicationName()).equals("TestApp")) {
String appusername = link.getAttribute("UserId");
// return the link of the application username
if (appusername.toUpperCase().equals(username)) {
foundlink = link;
}
}
}
RoleTarget roleTarget = new RoleTarget(foundlink);
AccountSelection.AccountInfo accountInfo = new AccountSelection.AccountInfo(roleTarget);
accountSelector.addAccountInfo(accountInfo);
accountSelector.setSelectedNativeIdentity(username);
accountSelector.setRoleName(role.getName());
accountSelector.setSelection(roleTarget.getNativeIdentity());
pT.addAccountSelection(accountSelector);
}
// add Provisioning Target to plan
provisioningTargets.add(pT);
plan.setProvisioningTargets(provisioningTargets);
// add Attribute Request to IIQ Account Request
acctReq.add(attrReq);
// add IIQ account request to plan
plan.add(acctReq);
// Compile the Plan into a new Project and return this new Project
Provisioner p = new Provisioner(context);
ProvisioningProject proj = p.compile(plan);
p.setNoRoleExpansion(true);
p.execute(proj);
Regards,
Alex