Generate Email Alert for Requestable Groups Without Owners in IIQ

Use Case

Consider that your customer is using IdentityIQ for their IAM solution. The administrators face a challenge of checking the groups from the Active Directory application to see if group owners are missing when they are aggregated or refreshed. Or perhaps someone manually removes those values from the Entitlement Catalog by mistake. Group owners hold significant importance as the approvers if the groups are available for request. If a request goes through and the group is assigned to the identity without any approval, this may lead to wrong access to the wrong person and poses a risk to the organization. So, it is a good and recommended practice to have the owner assigned to groups if they are requestable. If those values are missing, then the administrators will ideally fix those groups as soon as possible by adding owners. For this to be an efficient process, administrators need to be notified of the missing group owners because it is very difficult to check thousands of groups in IdentityIQ.

Proposed Solution

The solution is a preventative measure to alert an admin that there are requestable groups without owners. It does not fix the issue, but rather the administrators will need to manually add the group owners to prevent the group getting provisioned to more identities without the proper approval process. To achieve this, you can schedule a task in which you configure a rule that contains the logic to iterate through the groups to see if they are requestable. If requestable groups without an owner are found, then send an email alert to the administrators to take action by adding owners to those groups.

Note: Additional important conditions to add:

  1. Since it is an AD application, there might be groups from many containers (OUs). The client has requested that you don’t need to check groups that are coming from certain OUs due to their business requirements.

  2. The client has also requested to skip approval for certain groups from the AD application because they are distribution lists (DL) and general groups for which the approval process isn’t required. (The approval process and group owners can be set according to their needs later.) So, you need to skip those groups as well.

Implementation Steps

  1. Create two custom objects for these additional conditions. The first one is for maintaining the list of OUs from which the groups should be excluded as you iterate. The second custom object is for maintaining the list of groups that do not require approval.

Custom: AD-Excluded-Groups.xml (This custom object holds the list of excluded OUs.)

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE Custom PUBLIC "sailpoint.dtd" "sailpoint.dtd">
<Custom name="AD-Excluded-Groups">
  <Attributes>
    <Map>
      <entry key="Excluded_OUs">
        <value>
          <List>
            <String>OU=Builtin</String>
            <String>OU=QueryBased,OU=Distribution</String>
            <String>OU=QueryBased,OU=Security</String>
            <String>OU=Automated</String>
          </List>
        </value>
      </entry>
    </Map>
  </Attributes>
</Custom>

Custom: AD-Approval-Excluded-Groups.xml (This custom object holds the list of groups that do not require approval.)

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE Custom PUBLIC "sailpoint.dtd" "sailpoint.dtd">
<Custom name="AD-Approval-Excluded-Groups">
  <Attributes>
    <Map>
      <entry key="Excluded_Groups">
        <value>
          <List>
            <String>CN=server-test123,CN=Users,DC=corp,DC=kbp,DC=com</String>
          </List>
        </value>
      </entry>
    </Map>
  </Attributes>
</Custom>

  1. Create an email template for generating the alerts sent to the administrators. In this email template, you can include whatever text message you want for the administrators as well as format the style and font to match the organization’s logo or brand, etc. (I have just added plain text for the body and the subject line. You can edit or update the email template if you need a more cosmetic feel.)

EmailTemplate: ADGroups-Approval-Unchecked-Alert.xml

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE EmailTemplate PUBLIC "sailpoint.dtd" "sailpoint.dtd">
<EmailTemplate name="ADGroups-Approval-Unchecked-Alert">
  <Body>
    &lt;html>
&lt;head>
&lt;style type="html/css">body { font-family: Calibri; font-size: 11; }&lt;/style>
&lt;/head>
&lt;body>
&lt;p align="center">&lt;strong>Do Not Reply&lt;/strong>: Automated informational message from identity.kbp.com /p>
&lt;p>Hello,&lt;/p>
&lt;p>Please note that the Manager or Owner approval flags are not enabled for the group below. Please investigate. &lt;/p>
&lt;p>Group: &lt;strong>$!groupName&lt;/strong>&lt;/p>
&lt;p>Thank You,&lt;/p>
&lt;p>Identity &amp; Access Management Operations&lt;/p>
&lt;/body>
&lt;/html>
  </Body>
  <Description>
    The email template is for notifying the team to check the AD groups with missing Manager and Owner approval flags.
  </Description>
  <Signature>
    <Inputs>
      <Argument name="groupName" type="string">
        <Description>The group name.</Description>
      </Argument>
    </Inputs>
  </Signature>
  <Subject>Action Required: Approvals missing for [$!groupName]</Subject>
