Reassignment in Targeted Certification using certifier rule

I have a usecase for a targeted certification, the certification should be assigned to the entitlement owners(which are there in extented atrribute owner1, owner2 and owner3), depending on the entitlement.

I have written rule for that which you can find below, but for a single user if he has 2 entitlements with different set of owners, then all the 6 owners(3 owners from 1 entitlement,3 owners from 2nd entitlement) are receiving the same certification.

Can anyone guide me here, what needs to be changed

import sailpoint.api.SailPointContext;
  import sailpoint.object.*;
  import sailpoint.tools.GeneralException;
  import sailpoint.object.Certifiable;
  import sailpoint.tools.Filter;
  import sailpoint.tools.QueryOptions;
  import java.util.*;
  import org.apache.log4j.Logger;
  import org.apache.log4j.Level;
  import sailpoint.object.QueryOptions;
  import sailpoint.object.Filter;
  import sailpoint.object.ManagedAttribute;
 
  import java.util.*;
 
  Logger logger = Logger.getLogger("Rule.Certification.EntitlementOwnerCertifier.Global");
  logger.setLevel(Level.DEBUG);
 
  List <String> certifiers = new ArrayList <String>();
   try {
    List <CertificationItem> items = entity.getItems();
 
   if (items != null) {
     // Object o = items;
      //for (Object o : items) {
               //if (items instanceof CertificationItem) {
           CertificationItem item = (CertificationItem) items.get(0);
        String exceptionApp = item.getExceptionApplication();
 
        String exceptionValue = item.getExceptionAttributeValue();
 
        String exceptionValueType = item.getExceptionAttributeName();
 
        QueryOptions qo = new QueryOptions();
        qo.addFilter(Filter.eq("application.name", exceptionApp));
        qo.addFilter(Filter.eq("attribute", exceptionValueType));
        qo.addFilter(Filter.eq("value", exceptionValue));
 
 
        Iterator it = context.search(ManagedAttribute.class, qo);
 
        while (it.hasNext()) {
          ManagedAttribute ma = (ManagedAttribute) it.next(); 
                   // ManagedAttribute ma = (ManagedAttribute)it;
          //Identity owner1 = ma.getOwner1();
 
          String owner1Email = ma.getAttribute("owner1");
 
          String owner2Email = ma.getAttribute("owner2");
 
          String owner3Email = ma.getAttribute("owner3");
 
          // Lookup Identity for owner1 by email
 
          if (owner1Email != null) {
 
            Identity owner1 = context.getObject(Identity.class,owner1Email);
 
            if (owner1 != null) {
              certifiers.add(owner1Email);
            }
          }
          // Lookup Identity for owner2 by email
          if (owner2Email != null) {
            Identity owner2 = context.getObject(Identity.class,owner2Email);
            if (owner2 != null && !certifiers.contains(owner2)) {
              certifiers.add(owner2Email);
            }
          }
          // Lookup Identity for owner3 by email
          if (owner3Email != null) {
            Identity owner3 = context.getObject(Identity.class,owner3Email);
            if (owner3 != null && !certifiers.contains(owner3)) {
              certifiers.add(owner3Email);
            }
          }
                 // return certifiers;
        }
 
      }
 
   // }
  // }
  }catch (Exception e) {
    logger.error("Error in Certifier Rule: " + e.getMessage());
  }
  // Return list of certifiers
  return certifiers;

Hi @smritgupta , Here’s how to modify your rule to correctly assign certifiers based on each individual entitlement, ensuring that only the owners of a specific entitlement are assigned to certify that entitlement:

import sailpoint.api.SailPointContext;
import sailpoint.object.*;
import sailpoint.tools.GeneralException;
import sailpoint.object.Certifiable;
import sailpoint.tools.Filter;
import sailpoint.tools.QueryOptions;
import java.util.*;
import org.apache.log4j.Logger;
import org.apache.log4j.Level;
import sailpoint.object.QueryOptions;
import sailpoint.object.Filter;
import sailpoint.object.ManagedAttribute;

Logger logger = Logger.getLogger("Rule.Certification.EntitlementOwnerCertifier.Global");
logger.setLevel(Level.DEBUG); // Set to DEBUG for better troubleshooting

