Disconnected Applications in Sailpoint IdentityIQ

Date Posted:

28 Oct 2025

Category:

Security

Disconnected Applications in Sailpoint IdentityIQ

Date Posted:

28 Oct 2025

Category:

Security

Disconnected Applications in Sailpoint IdentityIQ

Date Posted:

28 Oct 2025

Category:

Security

Tracking Revocation for Disconnected Applications in Sailpoint IdentityIQ

Introduction Of Disconnected Applications in Sailpoint IdentityIQ

In many IdentityIQ implementations, disconnected applications are not integrated with servicenow or any other ticketing system. This will create a challenging during certification campaign. When an access item of disconnected application is revoked, it’s not actually removed from the user’s account immediately. Someone has to manually do this job. So, on the UI this shows as a warning like “item is revoked but not removed”.

To address this issue and provide visibility into such cases, I designed a custom report configuration that effectively tracks these unremoved access items.

Solution overview:

Instead of relying on database scripts (since there was no existing tracker table for these warnings), I built a custom report using Filter Data Source. This approach provided more flexibility compared to HQL-based reporting, which has limitations in handling complex logic.

The core logic uses two key attributes:

  • Remediation Kickoff = true – this indicates the remediation process has started.

  • Remediation Completed = false – this indicates the access has not been removed.

By combining these attributes as query parameters in the Filter Data Source , the report identifies all certification items that were revoked but still remains active for users.

Adding Validation Logic:

To ensure accuracy, I embedded a small java logic snippet in the report column configuration. This logic checks whether the access item is still assigned to the user. This will give statement in report like item is revoked and not revoked.

Outcome:

With this configuration, administrators can now:

  • Easily track disconnected application access revocation status.

  • Monitor pending removals in real time.

  • Eliminate the need for manual tracking or database level queries.

This approach provides a simple yet reliable way to bring visibility to disconnected application access revocations – all within the existing reporting framework.

Code:

<?xml version='1.0' encoding='UTF-8'?>

<!DOCTYPE TaskDefinition PUBLIC "sailpoint.dtd" "sailpoint.dtd">

<TaskDefinition created="" executor="sailpoint.reporting.LiveReportExecutor" id="" modified="" name="" progressMode="Percentage" resultAction="Rename" subType="Access Review and Certification Reports" template="true" type="LiveReport">

  <Attributes>

    <Map>

      <entry key="report">

        <value>

          <LiveReport title="Report">

            <DataSource dataSourceClass="sailpoint.reporting.datasource.CertificationLiveReportDataSource" objectType="CertificationItem" type="Java">

              <QueryParameters>

                                             Parameter argument="remediationKickedOff" defaultValue="true" property="action.remediationKickedOff" valueClass="boolean"/>

                <Parameter argument="remediationCompleted" defaultValue="false" property="action.remediationCompleted" valueClass="boolean"/>

                <Parameter argument="tags" property="parent.certification.tags.id">

                  <QueryScript>

                    <Source>

                      import sailpoint.object.*;

                      import java.util.*;

                      if (value != null &amp;&amp; !value.isEmpty()){

                      queryOptions.addFilter(Filter.containsAll("parent.certification.tags.id", value));

                      }

                      return queryOptions;

                    </Source>

                  </QueryScript>

                </Parameter>

              </QueryParameters>

            </DataSource>

            <Columns>

              <ReportColumnConfig field="identity" header="Identity" property="parent.targetName" sortable="true" width="110"/>

              <ReportColumnConfig field="objectName" header="Revoked Object Name" property="bundle" scriptArguments="type,exceptionAttributeValue" sortable="true" width="110">

                <RenderScript>

                  <Source>

                    import sailpoint.object.CertificationItem;

                    import sailpoint.object.Bundle;

                    import sailpoint.api.SailPointContext;

                    import sailpoint.tools.GeneralException;

                    String object = null;

                    if (value != null) {

                    object = value;

                    }

                    else {

                    object = scriptArgs.get("exceptionAttributeValue");

                    }