</EmailTemplate>

  1. Create a rule that checks the groups. Basically, this rule will iterate through all the AD groups except the groups that belong to those two custom objects that were created. It checks if the groups are requestable or not. If yes, then it will check the owner values. If a group does not have an owner, then it will send an email alert with whatever content you have defined in the email template for taking the action on the group.

Rule: ADGroups-Unchecked-Alert.xml

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE Rule PUBLIC "sailpoint.dtd" "sailpoint.dtd">
<Rule language="beanshell" name="ADGroups-Unchecked-Alert">
  <Signature returnType="String">
    <Inputs>
      <Argument name="log">
        <Description>
          The log object associated with the SailPointContext.
        </Description>
      </Argument>
      <Argument name="context">
        <Description>
          A sailpoint.api.SailPointContext object that can be used to query the database if necessary.
        </Description>
      </Argument>
  </Inputs>
  </Signature>
  <Source>
import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Map;

  import sailpoint.api.SailPointContext;
  import sailpoint.object.Application;
  import sailpoint.object.Attributes;
  import sailpoint.object.Custom;
  import sailpoint.object.EmailOptions;
  import sailpoint.object.EmailTemplate;
  import sailpoint.object.Filter;
  import sailpoint.object.ManagedAttribute;
  import sailpoint.object.QueryOptions;

  import org.apache.log4j.Logger;
  import org.apache.log4j.Level;

  boolean isExcludedGroup(String entitlementValue, Logger log) {
    log.error("Starting isExcludedGroup method");
    boolean isExcluded = false;
    boolean isExcludedOuGroup = false;
    boolean isExcludedGroup = false;

    Custom customData = context.getObject(Custom.class, "AD-Excluded-Groups");
    Custom customSpecicGrps = context.getObject(Custom.class, "AD-Approval-Excluded-Groups");
    HashMap excludedMap = null;
    HashMap excludedGroupsMap = null;
    if (customData != null &amp;&amp; customSpecicGrps != null) {
      excludedMap = customData.getAttributes().getMap();
      excludedGroupsMap = customSpecicGrps.getAttributes().getMap();
      List excludedList = null;
      List excludedGroupsList = null;
      if (null != excludedMap &amp;&amp; null != excludedGroupsMap) {
        excludedList = excludedMap.get("Excluded_OUs");
        excludedGroupsList = excludedGroupsMap.get("Excluded_Groups");
        
        if (null != excludedList) {
          for (String listValue : excludedList) {
            if (entitlementValue.contains(listValue)) {
              isExcludedOuGroup = true;
              log.error(entitlementValue + " is excluded and non-requestable");
              break;
            }
          }
        } else {
          log.error("AD-Excluded-Groups has no List of Excluded OUs");
        }

        if (null != excludedGroupsList) {
          for (String listGroupValue : excludedGroupsList) {
            if (entitlementValue.equals(listGroupValue)) {
              isExcludedGroup = true;
              log.error(entitlementValue + " is excluded because of special group and non-requestable");
              break;
            }
          }
        } else {
          log.error("AD-Approval-Excluded-Groups has no List of Excluded groups");
        }
        
        if(isExcludedOuGroup || isExcludedGroup)
        {
          isExcluded = true;
        }
        else {
          log.error("In either or both custom objects the group is not present");
        }

      } else {
        log.error("Both custom objects have no Map of excluded OUs groups");
      }

    }
    log.error("Ending isExcludedGroup method");
    return isExcluded;
  }

  try {

    List listOfMAs = new ArrayList();
    String emailDest = "[email protected]";
    String emailTemplateName = "ADGroups-Approval-Unchecked-Alert";
    EmailTemplate emailTemplate = context.getObjectByName(EmailTemplate.class, emailTemplateName);



    Application appName = context.getObjectByName(Application.class, "Active Directory");
    QueryOptions qo = new QueryOptions();
    qo.setCloneResults(true);
    qo.addFilter(Filter.eq("application.name", appName.getName()));
    Iterator it = context.search(ManagedAttribute.class,qo);
    while ( (it!= null) &amp;&amp; (it.hasNext()) ) {
      ManagedAttribute ma = (ManagedAttribute) it.next();
      String entitlementValue = ma.getValue();
      log.error("For the group: "+entitlementValue);
      if(null != entitlementValue &amp;&amp; ma.isRequestable())
      {
        boolean found = isExcludedGroup( entitlementValue,  log);
        if(!found)
        {
          Attributes attributesMA = ma.getAttributes();
          if(attributesMA!=null)
          {
            boolean mangerAttribute = attributesMA.getBoolean("managerApprovalRequired"); // these are our custom attributes, so you might not seen your environment. You can do whatever you want with your attributes.
            boolean ownerAttribute = attributesMA.getBoolean("ownerApprovalRequired");
            if((!mangerAttribute) || (!ownerAttribute))
            {
              listOfMAs.add(ma.getDisplayName());
            }
          }
        }
      }
    }
    if(listOfMAs!=null)
    {
      Iterator iterator =  listOfMAs.iterator();
      while(iterator.hasNext()) {
        Map arguments = new HashMap();
        String groupName = iterator.next();
        arguments.put("groupName", groupName);
        EmailOptions emailOptions = new EmailOptions(emailDest, arguments);
        context.sendEmailNotification(emailTemplate, emailOptions);
      }
    }
  }
  catch(Exception e)
  {
    System.err.println("Exception occured at: " + e);
  }
  </Source>
  </Rule>

  1. Create the run rule task and add the above rule. This task takes care of running the rule.

