Operations in IdentityNow Transforms

Operations in IdentityNow Transforms

Transforms allow you to manipulate attribute values while aggregating from, or provisioning to, a source.

You can find out more about the use and construction of transforms in Building Transforms in IdentityNow.

In this document, you can see each type of operation you can perform in a transform, as well as examples of their use.

Sometimes you will hear these transforms referred to as Seaspray, which is the code-name for transforms. IdentityNow Transforms and Seaspray are essentially the same thing.

 

 

The general form of a transform definition is as follows:

{
"type": "<transform-type>",
"attributes": {
"transform-attribute-1": "attribute-1-value",
"transform-attribute-2": "attribute-2-value"
}
}

Attribute values can be any JSON value, including nested transforms and primitive operations. A special attribute "input" can be used to seed the input value for a transform instead of deriving it from the transform context.

NOTE: In each of the examples that follows, an example of the input and result of each transform appears at the top of the example in comments. See the following for an example:

// Value -> value
// VALUE -> value

 

Primitive Operations

Seaspray ships out of the box with a number of primitive operations. They are described in the following sections.

 

Operations:

 

Complex Examples

 

Example: Country Code to Timezone

 

// Maps an uppercase version of an account attribute into a lookup table to convert a country code to a timezone.
{
"type": "lookup",
"attributes": {
"table": {
"EN-US": "CST",
"ES-MX": "CST",
"EN-GB": "GMT",
"default": "GMT"
},
"input": {
"type": "upper",
"attributes": {
"input": {
"type": "accountAttribute",
"attributes": {
"sourceName": "Salesforce",
"attributeName": "country"
}
}
}
}
}
}

 

Example: Remove a Prefix and Suffix from a Value

 

// Removes a prefix and suffix from a value. prefix_value_suffix -> value
{
"type": "substring",
"attributes": {
"begin": {
"type": "indexOf",
"attributes": {
"substring": "_"
}
},
"beginOffset": 1,
"end": {
"type": "lastIndexOf",
"attributes": {
"substring": "_"
}
}
}
}

 

Example: Nest Operations to Find or Generate a Valid Email Address

This is also an example of defining a default static value when needed.

// Chooses the first valid from a list of account attributes on different sources, falling
// back to a generated value
{
    "type": "firstValid",
    "attributes": {
        "values": [
            {
                "type": "accountAttribute",
                "attributes": {
                    "attributeName": "mail",
                    "sourceName": "LDAP"
                }
            },
            {
                "type": "accountAttribute",
                "attributes": {
                    "attributeName": "mail",
                    "sourceName": "OIDP"
                }
            },
            {
                "type": "static",
                "attributes": {
                    "value": "$employeeNumber@chadwick.com",
                    "employeeNumber": {
                        "type": "accountAttribute",
                        "attributes": {
                            "attributeName": "employeeNumber",
                            "sourceName": "OIDP"
                        }
                    }
                }
            }
        ]
    }
}

Template Engine

Seaspray ships with a template engine that allows a transform to reference, transform, and render values passed into the transform context. Every string value in a seaspray transform may contain templated text, and will be run through the template engine.

Example

 

// In the following string, the text "$firstName" is replaced by the value of firstName in the template context. The same goes for "$lastName".
"$firstName.$lastName"

 

Identity Attribute Context

The following variables are available to the template engine when a transform is used to source an identity attribute.

  • identity - sailpoint.object.Identity - The identity on which attribute promotion is being performed.
  • oldValue - Object - The previous value of the attribute.
  • attributeDefinition - sailpoint.object.ObjectAttribute - The definition of the attribute being promoted.

 

Account Profile Context

The following variables are available to the template engine when a transform is used in an account profile.

  • field - sailpoint.object.Field - The field definition backing the account profile attribute.
  • identity - sailpoint.object.Identity - The identity that the account profile is being generated for.
  • application - sailpoint.object.Application - The application backing the source that owns the account profile.
  • current - Object - The current value of the attribute.

 

Feedback

If you have feedback or suggestions on useful additions to the library, please send an email to dan.smith or jeff.upton and your input will be considered.

Comments

A small note about the dateCompare transform. The NOW keyword includes a time element. So comparing a date without a time element to NOW will not return equal in most cases.

 