// The 'certifiers' list should contain unique Identity names (not email)
// It's better to return a List<String> of Identity names (IDs) for certifiers.
// SailPoint usually expects the Identity name or ID as the certifier.
List <String> uniqueCertifierNames = new ArrayList <String>();
Set <String> processedCertifierEmails = new HashSet <String>(); // To prevent duplicate email lookups

try {
    // The 'entity' object in a CertificationEntityRule represents the CertificationEntity (e.g., for an Identity).
    // The 'entity.getItems()' method returns a list of CertificationItem objects associated with this entity.
    // Each CertificationItem represents a specific entitlement being certified for the identity.
    List <CertificationItem> items = entity.getItems();

    if (items != null && !items.isEmpty()) {
        logger.debug("Processing " + items.size() + " certification item(s) for identity: " + entity.getIdentity().getName());

        // Iterate through EACH CertificationItem to get its specific owners
        for (CertificationItem item : items) {
            // Check if the item is an entitlement (not a role, policy violation, etc.)
            // You might need to adjust this condition based on how your entitlements are represented.
            // For entitlements, typically item.getType() will be "Entitlement" or similar.
            if (item.isEntitlement()) { // A more robust way to check if it's an entitlement
                String exceptionApp = item.getExceptionApplication(); // The application name
                String exceptionValue = item.getExceptionAttributeValue(); // The entitlement value (e.g., group name)
                String exceptionValueType = item.getExceptionAttributeName(); // The entitlement attribute name (e.g., memberOf)

                logger.debug("Processing entitlement: App=" + exceptionApp + ", Attribute=" + exceptionValueType + ", Value=" + exceptionValue);

                QueryOptions qo = new QueryOptions();
                qo.addFilter(Filter.eq("application.name", exceptionApp));
                qo.addFilter(Filter.eq("attribute", exceptionValueType));
                qo.addFilter(Filter.eq("value", exceptionValue));

                Iterator it = context.search(ManagedAttribute.class, qo);

                while (it.hasNext()) {
                    ManagedAttribute ma = (ManagedAttribute) it.next();
                    logger.debug("Found ManagedAttribute: " + ma.getName());

                    String owner1Email = ma.getAttribute("owner1");
                    String owner2Email = ma.getAttribute("owner2");
                    String owner3Email = ma.getAttribute("owner3");

                    // Process owner1
                    if (owner1Email != null && !owner1Email.isEmpty()) {
                        if (!processedCertifierEmails.contains(owner1Email)) {
                            Identity owner1 = context.getObject(Identity.class, owner1Email);
                            if (owner1 != null) {
                                uniqueCertifierNames.add(owner1.getName()); // Add Identity Name, not email
                                processedCertifierEmails.add(owner1Email);
                                logger.debug("Added certifier: " + owner1.getName() + " (from owner1)");
                            } else {
                                logger.warn("Owner1 Identity not found for email: " + owner1Email);
                            }
                        } else {
                            logger.debug("Owner1 email already processed: " + owner1Email);
                        }
                    }

                    // Process owner2
                    if (owner2Email != null && !owner2Email.isEmpty()) {
                        if (!processedCertifierEmails.contains(owner2Email)) {
                            Identity owner2 = context.getObject(Identity.class, owner2Email);
                            if (owner2 != null) {
                                uniqueCertifierNames.add(owner2.getName());
                                processedCertifierEmails.add(owner2Email);
                                logger.debug("Added certifier: " + owner2.getName() + " (from owner2)");
                            } else {
                                logger.warn("Owner2 Identity not found for email: " + owner2Email);
                            }
                        } else {
                            logger.debug("Owner2 email already processed: " + owner2Email);
                        }
                    }

                    // Process owner3
                    if (owner3Email != null && !owner3Email.isEmpty()) {
                        if (!processedCertifierEmails.contains(owner3Email)) {
                            Identity owner3 = context.getObject(Identity.class, owner3Email);
                            if (owner3 != null) {
                                uniqueCertifierNames.add(owner3.getName());
                                processedCertifierEmails.add(owner3Email);
                                logger.debug("Added certifier: " + owner3.getName() + " (from owner3)");
                            } else {
                                logger.warn("Owner3 Identity not found for email: " + owner3Email);
                            }
                        } else {
                            logger.debug("Owner3 email already processed: " + owner3Email);
                        }
                    }
                }
            } else {
                logger.debug("Skipping non-entitlement item type: " + item.getType());
            }
        }
    } else {
        logger.debug("No certification items found for this entity.");
    }
} catch (Exception e) {
    logger.error("Error in Certifier Rule: " + e.getMessage(), e); // Log the stack trace for better debugging
}

