Delete bundles from identity in exclusion rule

Which IIQ version are you inquiring about?

8.4

Share all details about your problem, including any error messages you may have received.

We user a certification event when a user is considered a mover, as the new manager needs to review the access its employee has.
We want to remove bundles when this happens, and leave only EntitlementGroups to certified, however, creating a provisioning plan in the exclusion rule to remove the roles, will end in a DB error: how can I solve this DB issue:
[Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): EntitlementGroups]
Has anyone had a similar ask in the past?

Hello @duhanalin If it’s possible for you to share the code?, we can assist you.

1 Like

Sure!

import org.apache.logging.log4j.Logger;
      import org.apache.logging.log4j.LogManager;
      import sailpoint.object.Attributes;
      import sailpoint.object.Bundle;
      import sailpoint.object.Certifiable;
      import sailpoint.object.EntitlementGroup;
      import sailpoint.object.Custom;
      import sailpoint.object.Identity;
      import sailpoint.object.Bundle;
      import sailpoint.object.Application;
      import sailpoint.object.Profile;
      import sailpoint.object.Filter;
      import sailpoint.object.ProvisioningPlan;
      import sailpoint.object.ProvisioningPlan.AccountRequest;
      import sailpoint.object.ProvisioningPlan.AttributeRequest;
      import sailpoint.object.ProvisioningProject;
      import sailpoint.api.Provisioner;
      import org.apache.commons.collections4.CollectionUtils;

      Logger log = LogManager.getLogger("mover.exclusion.rule");

      Set groupsToBeExcluded = new HashSet();
      List rolesToBeDeleted = new ArrayList();

      private void collectRoleGroups(Bundle bundle) {

        List profiles = bundle.getProfiles();
        for (Profile profile : profiles) {
          Application application = profile.getApplication();
          List constraints = profile.getConstraints();
          for (Filter constraint : constraints) {
            Filter.LeafFilter leafFilter = constraint;
            String attributeName = leafFilter.getProperty();
            Object valueObj = leafFilter.getValue();
            log.debug("In collectRoleGroups method, list of entitlements {}", valueObj);

            CollectionUtils.addAll(groupsToBeExcluded, valueObj);
          }
        }
      }

      List roles = entity.getAssignedRoles();

      if (CollectionUtils.isNotEmpty(roles)) {
        for (Bundle role : roles) {

          if ("rapidSetupBirthright".equalsIgnoreCase(role.getType())) {
            log.debug("Found birthright role {}", role.getName());
            itemsToExclude.add(role);
          } else {
            log.debug("Identity for which bundles will be removed: {}", entity.getName());
            itemsToExclude.add(role);

            collectRoleGroups(role);
            rolesToBeDeleted.add(role);
          }
        }

        Iterator it = items.iterator();
        
        log.debug("The list of groups to be excluded list {}", groupsToBeExcluded);

        while (it.hasNext()) {
          Certifiable certifiable = (Certifiable) it.next();

          if (certifiable instanceof EntitlementGroup) {
            EntitlementGroup entGroup = certifiable;
            
            log.debug("Iterating through entGroups, now at {} of type {}", entGroup.getDisplayName(), entGroup.getApplicationName());

            Attributes attributes = entGroup.getAttributes();
            List entList = attributes.getKeys();
            Iterator entIterator = entList.iterator();

            while (entIterator.hasNext()) {
              String attrName = (String) entIterator.next();
              List attrValue = attributes.getList(attrName);

              if (null != attrValue && !attrValue.isEmpty()) {
                for (String value : attrValue) {
                  log.debug("Found value {} with key {}", value, attrName);
                  if (groupsToBeExcluded.contains(value)) {
                    log.debug("Excluding EntitlementGroup {} of type {}", entGroup.getDisplayName(), entGroup.getApplicationName());
                    log.debug("Found value {}", value);
                    itemsToExclude.add(certifiable);
                  }
                }
              }
            }
          }
        }
      }

      items.removeAll(itemsToExclude);
  
  		log.debug("Items {}", items);
  
      for (EntitlementGroup item : items) {
        log.debug("Iterating through entGroups, now at {}", item.getDisplayName());
      }
		  log.debug("itemsToExclude {}", itemsToExclude);
  		log.debug("rolesToBeDeleted {}", rolesToBeDeleted);
  		log.debug("groupsToBeExcluded {}", groupsToBeExcluded);

      if (CollectionUtils.isNotEmpty(rolesToBeDeleted)) {
        for (Bundle role : rolesToBeDeleted) {
          ProvisioningPlan plan = new ProvisioningPlan();
          plan.setIdentity(entity);

          AccountRequest accountRequest = new AccountRequest();

          accountRequest.setNativeIdentity(entity.getName());
          accountRequest.setOperation(ProvisioningPlan.AccountRequest.Operation.Modify);
          accountRequest.setApplication(ProvisioningPlan.APP_IIQ);

          AttributeRequest attributeRequest = new AttributeRequest(ProvisioningPlan.ATT_IIQ_ASSIGNED_ROLES, ProvisioningPlan.Operation.Remove, role.getName());
          accountRequest.add(attributeRequest);

          plan.add(accountRequest);

          Provisioner provisioner = new Provisioner(context);
          provisioner.setAssigner("spadmin");
          ProvisioningProject project = provisioner.compile(plan);
          provisioner.execute(plan);

          log.debug("Excluded {} after removal for {}", role.getName(), entity.getName());
        }
      }
  
  		log.debug("Before existing rule");

      return "Current roles are removed automatically through mover certification, birthrights are excluded.";

