"Fixing Entitlement Reassignment Issue in SailPoint IIQ Identity Refresh"

Hello @Everyone
I am working on implementing the Checker and Maker entitlements in VMware within SailPoint IIQ. I have created two entitlements: Checker and Maker.

If an identity requests Checker, it gets assigned successfully. If the same identity later requests Maker, the Checker entitlement is removed, and vice versa. This functionality is working correctly.

However, when I run the Refresh Identity Cube with Enable Provisioning Assignment, the removed entitlement (Checker or Maker) is getting re-assigned.

I have already added steps in the Approve and Provision Subprocess workflow, but the issue still persists.

How can I modify the workflow or configuration in SailPoint IIQ to ensure that the removed entitlement does not get re-assigned after running the Refresh Identity Cube task?

@Ishiiiiita
Most probably your entitlements are marked as sticky within the identity causing the refresh to reprovision the entitlement again

please check this code to clean those

https://community.sailpoint.com/t5/IdentityIQ-Forum/Sticky-Attribute-Assignment-Issues-in-IIQ-8-1/m-p/190744

How to Implement that code

The issue occurs due to the entitlement’s sticky nature. entitlements assigned through access requests are typically sticky, causing them to reassign upon refresh if they’re removed accidentally. To permanently remove these sticky entitlements, you can implement one of the following approaches:

Approach 1: Update the Before Provisioning Rule When removing an entitlement assigned via access request, explicitly set the assignment flag to true in the Before Provisioning Rule.
example:

Attributes attrs = new Attributes();
attrs.put("assignment", true);

AttributeRequest attributeRequest = new AttributeRequest(attribute, ProvisioningPlan.Operation.Remove, entitlementValue);
attributeRequest.setArgs(attrs);
accountRequest.add(attributeRequest);

This ensures the removal is treated as permanent, preventing reassignment upon subsequent refreshes.

Approach 2: Using Rule runner task to remove (as suggested above link)

  • Go to SailPoint’s debug page.
  • Select object type TaskDefinition and select the action New .
  • Paste the TaskDefinition XML content and save.
  • Repeat this step for the associated Rule object.
  • Navigate to IIQ UI Tasks, search for, and open the task (Task-StickyEntRemoval).
  • Run the task to remove all existing AttributeAssignments.

Note: The Task-StickyEntRemoval method removes all AttributeAssignments, so ensure this aligns with your operational requirements.

Applying either of these methods will resolve your entitlement reassignment issue.

Most probably your entitlements are marked as sticky within the identity causing the refresh to reprovision the entitlement again.

Adding this statement while creating the new attribute request will make the removal as permanent

Attributes attrs = new Attributes();
attrs.put("assignment", true);

AttributeRequest attributeRequest = new AttributeRequest(attribute, ProvisioningPlan.Operation.Remove, entitlementValue);
attributeRequest.setArgs(attrs);

However I don’t see the creation of new attribute request in your code. I would suggest you run the sticky entitlement removal task before the refresh of Identity Cubes.

Task Definition XML:

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE TaskDefinition PUBLIC "sailpoint.dtd" "sailpoint.dtd">
<TaskDefinition name="Task-StickyEntRemoval" resultAction="Rename" subType="task_item_type_generic" type="Generic">
  <Attributes>
    <Map>
      <entry key="TaskDefinition.runLengthAverage"/>
      <entry key="TaskDefinition.runLengthTotal"/>
      <entry key="TaskDefinition.runs"/>
      <entry key="TaskSchedule.host"/>
      <entry key="deleteStickEnt" value="false"/>
      <entry key="ruleName" value="Rule-StickyEntRemoval"/>
      <entry key="taskCompletionEmailNotify" value="Disabled"/>
      <entry key="taskCompletionEmailRecipients"/>
      <entry key="taskCompletionEmailTemplate"/>
    </Map>
  </Attributes>
  <Description>A task is used to remove Sticky Entitlements and Disconnected Attribute Assignments from the Identities.</Description>
  <Owner>
    <Reference class="sailpoint.object.Identity" name="spadmin"/>
  </Owner>
  <Parent>
    <Reference class="sailpoint.object.TaskDefinition" name="Run Rule"/>
  </Parent>
  <Signature>
    <Inputs>
      <Argument helpKey="When this is checked, sticky entitlements will be deleted along with Attribute Assignments. Else task result will only show the stick entitlements present in OneAccess" name="deleteStickEnt" type="boolean">
        <Prompt>Delete the Sticky Entitlements</Prompt>
      </Argument>
      <Argument helpKey="When this is checked, the task will only run on the specific identity" name="identityValue" type="string">
        <Prompt>Identity Distinguished Name</Prompt>
      </Argument>
    </Inputs>
    <Returns>
      <Argument name="attrAssignRemovalCount" type="string">
        <Prompt>Total attributeAssignments removed</Prompt>
      </Argument>
      <Argument name="attrAssignIdList" type="string">
        <Prompt>Identities name with AttributeAssignment</Prompt>
      </Argument>
      <Argument name="idenEntRemovalCount" type="string">
        <Prompt>Total sticky entitlements removed</Prompt>
      </Argument>
      <Argument name="totalIE" type="int">
        <Prompt>Total number of sticky entitlements</Prompt>
      </Argument>
      <Argument name="idenEntList" type="string">
        <Prompt>Sticky entitlements</Prompt>
      </Argument>
    </Returns>
  </Signature>
