In case Felipe’s linked article ever changes, here are the details of the article.
Overview
Often there is a need to filter accounts on a source during an account aggregation process.
While some source connectors offer an ability to filter accounts natively, other source connectors may not, usually due to technical limitations - such as a lack of filtering abilities in APIs the connector is calling.
Luckily, IdentityNow does have an ability to filter accounts on its side, as part of the aggregation process. This is configured by setting a filterString
property on the source configuration. Once configured, the aggregation process matches accounts as they are aggregated against the filter string. Accounts which match the filter string will be filtered. Accounts which do not match, will be sent to IdentityNow as normal.
The trade-offs are summarized here:
Advantages
- Ability to filter accounts during an account aggregation, despite connectivity.
- Applies to all account aggregations and delta aggregations.
Disadvantages
-
This offers no aggregation performance improvements. This still forces an aggregation to run in its entriety, and filters the accounts as they are iterated back to IdentityNow.If performance improvements are desired, we recommend leveraging filtering on the connector, where applicable. Not all source connectors have filtering abilities, as described above.
-
Excessive filtering can produce aggregation timeouts. This is because IdentityNow (in the cloud) never receives any accounts (due to filtering) and keeps waiting for a response from the virtual appliance. The more accounts which are filtered, the greater chance for an aggregation timeout.
Note: For dedicated connectors, filterString
will not work on the following attribute names unless they are marked as Account Name in the source.
- displayName
- identity
- instance
We don’t recommend changing the default Account Name of any dedicated connectors as it may have some unforeseen issues.
Configuration
In order to configure this, you need to modify the source configurations to add in a filterString
property. This can be done with a simple partial update to the source, using the REST APIs.
As an example, we may want to only bring in accounts which belong to employees. If employee was denoted by the “type” attribute on the account, we might configure our filterString
to look like this:
Attribute |
Value |
filterString |
( type != “Employee” ) |
Different Filters for Different Objects - The filterString
applies to all objects which are aggregated. If you want to get more specific to accounts or groups which are filtered, you can use account.filterString
or group.filterString
to denote specific filters for those particular objects. This document assumes filterString
but you can also use the others as well.
Any account which matches this criteria will be filtered. The value you see is filter string syntax. The filter string reference guide follows this section. More complex values can be implemented, such as groupings, logical operators, etc.
The configuration of this is done via the IdentityNow Source Partial Update API. The details are as follows:
Request
PATCH /v3/sources/{id}
HTTP Headers
Key |
Value |
Description |
authorization |
Bearer {token} |
This is the JWT OAuth token. |
content-type |
application/json-patch+json |
This is needed for PATCH operations. |
Path Parameters
- id - The ID of the source. e.g. 2c9180835d191a86015d27ac132112ae
Query Parameters
Request Body
JSON Patch syntax representing the change:
content-type: application/json-patch+json
[
{
"op": "add",
"path": "/connectorAttributes/filterString",
"value": "( type != \"Employee\" )"
}
]
Example
curl -X PATCH \
https://example.api.identitynow.com/v3/sources/2c9180835d191a86015d27ac132112ae \
-H 'Authorization: Bearer eyJ...BRM' \
-H 'Content-Type: application/json-patch+json' \
-H 'cache-control: no-cache' \
-d '[
{
"op": "add",
"path": "/connectorAttributes/filterString",
"value": "( type != \"Employee\" )"
}
]'
Response
Response Codes
HTTP Code |
HTTP Status |
Description |
200 |
OK |
Returned if the request was successfully processed. |
401 |
Unauthorized |
Returned if there is no authorization header, or if the JWT token is expired. |
403 |
Forbidden |
Returned if the user you are running as, doesn’t have access to this end-point. |
429 |
Too Many Requests |
Returned in response to too many requests in a given period of time - rate limited. The Retry-After header in the response includes how long to wait before trying again. |
500 |
Internal Server Error |
Returned if there is an unexpected error. |
Response Body
content-type: application/json
A modified source object.
Filter Reference
This is a filter reference to show how various account filters might be constructed. The expressions genreally follow the form:
{property} {operation} {value}
Here are additional rules for parsing correctly:
- String literals should have double-quotes.
- True / false values are treated as boolean literals
- Digits are treated as numbers
- The string value ‘null’ (no quotes) is treated as null
- Everything else is assumed to be the property name
- e.g.
email == contactAddress
Composite Filters
Filters can also be grouped and used together as composite filters:
Composite Filter |
Pattern |
Example |
Grouping |
( {expression} ) |
`!( type == “Employee” ) |
AND |
( {expression} && {expression} ) |
( type == "Employee" && location == "Austin" ) |
OR |
`( {expression} |
|
NOT |
!( {expression} ) |
!( company == "SailPoint" ) |
Operations
Note: Any comparison operator can be prepended with an ‘i’ to signify a case-insensitive comparison (eg - i==, i!=, etc…).
Operation |
Pattern |
Example |
Equals |
{property} == {value} |
firstname == "Neil" |
Not Equals |
{property} != {value} |
lastname != "Smith" |
Less Than |
{property} < {value} |
age < 18 |
Greater Than |
{property} > value |
age > 18 |
Less Than, Equals |
{property} <= {value} |
age <= 18 |
Greater Than, Equals |
{property} >= value |
age >= 18 |
Is Null |
{property}.isNull() |
email.isNull() |
Not Null |
{property}.notNull() |
company.notNull() |
Is Empty |
{property}.isEmpty() |
Groups.isEmpty() |
Like, Exact |
{property} == {value} |
firstname == "Neil" |
Like, Start |
{property}.startsWith( {value} ) |
lastname.startsWith( "Mc" ) |
Like, Start |
|
|
(Ignoring Case) |
{property}.startsWithIgnoreCase( {value} ) |
lastname.startsWithIgnoreCase( "Mc" ) |
Like, End |
{property}.endsWith( {value} ) |
email.endsWith( "@sailpoint.com" ) |
Like, End |
|
|
(Ignoring Case) |
{property}.endsWithIgnoreCase( {value} ) |
email.endsWithIgnoreCase( "@sailpoint.com" ) |
Like, Anywhere |
{property}.contains( {value} ) |
email.contains( "sail" ) |
Like, Anywhere (Ignoring Case) |
{property}.containsIgnoreCase( {value} ) |
email.containsIgnoreCase( "sail" ) |
Contains All |
{property}.containsAll({ {value}, {value}, {value}, ... }) |
Groups.containsAll( { "A", "B", "C" } ) |
Contains All |
|
|
(Ignoring Case) |
{property}.containsAllIgnoreCase({ {value}, {value}, {value}, ... }) |
Groups.containsAllIgnoreCase( { "A", "B", "C" } ) |