return object;

                  </Source>

                </RenderScript>

              </ReportColumnConfig>

              <ReportColumnConfig field="certName" header="rept_cert_col_cert_access_review" property="parent.certification.name" sortable="true" width="110"/>

              <ReportColumnConfig field="certGroupName" header="rept_cert_col_cert_grp_name" property="parent.certification.certificationGroups.name" sortable="true" width="110"/>

              <ReportColumnConfig field="status" header="rept_comp_cert_col_status" property="summaryStatus" sortable="true" width="110"/>

              <ReportColumnConfig field="decision" header="rept_comp_cert_col_decision" property="action.status" sortable="true" width="110"/>

              <ReportColumnConfig field="applicationName" header="Application Name" property="exceptionEntitlements.application" sortable="true" width="110"/>

              <ReportColumnConfig field="remediationCompleted" header="rept_cert_col_revoke_completed" property="action" width="110">

                <RenderScript>

                  <Source>

                    import sailpoint.reporting.ReportingLibrary;

                    import sailpoint.tools.Util;

                    import java.lang.String;

                    String status = ReportingLibrary.getRemediationStatus(context, value);

                    return Util.isNullOrEmpty(status) ? "" : status;

                  </Source>

                </RenderScript>

              </ReportColumnConfig>

                <RenderScript>

                  <Source>

                    return value != null ? value.getComments() : null;

                  </Source>

                </RenderScript>

              </ReportColumnConfig>

              <ReportColumnConfig field="accessRemovedStatus" header="accessRemovedStatus" property="parent.targetName" scriptArguments="type,summaryStatus,bundle,exceptionAttributeValue,action" width="110">

                <RenderScript>

                  <Source>

                    import sailpoint.object.*;

                    import sailpoint.api.SailPointContext;

                    import sailpoint.tools.GeneralException;

                    import sailpoint.object.QueryOptions;

                    import sailpoint.object.Filter;

                    import java.util.ArrayList;

                    import java.util.Iterator;

                    import java.util.List;

                    Identity id = context.getObjectByName(Identity.class, value);

                    List businessRoleList = id.getAssignedRoles();

                    List itRoleList = id.getDetectedRoles();

                    QueryOptions qoEntUser = new QueryOptions();

                    Filter f1 = Filter.eq("identity", id);

                    qoEntUser.addFilter(f1);

                    Iterator identityEntitlements = context.search(IdentityEntitlement.class, qoEntUser);

                    List allEntsBelongingToUser = new ArrayList();

                    while (identityEntitlements.hasNext()) {

                    IdentityEntitlement idEnt = (IdentityEntitlement) identityEntitlements.next();

                    allEntsBelongingToUser.add(idEnt.getValue());

                    }

                    Object action = scriptArgs.get("action");

                    Object summaryStatus = scriptArgs.get("summaryStatus");

                    String status = null;

                    if (action != null) {

                    CertificationAction.Status actionStatus = action.getStatus();

                    if (actionStatus != null) {

                    status = actionStatus.toString();

                    log.error("Action status: " + status);

                    } else {

                    log.error("Action status is null");

                    }

                    } else {

                    log.error("Action is null");

                    }

                    log.error("Checking status: " + status);

                    if ("Approved".equals(status)) {

                    log.error("inside if::");

                    return "Approved Access";

                    }

                    else {

                    log.error("inside else::");

                    if (scriptArgs.get("exceptionAttributeValue") != null) {

                    String exceptionAttributeValue = scriptArgs.get("exceptionAttributeValue").toString();

                    if (allEntsBelongingToUser.contains(exceptionAttributeValue)) {

                    return "Not Removed";

                    }

                    }

                    if (scriptArgs.get("bundle") != null) {

                    String bundle = scriptArgs.get("bundle").toString();

                    if (businessRoleList.contains(bundle) || itRoleList.contains(bundle)) {

                    return "Not Removed";

                    }

                    }

                    return "Access Removed";

                    }

                  </Source>

                </RenderScript>

              </ReportColumnConfig>

            </Columns>

          </LiveReport>

        </value>

      </entry>

    </Map>

  </Attributes>

  <Description>Displays information about all role composition certifications in detailed format.</Description>

  <RequiredRights>

    <Reference class="sailpoint.object.SPRight" id="" name="FullAccessManagerCertificationReport"/>

  </RequiredRights>

  <Signature>

    <Inputs>

      <Argument name="exclusions" type="boolean"/>

      <Argument multi="true" name="certificationGroups" type="CertificationGroup"/>

      <Argument multi="true" name="roles" type="Bundle">

        <Description>rept_input_biz_role_cert_report_biz_roles</Description>

      </Argument>

      <Argument name="creationDate" type="date">

        <Description>rept_input_cert_report_create_dt</Description>

      </Argument>

      <Argument name="signedDate" type="date">

        <Description>rept_input_cert_report_signed_date</Description>

      </Argument>

      <Argument name="expirationDate" type="date">

        <Description>rept_input_cert_report_exp_dt</Description>

      </Argument>

      <Argument multi="true" name="tags" type="Tag">

        <Description>rept_input_cert_report_tags</Description>

      </Argument>

    </Inputs>

  </Signature>

</TaskDefinition>

Stay tuned to our blog to see more posts about

Sailpoint products implementation and its related updates.

Stay tuned to our blog to see more posts about

Sailpoint products implementation and its related updates.

Category:

Security

Stay tuned to our blog to see more posts about

Sailpoint products implementation and its related updates.

Stay tuned to our blog to see more posts about

Sailpoint products implementation and its related updates.

Category:

Category:

Security

Security

BLS360's IDENTITY AI 2026

HACKATHON

BLS360's IDENTITY AI 2026 HACKATHON

07days
04hours
30minutes
30seconds
07days
04hours
30minutes
30seconds
07days
04hours
30minutes
30seconds

Tracking Revocation for Disconnected Applications in Sailpoint IdentityIQ

Introduction Of Disconnected Applications in Sailpoint IdentityIQ

In many IdentityIQ implementations, disconnected applications are not integrated with servicenow or any other ticketing system. This will create a challenging during certification campaign. When an access item of disconnected application is revoked, it’s not actually removed from the user’s account immediately. Someone has to manually do this job. So, on the UI this shows as a warning like “item is revoked but not removed”.

To address this issue and provide visibility into such cases, I designed a custom report configuration that effectively tracks these unremoved access items.

Solution overview:

Instead of relying on database scripts (since there was no existing tracker table for these warnings), I built a custom report using Filter Data Source. This approach provided more flexibility compared to HQL-based reporting, which has limitations in handling complex logic.

The core logic uses two key attributes:

  • Remediation Kickoff = true – this indicates the remediation process has started.

  • Remediation Completed = false – this indicates the access has not been removed.

By combining these attributes as query parameters in the Filter Data Source , the report identifies all certification items that were revoked but still remains active for users.

Adding Validation Logic:

To ensure accuracy, I embedded a small java logic snippet in the report column configuration. This logic checks whether the access item is still assigned to the user. This will give statement in report like item is revoked and not revoked.

Outcome:

With this configuration, administrators can now:

  • Easily track disconnected application access revocation status.

  • Monitor pending removals in real time.

  • Eliminate the need for manual tracking or database level queries.

This approach provides a simple yet reliable way to bring visibility to disconnected application access revocations – all within the existing reporting framework.

Code:

<?xml version='1.0' encoding='UTF-8'?>

<!DOCTYPE TaskDefinition PUBLIC "sailpoint.dtd" "sailpoint.dtd">

<TaskDefinition created="" executor="sailpoint.reporting.LiveReportExecutor" id="" modified="" name="" progressMode="Percentage" resultAction="Rename" subType="Access Review and Certification Reports" template="true" type="LiveReport">

  <Attributes>

    <Map>

      <entry key="report">

        <value>

          <LiveReport title="Report">

            <DataSource dataSourceClass="sailpoint.reporting.datasource.CertificationLiveReportDataSource" objectType="CertificationItem" type="Java">

              <QueryParameters>

                                             Parameter argument="remediationKickedOff" defaultValue="true" property="action.remediationKickedOff" valueClass="boolean"/>

                <Parameter argument="remediationCompleted" defaultValue="false" property="action.remediationCompleted" valueClass="boolean"/>

                <Parameter argument="tags" property="parent.certification.tags.id">

                  <QueryScript>

                    <Source>

                      import sailpoint.object.*;

                      import java.util.*;

                      if (value != null &amp;&amp; !value.isEmpty()){

                      queryOptions.addFilter(Filter.containsAll("parent.certification.tags.id", value));

                      }

                      return queryOptions;

                    </Source>

                  </QueryScript>

                </Parameter>

              </QueryParameters>

            </DataSource>

            <Columns>

              <ReportColumnConfig field="identity" header="Identity" property="parent.targetName" sortable="true" width="110"/>

              <ReportColumnConfig field="objectName" header="Revoked Object Name" property="bundle" scriptArguments="type,exceptionAttributeValue" sortable="true" width="110">

                <RenderScript>

                  <Source>

                    import sailpoint.object.CertificationItem;

                    import sailpoint.object.Bundle;

                    import sailpoint.api.SailPointContext;

                    import sailpoint.tools.GeneralException;

                    String object = null;

                    if (value != null) {

                    object = value;

                    }

                    else {

                    object = scriptArgs.get("exceptionAttributeValue");

                    }

return object;

                  </Source>

                </RenderScript>

              </ReportColumnConfig>

              <ReportColumnConfig field="certName" header="rept_cert_col_cert_access_review" property="parent.certification.name" sortable="true" width="110"/>

              <ReportColumnConfig field="certGroupName" header="rept_cert_col_cert_grp_name" property="parent.certification.certificationGroups.name" sortable="true" width="110"/>

              <ReportColumnConfig field="status" header="rept_comp_cert_col_status" property="summaryStatus" sortable="true" width="110"/>

              <ReportColumnConfig field="decision" header="rept_comp_cert_col_decision" property="action.status" sortable="true" width="110"/>

              <ReportColumnConfig field="applicationName" header="Application Name" property="exceptionEntitlements.application" sortable="true" width="110"/>

              <ReportColumnConfig field="remediationCompleted" header="rept_cert_col_revoke_completed" property="action" width="110">

                <RenderScript>

                  <Source>

                    import sailpoint.reporting.ReportingLibrary;

                    import sailpoint.tools.Util;

                    import java.lang.String;

                    String status = ReportingLibrary.getRemediationStatus(context, value);

                    return Util.isNullOrEmpty(status) ? "" : status;

                  </Source>

                </RenderScript>

              </ReportColumnConfig>

                <RenderScript>

                  <Source>

                    return value != null ? value.getComments() : null;

                  </Source>

                </RenderScript>

              </ReportColumnConfig>

              <ReportColumnConfig field="accessRemovedStatus" header="accessRemovedStatus" property="parent.targetName" scriptArguments="type,summaryStatus,bundle,exceptionAttributeValue,action" width="110">

                <RenderScript>

                  <Source>

                    import sailpoint.object.*;

                    import sailpoint.api.SailPointContext;

                    import sailpoint.tools.GeneralException;

                    import sailpoint.object.QueryOptions;

                    import sailpoint.object.Filter;

                    import java.util.ArrayList;

                    import java.util.Iterator;

                    import java.util.List;

                    Identity id = context.getObjectByName(Identity.class, value);

                    List businessRoleList = id.getAssignedRoles();

                    List itRoleList = id.getDetectedRoles();

                    QueryOptions qoEntUser = new QueryOptions();

                    Filter f1 = Filter.eq("identity", id);

                    qoEntUser.addFilter(f1);

                    Iterator identityEntitlements = context.search(IdentityEntitlement.class, qoEntUser);

                    List allEntsBelongingToUser = new ArrayList();

                    while (identityEntitlements.hasNext()) {

                    IdentityEntitlement idEnt = (IdentityEntitlement) identityEntitlements.next();

                    allEntsBelongingToUser.add(idEnt.getValue());

                    }

                    Object action = scriptArgs.get("action");

                    Object summaryStatus = scriptArgs.get("summaryStatus");

                    String status = null;

                    if (action != null) {

                    CertificationAction.Status actionStatus = action.getStatus();

                    if (actionStatus != null) {

                    status = actionStatus.toString();

                    log.error("Action status: " + status);

                    } else {

                    log.error("Action status is null");

                    }

                    } else {

                    log.error("Action is null");

                    }

                    log.error("Checking status: " + status);

                    if ("Approved".equals(status)) {

                    log.error("inside if::");

                    return "Approved Access";

                    }

                    else {

                    log.error("inside else::");

                    if (scriptArgs.get("exceptionAttributeValue") != null) {

                    String exceptionAttributeValue = scriptArgs.get("exceptionAttributeValue").toString();

                    if (allEntsBelongingToUser.contains(exceptionAttributeValue)) {

                    return "Not Removed";

                    }

                    }

                    if (scriptArgs.get("bundle") != null) {

                    String bundle = scriptArgs.get("bundle").toString();

                    if (businessRoleList.contains(bundle) || itRoleList.contains(bundle)) {

                    return "Not Removed";

                    }

                    }

                    return "Access Removed";

                    }

                  </Source>

                </RenderScript>

              </ReportColumnConfig>

            </Columns>

          </LiveReport>

        </value>

      </entry>

    </Map>

  </Attributes>

  <Description>Displays information about all role composition certifications in detailed format.</Description>

  <RequiredRights>

    <Reference class="sailpoint.object.SPRight" id="" name="FullAccessManagerCertificationReport"/>

  </RequiredRights>

  <Signature>

    <Inputs>

      <Argument name="exclusions" type="boolean"/>

      <Argument multi="true" name="certificationGroups" type="CertificationGroup"/>

      <Argument multi="true" name="roles" type="Bundle">

        <Description>rept_input_biz_role_cert_report_biz_roles</Description>

      </Argument>

      <Argument name="creationDate" type="date">

        <Description>rept_input_cert_report_create_dt</Description>

      </Argument>

      <Argument name="signedDate" type="date">

        <Description>rept_input_cert_report_signed_date</Description>

      </Argument>

      <Argument name="expirationDate" type="date">

        <Description>rept_input_cert_report_exp_dt</Description>

      </Argument>

      <Argument multi="true" name="tags" type="Tag">

        <Description>rept_input_cert_report_tags</Description>

      </Argument>

    </Inputs>

  </Signature>

</TaskDefinition>