</TaskDefinition>

Rule Object:

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE Rule PUBLIC "sailpoint.dtd" "sailpoint.dtd">
<Rule language="beanshell" name="Rule-StickyEntRemoval">
  <Source><![CDATA[
  import java.util.Iterator;
  import java.util.List;
  import sailpoint.api.ObjectUtil;
  import sailpoint.api.PersistenceManager.LockParameters;
  import sailpoint.api.Terminator;
  import sailpoint.object.AttributeAssignment;
  import sailpoint.object.Filter;
  import sailpoint.object.Identity;
  import sailpoint.object.IdentityEntitlement;
  import sailpoint.object.QueryOptions;
  import sailpoint.object.TaskResult;
  import sailpoint.tools.GeneralException;
  import sailpoint.tools.Message;
  import sailpoint.tools.Util;

  if(void==taskResult || null == taskResult || config == null){
    return ;
  }

  int attrAssignRemovalCount=0;
  int idenEntRemovalCount = 0;
  int totalIE=0;
  boolean isDelete = Util.otob(config.get("deleteStickEnt"));
  String identityValue = Util.otoa(config.get("identityValue"));
  List attrAssignIdList = new ArrayList();
  List idenEntList = new ArrayList();
  Iterator itr = null;
  Iterator aaIt = null;
  String RESULT = "Success";


  private void getAttrAssignmentsId(IdentityEntitlement idenEnt) throws GeneralException{
    Identity id = null;
    try{
      id = idenEnt.getIdentity();
      if(id!=null){
        List attributeAssignmentList = id.getAttributeAssignments();
        if(!Util.isEmpty(attributeAssignmentList)){
          aaIt = attributeAssignmentList.iterator();
          while(aaIt.hasNext()) {
            AttributeAssignment attAssign = aaIt.next();
            if(Util.isNotNullOrEmpty(idenEnt.getAssignmentId()) && Util.isNotNullOrEmpty(attAssign.getAssignmentId()) && attAssign.getAssignmentId().equalsIgnoreCase(idenEnt.getAssignmentId())){
              attrAssignIdList.add(id.getName());
            }
          }
        }
      }
    }
    catch (GeneralException genExp) {
      log.error("GeneralException in Rule-TaskRule-StickyEntRemoval :: getAttrAssignmentsId : "+genExp);
      throw new GeneralException(genExp);
    }
    finally{
      if(aaIt!=null){
        Util.flushIterator(aaIt);
      }
    }
  }

  private void removeAttrAssignment(IdentityEntitlement idenEnt) throws GeneralException{
    Identity id = null;
    try{
      id = ObjectUtil.lockIfNecessary(context,idenEnt.getIdentity().getName());
      if(id!=null){
        List attributeAssignmentList = id.getAttributeAssignments();
        if(!Util.isEmpty(attributeAssignmentList)){
          aaIt = attributeAssignmentList.iterator();
          while(aaIt.hasNext()) {
            AttributeAssignment attAssign = aaIt.next();
            if(Util.isNotNullOrEmpty(idenEnt.getAssignmentId()) && Util.isNotNullOrEmpty(attAssign.getAssignmentId()) && attAssign.getAssignmentId().equalsIgnoreCase(idenEnt.getAssignmentId())){
              aaIt.remove();
              attrAssignIdList.add(id.getName());
              attrAssignRemovalCount++;
            }
          } 
          context.saveObject(id);
        }
      }
    }
    catch (GeneralException genExp) {
      if(id!=null){
        ObjectUtil.unlockIdentity(context, id);
      }
      log.error("GeneralException in Rule-TaskRule-StickyEntRemoval :: removeAttrAssignment : "+genExp);
      throw new GeneralException(genExp);
    }
    finally{
      if(id!=null){
        ObjectUtil.unlockIdentity(context, id);
      }
      if(aaIt!=null){
        Util.flushIterator(aaIt);
      }
    }
  }
  //Rule starts here

  try {
    QueryOptions qo = new QueryOptions();
    qo.setCloneResults(true);
    qo.setDistinct(true);
    if(Util.isNotNullOrEmpty(identityValue)){
      qo.addFilter(Filter.ignoreCase(Filter.eq("nativeIdentity",identityValue)));
    }
    qo.addFilter(Filter.ignoreCase(Filter.eq("aggregationState","disconnected")));
    itr = context.search(IdentityEntitlement.class,qo);
    while(itr.hasNext()){
      IdentityEntitlement idenEnt = itr.next();
      if(isDelete){
        context.startTransaction();
        removeAttrAssignment(idenEnt);
        Terminator terminator = new Terminator(context);
        terminator.deleteObject(idenEnt);
        context.commitTransaction();
        idenEntRemovalCount++;
      }
      else{
        getAttrAssignmentsId(idenEnt);
      }
      if(totalIE<100){
        idenEntList.add(" [ "+idenEnt.getValue()+" ] ");
      }
      totalIE++;
    }
    if(!isDelete){
      taskResult.addMessage(new Message(Message.Type.Info,"No records were deleted", null));
    }
    
  } catch (GeneralException genExp) {
    context.rollbackTransaction();
    taskResult.addMessage(new Message(Message.Type.Error, "GeneralException occurred: "+genExp.getMessage(), null));
    RESULT = "Fail";
  } finally {
    try{
      if(itr!=null){
        Util.flushIterator(itr);
      }
      context.decache();
    }catch(GeneralException exp){
      log.error("GeneralException in Rule-TaskRule-StickyEntRemoval : "+exp);
      taskResult.addMessage(new Message(Message.Type.Error, "GeneralException occurred: "+exp.getMessage(), null));
      RESULT = "Fail";
    }
  }
  if(totalIE>100){
    taskResult.addMessage(new Message(Message.Type.Info,"Task Result will only display 100 entitlements", null));
  }
  taskResult.put("attrAssignRemovalCount",attrAssignRemovalCount);
  taskResult.put("attrAssignIdList",attrAssignIdList);
  taskResult.put("idenEntRemovalCount",idenEntRemovalCount);
  taskResult.put("totalIE",totalIE);
  taskResult.put("idenEntList",idenEntList);
  return RESULT;

  ]]></Source>