some loggers were adding for debugging purposes. Please let me know if anything else is required.

once again, the error I’m getting is:
An unexpected error occurred: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [sailpoint.object.EntitlementGroup#

Hi @duhanalin , You’re right — this error:

“Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): EntitlementGroups”

is caused because of executing a provisioning plan inside the exclusion rule. The reason this fails is because the exclusion rule runs while SailPoint is still processing certification items within an active Hibernate session. Trying to modify the same identity (like removing roles) during this time can lead to session conflicts, since those objects are still being tracked and used.

Instead of provisioning directly in the exclusion rule, I’d suggest separating the two steps:

  1. Use the exclusion rule only to exclude the roles from certification (like removing Bundle items from the items list).
  2. Then handle the actual removal of roles via provisioning in a separate rule or flow — such as a post-certification rule, a workflow step, or an identity lifecycle event rule (for example, triggered by a move).

This should prevent the session conflict and allow both certification and provisioning to work as expected.

Hope this helps — let me know how it goes if you try it this way.

If I would want to use the exclusion rule, aren’t any recommended locking options at hand that I could use? Reason for asking is, I’d like to avoid creating another WF for this item, even if I understand it may be better to separate the processes.

I understand your intention to avoid adding another workflow.

As far as I know, there’s no supported locking mechanism available within the exclusion rule context. Even if we try to introduce a delay or flag, the session-level conflict would still occur, and there’s no safe way to lock or pause the transaction at the rule level.

I’m not sure, but can’t you lock the identity with:

import sailpoint.api.ObjectUtil;
import sailpoint.api.PersistenceManager;
identity=ObjectUtil.lockObject(context,Identity.class,idToLock,null,PersistenceManager.LOCK_TYPE_TRANSACTION);

Then commit the transaction to release the lock?

For some reason, after I used a lock and saved and committed the transaction, the rule ended without error, however, the role and the entitlements that it contains remained on the identity.
However, in the certification, all looks good, the role does not appear in the list of exclusions (as expected and wanted), and only the entitlements I wanted were included, however, I don’t fully understand why the role hasn’t been removed this time.

Did they remain on the identity after a refresh? I believe that if a refresh including roles hasn’t been done yet, you wouldn’t necessarily see the updated information.

Apologies for late reply, I refreshed the identity before attempting to trigger a certification, and after the mover certification event was triggered. The role still remained on the identity.

In the end, I decided to go with having a lifecycle event that removes the roles, instead of keeping that logic in the Exclusion rule.