// Return list of certifiers (unique Identity names)
return uniqueCertifierNames;

Hi @pattabhi

Thanks for replying , i tried this code, i got the following error

Exception: 
java.security.PrivilegedActionException: null
	at java.security.AccessController.doPrivileged(AccessController.java:573) ~[?:?]
	at org.apache.bsf.BSFManager.eval(BSFManager.java:442) [bsf.jar:?]
	at sailpoint.server.BSFRuleRunner.eval(BSFRuleRunner.java:249) [identityiq.jar:8.4p1 Build e243e6f4783-20240325-035201]
	at sailpoint.server.BSFRuleRunner.runRule(BSFRuleRunner.java:218) [identityiq.jar:8.4p1 Build e243e6f4783-20240325-035201]
	at sailpoint.server.InternalContext.runRule(InternalContext.java:1322) [identityiq.jar:8.4p1 Build e243e6f4783-20240325-035201]
	at sailpoint.server.InternalContext.runRule(InternalContext.java:1294) [identityiq.jar:8.4p1 Build e243e6f4783-20240325-035201]
	at sailpoint.certification.EntityBuilder.assignOwnershipWithRule(EntityBuilder.java:934) [identityiq.jar:8.4p1 Build e243e6f4783-20240325-035201]
	at sailpoint.certification.EntityBuilder.assignOwnership(EntityBuilder.java:833) [identityiq.jar:8.4p1 Build e243e6f4783-20240325-035201]
	at sailpoint.certification.EntityBuilder.buildEntity(EntityBuilder.java:680) [identityiq.jar:8.4p1 Build e243e6f4783-20240325-035201]
	at sailpoint.certification.EntityBuilder.buildEntities(EntityBuilder.java:620) [identityiq.jar:8.4p1 Build e243e6f4783-20240325-035201]
	at sailpoint.certification.PartitionHandler.execute(PartitionHandler.java:162) [identityiq.jar:8.4p1 Build e243e6f4783-20240325-035201]
	at sailpoint.request.CertificationBuilderExecutor.execute(CertificationBuilderExecutor.java:81) [identityiq.jar:8.4p1 Build e243e6f4783-20240325-035201]
	at sailpoint.request.RequestHandler.run(RequestHandler.java:163) [identityiq.jar:8.4p1 Build e243e6f4783-20240325-035201]
Caused by: org.apache.bsf.BSFException: BeanShell script error: bsh.EvalError: Sourced file: inline evaluation of: ``import sailpoint.api.SailPointContext;   import sailpoint.object.*;   import sai . . . '' : Error in method invocation: Method isEntitlement() not found in class'sailpoint.object.CertificationItem' : at Line: 39 : in file: inline evaluation of: ``import sailpoint.api.SailPointContext;   import sailpoint.object.*;   import sai . . . '' : item .isEntitlement ( ) 
BSF info: Rule - Certification - EntitlementOwnerCertifierRule at line: 0 column: columnNo
	at bsh.util.BeanShellBSFEngine.eval(BeanShellBSFEngine.java:202) ~[bsh-2.1.8.jar:2.1.8 2018-10-02 08:36:04]
	at org.apache.bsf.BSFManager$5.run(BSFManager.java:445) ~[bsf.jar:?]
	at java.security.AccessController.doPrivileged(AccessController.java:569) ~[?:?]
	... 12 more

Also, this how the certification entity looks like