</Rule>

Let me know if this works.

Regards,
Balaji

3 Likes

Using approach 1 by putting rule in before provisioning rule it is still not working below the rule : import sailpoint.object.;
import sailpoint.api.
;
import java.util.*;

try {
if (plan == null) {
return;
}

for (ProvisioningPlan.AccountRequest accountRequest : plan.getAccountRequests()) {

    if (accountRequest.getOperation() == ProvisioningPlan.AccountRequest.Operation.Modify) {
        
        // Define attribute name and AD groups
        String attribute = "memberOf";  
        String checkerGroup = "CN=Checker,CN=Users,DC=IIQAD,DC=com";
        String makerGroup = "CN=Maker,CN=Users,DC=IIQAD,DC=com";

        // Fetch existing entitlements from Identity
        Identity identity = context.getObject(Identity.class, accountRequest.getNativeIdentity());
        List<String> assignedGroups = identity.getEntitlementStrings();

        System.out.println("Assigned Groups: " + assignedGroups);

        // Create attributes object with assignment flag
        Attributes attrs = new Attributes();
        attrs.put("assignment", true);

        // If user is requesting "Maker", remove "Checker"
        if (accountRequest.getAttributeRequest(attribute, ProvisioningPlan.Operation.Add) != null
                && assignedGroups.contains(checkerGroup)) {

            System.out.println("Removing Checker as Maker is being assigned");

            // Use your provided logic
            AttributeRequest removeChecker = new AttributeRequest(attribute, ProvisioningPlan.Operation.Remove, checkerGroup);
            removeChecker.setArgs(attrs);
            accountRequest.add(removeChecker);
        }

        // If user is requesting "Checker", remove "Maker"
        if (accountRequest.getAttributeRequest(attribute, ProvisioningPlan.Operation.Add) != null
                && assignedGroups.contains(makerGroup)) {

            System.out.println("Removing Maker as Checker is being assigned");

            // Use your provided logic
            AttributeRequest removeMaker = new AttributeRequest(attribute, ProvisioningPlan.Operation.Remove, makerGroup);
            removeMaker.setArgs(attrs);
            accountRequest.add(removeMaker);
        }
    }
}

} catch (Exception e) {
System.out.println("Error in Before Provisioning Rule: " + e.getMessage());
e.printStackTrace();
}

i create the task definition in debug page and rule also in debug but its not working

Hello @Ishiiiiita, Could you please explain how the Checker entitlement is removed after the request is made? If I understand this, I will be able to assist you further. Could you provide some clarity?

Thank you for the attrs.put("assignment", true);
That was what I needed!

This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.