However, you can use the dateFormat transform to transform the NOW keyword to a date without a time element.

 

for example:

{
          "attributes": {
	        "firstDate": "2019-08-30T13:00:00+00:00",
	        "negativeCondition": "negative",
	        "operator": "GTE",
	        "positiveCondition": "positive",
	        "secondDate": {
              "attributes": {
                "values": "now",
                "inputFormat": "yyyy-MM-dd",
                "outputFormat": "ISO8601"
              },
              "type": "dateFormat"
            }
	        },    
	      "type": "dateCompare"
        
		}

 

// NJ, NY -> US
// India -> Asia
// UK,France -> Europe
// Other

We have a requirement, if any Attribute
* Input is NJ OR NY should come up as US.
* If Input is India or Sri Lanka should come up as Asia.
* Input is UK OR France should come up as UK and
* rest other value to come up as Other.

The below transformation will work for it.

{
"attributes": {
"values": [
{
"attributes": {
"attributeName": "location",
},
"type": "identityAttribute"
},
"table": {
"NJ","NY": "US",
"India": "Asia",
"UK","France": "Europe",
"default": "Other"
}
]
},

"id": "SailPoint Location",
"type": "lookup"
},

Please Suggest Thanks.

What if you use this table instead?

 

"table": {
"NJ": "US",
"NY": "US",
"India": "Asia",
"UK": "Europe",
"France": "Europe",
"default": "Other"
}

What can i do if i want to pass the current date when the condition is positive?

{
          "attributes": {
	        "firstDate": "2019-08-30T13:00:00+00:00",
	        "negativeCondition": "negative",
	        "operator": "GTE",
	        "positiveCondition": "positive", //instead of positive i need the current date
	        "secondDate": {
              "attributes": {
                "values": "now",
                "inputFormat": "yyyy-MM-dd",
                "outputFormat": "ISO8601"
              },
              "type": "dateFormat"
            }
	        },    
	      "type": "dateCompare"
        
		}

 

That's not possible.

Is there a way capitalize the first character of a string? e.g. transform john to John

@niharikagurjar , the dateFormat transform does not have access to the "now" keyword at this time, so what you have written will not work. The "now" keyword is currently only available to the dateCompare transform.

Please correct the links under "Primitive Operations" to link to page anchors on current page, instead of linking users to other pages they cannot access.

Is there a Camel case transform available?

@vkoldenh Can you help with the example you've posted here for the "now" variable? I've tried it several ways and can't get it to work:

Specifically:

1) Why did you use "yyyy-MM-dd" as the inputFormat when we know the "now" variable has a time element? How can that possibly be the correct input format? FWIW, I tried using ISO8601 as the outputFormat and having no inputFormat because that's option, but this didn't work.

2) I did test to see if your example should be switched, and perhaps the "now" variable needs to have an inputFormat of ISO8601 and an outputFormat of yyyy-MM-dd in order to strip that time stamp, but that didn't work for me either, as I assume that secondDate value HAS to be ISO8601 based on this document.

3) Can you even use the "values" attribute on the dateFormat transform? Don't you need to have it as "input" in some way? I only ask because no matter how I try to utilize a dateFormat for "now," it doesn't work for my data. I'm getting a null pointer exception, which in all honesty I would expect due to the format of what you've provided, but I can't seem to do any better so I'm asking for clarity.

Thanks in advance!

 

@cassidiopia  The point I wanted to make is that the NOW keyword contains a time element. So comparing NOW to 31-08-2020 will never be true (except at midnight) as the comparison will add a time element of 00:00.000 to the date.

As I do not know what you are trying to do, here is an example I use to determine the status of a identity.

 

"attributes": {
                "firstDate": {
                    "attributes": {
                        "values": [
                            {
                                "attributes": {
                                    "values": [
                                        {
                                            "attributes": {
                                                "name": "endDate"
                                            },
                                            "type": "identityAttribute"
                                        },
                                        "2999-12-31"
                                    ]
                                },
                                "type": "firstValid"
                            },
                            "T23:59:59.000Z"
                        ]
                    },
                    "type": "concat"
                },
                "negativeCondition": "leaver",
                "operator": "GTE",
                "positiveCondition": "active",
                "secondDate": "now"
            },
            "type": "dateCompare"

 