<!DOCTYPE CertificationEntity PUBLIC "sailpoint.dtd" "sailpoint.dtd">
<CertificationEntity firstname="Bar" identity="13" lastname="Ha" snapshotId="XXXXXX" summaryStatus="Open" targetDisplayName="XXXX" targetId="XXXX" targetName="XXX" type="Identity">
<CertificationItem exceptionApplication="appname" exceptionAttributeName="Roles" exceptionAttributeValue="XXXX" summaryStatus="Open" type="Exception">
<ApplicationNames>
<String>appname</String>
</ApplicationNames>
<ExceptionEntitlements>
<EntitlementSnapshot application="appname" displayName="XXXX" nativeIdentity="XXXXX">
<Attributes>
<Map>
<entry key="Roles" value="XXXXX"/>
</Map>
</Attributes>
</EntitlementSnapshot>
</ExceptionEntitlements>
</CertificationItem>
<CertificationItem exceptionApplication="appname" exceptionAttributeName="Roles" exceptionAttributeValue="yyyyyyy" summaryStatus="Open" type="Exception">
<ApplicationNames>
<String>appname</String>
</ApplicationNames>
<ExceptionEntitlements>
<EntitlementSnapshot application="appname" displayName="xxxx" nativeIdentity="xxxxx">
<Attributes>
<Map>
<entry key="Roles" value="yyyyyyy"/>
</Map>
</Attributes>
</EntitlementSnapshot>
</ExceptionEntitlements>
</CertificationItem>
</CertificationEntity>

I tried this commenting this isentitlement also, but then also both the entitlements are going to 6 owners now

hi @smritgupta and @pattabhi

Thanks for sharing the rule and the error details. I had a look, and the root cause of the issue seems to be this line:

if (item.isEntitlement())

The method isEntitlement() doesn’t exist on the CertificationItem class in IdentityIQ 8.4p1, which is why you’re seeing the BSFException and EvalError.

Looking at the XML structure of your CertificationEntity, each CertificationItem has type="Exception", and the entitlement details are nested under <ExceptionEntitlements>. So instead of checking for "Entitlement" type, you should be checking for "Exception" and then validating that the item actually contains entitlement-related data.

I would try this

Replace:

if (item.isEntitlement())

With:


if ("Exception".equals(item.getType()) &&
    item.getExceptionApplication() != null &&
    item.getExceptionAttributeName() != null &&
    item.getExceptionAttributeValue() != null) {

I tried this commenting this isentitlement also, but then also both the entitlements are going to 6 owners now

hi @smritgupta

Thanks for the update! If both entitlements are now being assigned to six owners , it sounds like the rule is not properly filtering the owners per entitlement — instead, it’s likely accumulating all owners across all items and returning them as a single list.

Double-check the ManagedAttribute query: Make sure the QueryOptions are specific enough to return only the exact entitlement you’re processing.


qo.addFilter(Filter.eq("application.name", exceptionApp));

qo.addFilter(Filter.eq("attribute", exceptionValueType));

qo.addFilter(Filter.eq("value", exceptionValue));

Hi @haideralishaik

This line "for (CertificationItem item : items) " will force for all entity items to be checked right, can you please suggest something else

Also, if you see the entity xml, the type is identity , can there be some issue because of this

hi @smritgupta

  1. Yes, exactly. That loop goes through every item in the certification entity. If your goal is to only process one specific entitlement (e.g., the first one, or one matching a certain condition), you’ll need to add a filter or break early.

If you only want to process the first matching entitlement, you can do something like:

for (CertificationItem item : items) {
    if ("Exception".equals(item.getType()) &&
        item.getExceptionApplication() != null &&
        item.getExceptionAttributeName() != null &&
        item.getExceptionAttributeValue() != null) {

        // process the item
        break; // stop after the first match
    }
}
  1. Does type="Identity" in the entity XML cause issues?

No, that’s expected. The CertificationEntity type being "Identity" just means this is an Identity Certification — i.e., you’re certifying access for a person. That’s totally fine and doesn’t conflict with processing entitlement exceptions inside it.

The key is that each CertificationItem inside the entity can still represent an entitlement (even if the entity itself is for an identity).

Hi @haideralishaik ,

The goal is to sent the certification to the respective owners(which are present in extended attribute owner1, owner2 and owner3 of entitlement) of each entitlement.

There is no specific entitlement, the code needs to iterate, find the owner for each entitlement and assign them the items for certification(for which they are owner).

Can you please what we can modify in this code to achieve this.

Also, can this be achieved from targeted certification

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