TaskDefinition: ADGroups-Unchecked-Alert.xml

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE TaskDefinition PUBLIC "sailpoint.dtd" "sailpoint.dtd">
<TaskDefinition name="ADGroups-Unchecked-Alert" resultAction="Delete" subType="task_item_type_generic" type="Generic">
  <Attributes>
    <Map>
      <entry key="TaskDefinition.runLengthAverage" value="4"/>
      <entry key="TaskDefinition.runLengthTotal" value="4"/>
      <entry key="TaskDefinition.runs" value="1"/>
      <entry key="TaskSchedule.host"/>
      <entry key="ruleName" value="ADGroups-Unchecked-Alert"/>
      <entry key="taskCompletionEmailNotify" value="Disabled"/>
      <entry key="taskCompletionEmailRecipients"/>
      <entry key="taskCompletionEmailTemplate"/>
    </Map>
  </Attributes>
  <Description>A task that can be used to run an arbitrary rule.</Description>
  <Owner>
    <Reference class="sailpoint.object.Identity" name="spadmin"/>
  </Owner>
  <Parent>
    <Reference class="sailpoint.object.TaskDefinition" name="Run Rule"/>
  </Parent>
</TaskDefinition>

Now, keep all the above artifacts ready to import into IIQ. You can import those files using the import feature available in the UI, or you can also import using the iiq console.

Import the other files as well:

  1. Create a task schedule to run the rule once or twice weekly according to your requirement. The administrators will receive the alerts and will not have to do the tedious work of checking all the groups.

Click on Schedule, then scheduler has been created for the task.

If you are not familiar with scheduling tasks, please import the below artifact and check in the task schedule. (This is an alternate method for step 5.)

Create and import below file:

TaskSchedule: ADGroups-Unchecked-Alert-Schedule.xml

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE TaskSchedule PUBLIC "sailpoint.dtd" "sailpoint.dtd">
<TaskSchedule id="ADGroups-Unchecked-Alert-Schedule" name="ADGroups-Unchecked-Alert-Schedule">
  <Arguments>
    <Map>
      <entry key="TaskSchedule.host"/>
      <entry key="executor" value="ADGroups-Unchecked-Alert"/>
      <entry key="launcher" value="1234"/>
      <entry key="nextActualFireTime" value="1726482600120"/>
    </Map>
  </Arguments>
  <CronExpressions>
    <String>0 30 10 ? * 2 </String>
  </CronExpressions>
</TaskSchedule>

You can change the frequency of the task executor using epoch or cron expressions (https://www.epochconverter.com/) as well.

The task will be executed later according to the time you schedule and will send the alerts to the administrators. The destination email address is defined in the rule. It could be any distribution list or individual’s email.

Results

The administrators received the email informing them that the group xxxx-xxxx4 does not have an owner and action needs to be taken because the rule found that the group is requestable and is missing an owner.

Once the administrators receive the email alert, they can log into IIQ to check the group and add the owner.

Note: I added the two extra conditions to meet the requirements explained previously. This is not mandatory for all organizations. So, if you don’t have such requirements, you can remove that and adjust the code accordingly.

Please follow the above steps carefully to easily implement this useful solution. You can reach out if you need any assistance. I’m happy to help you. If you feel this is helpful, please like and share it with others who might benefit :blush:

6 Likes