It uses the enddate and adds the 23:59 time part to it. As our system gives us the last working day we want the user to be active the whole day. Then we compare it with NOW.

NOTE: This sniplet is part of a bigger static transform, so there might be some syntax error due to copy/paste.

Hope this help, otherwise you might need to contact support. I do not work for Sailpoint.

Thanks, @vkoldenh that's much more helpful. I ended up going that same route after many failed attempts to format the now keyword.

I appreciate it!

Just to help others, you can do very cool stuff using the static transform. For instance, getting an attribute from the manager of an identity you can use the following:

 

{
    "attributes": {
        "value": "$identity.getManager().getAttribute(\"someattribute\")"
    },
    "id": "get Manager Attribute",
    "type": "static"
}

Hi,

I am trying to generate the userid in identity now using ransforms which is a numeral  for example 555. I want a condition where if I create the next user the userid should be userid + 1. Which in our case would be 556. Subequently the userid generated for new users should be 557, 558.....

 

I have built this logic below.  Please validate it. 

 

Thanks

 

{
  "attributes": {
    "cloudMaxSize": "100",
    "cloudMaxUniqueChecks": "10",
    "cloudRequired": "true"
  },
  "name": "NumericID",
  "transform": {
    "type": "usernameGenerator",
    "attributes": {
      "sourceCheck": true,
      "patterns": [
        "$fi{uniqueCounter}"
      ],
      "ln": {
        "type": "identityAttribute",
        "attributes": {
          "name": "lastname"
        }
      },
      "fi": {
        "type": "substring",
        "attributes": {
          "input": {
            "type": "identityAttribute",
            "attributes": {
              "name": "firstname"
            }
          },
          "begin": 0,
          "end": 1
        }
      }
    }
  },
  "type": "usernameGenerator"
}

using info from "vkoldenh" above, wrapped get manager attribute in a firstValid transform in case that the attribute comes as null / unavailable which causes an error.

 

{
    "attributes": {
        "values": [
            {
                "attributes": {
                    "value""$identity.getManager().getAttribute(\"distinguishedName\")"
                },
                "type""static"
            },
            {
                "attributes": {
                    "value"""
                },
                "type""static"
            }
        ]
    },
    "type""firstValid",
    "name""Managers DistinguishedName Transform"
}
 

Can you cascade primitives within one Transform? I've encountered phone data with a forward slash delimiter between the area code and the exchange. I think it is giving the E.164 transform trouble.  Can you run "Replace" and replace / with a space, then run the E.164?

@david_wardeI believe that it's possible. The example in the E.164 transform has an example which contains a static transform. Change that to the replace transform and the proper regex and it might just work. Otherwise, please post the transform, and i will have a look.

Hi @vkoldenh 

Could you help me reference the $oldValue of an identity attribute in a transform?

It is mentioned in the documentation but no methodology has been shared. I have unable to get it to work.

Reference: https://developer.sailpoint.com/docs/transforms/transform_operations/transform_operations.html#ident...

Hi @ksbagade 

 

The oldValue variable is available, but might only be useful in a static transform. Below is a transform which is using the oldValue variable to verify if there has been a previous value or not.

{
            "attributes": {
                "preactiveoffset": {
                    "attributes": {
                        "expression": "now+2h/m",
                        "roundUp": true
                    },
                    "type": "dateMath"
                },
                "current_lcs": {
                    "attributes": {
                        "values": [
                            {
                                "attributes": {
                                    "name": "currentLifecyclestate"
                                },
                                "type": "identityAttribute"
                            },
                            "empty"
                        ]
                    },
                    "type": "firstValid"
                },
                "value": "#if(${current_lcs} eq \"prehire\" && ${oldValue} eq \"1900-01-01T00:00Z\")${preactiveoffset}#{elseif}(${current_lcs} eq \"prehire\")${oldValue}#{else}1900-01-01T00:00Z#end"
            },
            "id": "Determine PreActive offsetDate",
            "type": "static"
        },

 

If you explain what you might want to achieve I can help you if needed. But in short, the oldValue is filled with the current value of the attribute, so before the transform has changed it to something new. However, when the Identity Profile is created for a Identity the initial value of the oldValue variable is null. So keep that in mind please.

Hi @vkoldenh,

Thank you for the detailed response.

I am working on a use case that requires evaluation of the $oldValue of a specific Identity Attribute in a Transform being attached to another Identity Attribute in the Identity Profile.

Is it possible to call the $oldValue of another Identity Attribute apart from the one the transform is attached to?

Hi @ksbagade 

 

I don't think that is possible. The Identity Attributes are all evaluated in a random order (except for the LifeCycleState attribute which is always done last). So even if it was possible you have no knowledge if the oldValue of the other Identity Attibute is really the oldValue or has already been evaluated.

I think you can try to use an additional Identity Attribute which has the oldValue set and refer to that identityAttribute. But this depends on how the attribute should be filled.

You can also make a bigger transform and reuse the transfrom from the other Identity Attribute which you want to use. But getting the oldValue might be tricky.

Alternatively, you can try a rule but you need SailPoint Professional Services for that.

Hope this helps.

Regards,

Vincent

Hi @ksbagade

One issue I believe that is encountered with $oldValue is that the attribute containing $oldValue does not work very well when the related attribute changes a second time and you want to track changes over time. I have a potential solution for this which is having two additional identity fields. One that tracks the primary attribute old value and one that has an attribute change date that is used to reset the old value after a defined period of time. If you want to use transforms like this be sure to do some thorough testing including adding new identities and changes to in-situ identities to make sure it is working for you. I just created these transforms so that have not gone through a lot of testing.

(1) Transform for setting change date.

{
    "name""Test HR Department Changed Date Transform",
    "type""static",
    "attributes": {
        "Dept": {
            "attributes": {
                "values": [
                    {
                        "attributes": {
                            "attributeName""Org Level 2",
                            "sourceName""Test HR"
                        },
                        "type""accountAttribute"
                    },
                    "no department"
                ]
            },
            "type""firstValid"
        },
        "PrevDept": {
            "attributes": {
                "values": [
                    {
                        "attributes": {
                            "name""departmentPrevious"
                        },
                        "type""identityAttribute"
                    },
                    "no department"
                ]
            },
            "type""firstValid"
        },
        "Date": {
            "attributes": {
                "expression""now",
                "roundUp"false
            },
            "type""dateMath"
        },
        "value""#if($PrevDept != $Dept)$Date#{else}9999-01-01T00:00Z#end"
    }
}
 
(2) Transform for setting $oldValue
 
{
    "name""Test HR Previous Value Department",
    "type""firstValid",
    "attributes": {
        "values": [
            {
                "attributes": {
                    "firstDate": {
                        "attributes": {
                            "expression""+1h/m",
                            "input": {
                                "attributes": {
                                    "name""departmentChangedDate"
                                },
                                "type""identityAttribute"
                            }
                        },
                        "type""dateMath"
                    },
                    "secondDate""now",
                    "operator""lt",
                    "positiveCondition": {
                        "attributes": {
                            "attributeName""Org Level 2",
                            "sourceName""Test HR"
                        },
                        "type""accountAttribute"
                    },
                    "negativeCondition""$oldValue"
                },
                "type""dateCompare"
            },
            "$oldValue",
            {
                "attributes": {
                    "attributeName""Org Level 2",
                    "sourceName""Test HR"
                },
                "type""accountAttribute"
            },
            "none"
        ],
        "ignoreErrors""true"
    }
}
 

 

 

 

Hi @MikeHamilton 

I've been trying to get the last previous value of an attribute. So if there was a modification from values X01 to X02 and finally X03, i always need to read the n-1 previous value, that would be X02 here.

I tried using this transform, but i always get the first value there was in this attribute, for example X01. Code attached. Any idea? Thanks!

{
  "attributes": {
    "values": [
      "$oldValue",
      {
        "attributes": {
          "sourceName": "Mock-SAIP",
          "attributeName": "storeID"
        },
        "type": "accountAttribute"
      }
    ]
  },
  "type": "firstValid",
  "name": "oldValue firstValid"
}

 

Version history
Revision #:
26 of 26
Last update:
‎Feb 04, 2022 03:28 PM
Updated by: