Examples SAML

Login any user as guestuser if the attribute groups contains guests. 

This script should be mapped to the Application-attribute Username and assumes that the usernames comes from the SAML Name-ID:

// Check the group-attribute for the entry "guests"
if(mapping.groups != null && mapping.groups.contains("guests")) {
    // Return the static value "guestuser"
   return "guestuser"
} else {
    // Otherwise use the value from the Name ID
    return ATTR_NAMEID
}Handle Groups Not Sent As Multivalue Attribute
GROOVY

Set SAML attribute email to an unique value if the attribute is not present

This script should be mapped to the Application-attribute E-Mail Address.

return mapping.'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'?.first() ?: "UID@example.com"
GROOVY

Set the last name to uppercase

This script should be applied to the Application-attribute Full Name and assumes the first name is in first and the last name in last

// If the attribute lastName is present transform it to uppercase,
// otherwise use an empty string
def lastName  = mapping.lastName?.first() ? mapping.lastName.first().toUpperCase() : ""

// Use an emptry String for the first name if it is not present
def firstName = mapping.firstName?.first() ?: ""

def fullName = "$firstName $lastName"

// The SLF4J-logger de.resolution.retransform.impl.transformers.groovy.GroovyTransformerScript
// is available as logger and can be used to write to the application-log.
// warn is enabled by default, so this message should be visible in the log 
if (fullName.trim().isEmpty()) { 
    logger.warn("Fullname is empty or contains only whitespace")
}
    
return fullName
CODE

Combine groups from attributes with the value true

In this example. the IdP sends a fixed set of group names as keys with the value true if the user is member of that group:

"attributes": {
    "grp1": [ "true"],
	"grp2": [ "true"],
	"grp3": [ "false"]
},
CODE
def groups = []

if(mapping.grp1?.contains("true")) {
    groups.add("grp1");
}
if(mapping.grp2?.contains("true")) {
    groups.add("grp2");
}
if(mapping.grp3?.contains("true")) {
    groups.add("grp3");
}

// This logger should not be used in a production-system
logger.warn("Groups are {}", groups)

return groups
CODE

Handle Groups Not Sent As Multivalue Attribute in SAML Response, Replace Group Names In The Process

// list of keys/ values to replace group names after splitting
def trafoMap = ["20368564" : "one-group", "10096280" : "other-group"] 

// read groups attribute from SAML Response split by semicolon
def splitted = mapping.groups?.first()?.split(";") 

// keep this if you want to remove groups from the user, should there be none in the SAML response
if(splitted == null) {
    return DROP_ALL // return no values, so existing groups may be removed
}

return splitted
	.collect{trafoMap[it]} // apply transformation rules from trafoMap (search and replace)
	.findAll{it} // filter null values a.k.a. drop groups not in the trafoMap
GROOVY

Handle Groups Not Sent As Multivalue Attribute in SAML Response And Filter Empty Strings

// read groups attribute from SAML Response split by comma (or any other character that separates them)
def splitted = mapping.groups?.first()?.split(",") 

return splitted.findAll{it} // filter null values and empty strings and return
GROOVY

Transform one group from the SAML response to two or more groups

// Input your data as per the descriptions below
// Replace YourIDPGroupAttribute with your actual IdP Group Attribute
def idpGroupAttribute = "YourIDPGroupAttribute"

// Replace IdP_groupName with the group name that you need to transform
def idpGroupName = "IdP_groupName"

// Replace "replacement1" and "replacement2" with the actual group names replacements, and you can add other elements if needed
def replacements = ["replacement1", "replacement2"]

// No need to change anything in the following section
def groups = mapping.get(idpGroupAttribute)
if (groups.contains(idpGroupName)) {
    groups.remove(idpGroupName)
    groups.addAll(replacements)
} 
return groups
CODE

Transform one group from the SAML response to two or more groups and also perform more direct transformations

// Input your data as per the descriptions below
// Replace YourIDPGroupAttribute with your actual IdP Group Attribute
// The example below is the Azure AD Default groups claim
def idpGroupAttribute = "http://schemas.microsoft.com/ws/2008/06/identity/claims/groups"

// Replace IdP_groupName_1 with the group name that you want to transform into multiple other groups
def idpGroupName_1 = "my-group-1"

// Add as many groups as you want to be assigned to the user, if idpGroupName_1 is present
def idpGroupName_1_transform_to_groups = ["your-group-1", "your-group-2"]

// No need to change anything in the following block, this takes
def groups = mapping.get(idpGroupAttribute)
if (groups.contains(idpGroupName_1)) {
    groups.remove(idpGroupName_1)
    groups.addAll(idpGroupName_1_transform_to_groups)
} 

// Add more 1:1 replacements here
def idpGroupName_2 = "transform-me-1"
def idpGroupNameReplacement_2 = "your-group-3"

if (groups.contains(idpGroupName_2)) {
    groups.remove(idpGroupName_2)
    groups.add(idpGroupNameReplacement_2)
} 


def idpGroupName_3 = "transform-me-2"
def idpGroupNameReplacement_3 = "your-group-4"


if (groups.contains(idpGroupName_3)) {
    groups.remove(idpGroupName_3)
    groups.add(idpGroupNameReplacement_3)
} 

return groups
CODE

Allow user authentication based on the email domain of the user 

// Check the email-attribute and if it is not empty check if it contains the email domain
if (mapping.email[0] != null) {
	if (mapping.email[0].contains("@lab.resolution.de")) {
    	// if it contains the domain return the NameID
    	return mapping.ATTR_NAMEID
	} else {
    	// Otherwise drop the authentication
    	return DROP_ALL
	}
} else {
    // if email-attribute and if it is empty log a warning message and drop the authentication
	logger.warn("Dropping User authentication due to missing SAML attribute email")
	return DROP_ALL
}
CODE

Add a user into a specific group when not calling the JSM portal and the IdP returns a specific group

def groupsToReturn = (groups != null && groups != DROP) ? groups : []
//Check ATTR_SD_CUSTOMER attribute is false and if user is in group g1, when both conditions are met add the user to the group jira-servicedesk-users
if(ATTR_SD_CUSTOMER != ["true"]) {
    logger.warn("SD Portal is NOT called")
    if(groupsToReturn.contains("g1")) {
        logger.warn("User is in g1")
        groupsToReturn.add("jira-servicedesk-users")       
    } else {
		//If the user is not in group g1 
        logger.warn("User is not in g1")
    }
//If user has called the JSM portal    
} else {
	//If user has called the JSM portal    
	logger.warn("SD Portal is called")
}
return groupsToReturn

GROOVY

Drop the user if the email is already present

Use this to transform the email-attribute. If the email-address is found and don't belong to this user, drop the user.

String email = mapping.email?.first()
String username = mapping.ATTR_NAMEID?.first()

// Check if there is already a user with this email in the system
def existingUser = findUser("ATTR_EMAIL",email)
if(existingUser == null) {
    return email
} else {
    // If there is such a user check if it's the same username
    if(username == existingUser.get("ATTR_NAME").first()) {
        return email
    } else {
        // if not drop the user
        return DROP_USER
    }
}
CODE

Exclude some users from being reactivated during login even if that option is enabled

Use the following Groovy code for the username attribute mapping (assuming that the username attribute is mapped to the default value originally ATTR_NAMEID):

// Enter the list of usernames not to be reactivated below:
def usernameBlacklist = ["username1","username2","username3"]
// 

String username = mapping.ATTR_NAMEID
if (username in usernameBlacklist) {
    def user = findUser("ATTR_NAME",username)
    def active = user?.ATTR_ACTIVE
    if (active?.getAt(0) == "true") {
        return username
    } else {
        return DROP_ALL
        }
} else { 
    return username 
}
GROOVY


Fail the authentication if a value (among a list) of an attribute matches any value of another list

Use the following Groovy code for the username attribute mapping.

Assumptions:

  • The Jira Username attribute is mapped to ATTR_NAMEID
  • The attribute to be checked is called userDep
// Enter here the list of values that would be checked against:
allowedList = ["value1", "value2", "value3", "value4"]
//

String user = mapping.ATTR_NAMEID?.getAt(0)
def dep = mapping.userDep

if (dep.any { allowedList.contains( it ) }) {
    return user
} else {
    return DROP_ALL
}
GROOVY


Assign a specific group based on some conditions

In the following example, we are going to assign group 'myGroup' to the users if the following two conditions are both met:

  • The nameId starts with 'M' and followed by some numbers
  • The department attribute has a value of 'IT'

Assuming the following attributes returned by the IdP:

"loginInformation" : {
	"nameId" : "M1234",
	"attributes" : {
		"ATTR_NAMEID" : [ "M1234" ],
		"groups" : [ "group1", "group2" ],
		"email" : [ "john.doe@example.com" ],
        "fullname" : [ "John Doe" ],
		"department" : [ "IT" ]
      }
    }
CODE

Use the following Groovy code for the Groups attribute mapping.

username = mapping.ATTR_NAMEID
dept = mapping.department
grp = mapping.groups

if (dept?.getAt(0) == "IT" && username.any {it ==~ /M\d+/} ) {
    grp.addAll("myGroup")
}

return grp
GROOVY


Just-In-Time Provisiong add licensed Jira group to any authentication through SAML SSO

In the following example, we are going to add licensed Jira group to the users if the following conditions are met:

  • The user login via SAML
  • If SAML attribute <your-attribute> has a value of 'Internal'
    • The user will be added to the group 'jira-users'
  • Else
    • The user will be added to the group 'jira-external-users'

Use the following Groovy code for the Groups attribute mapping.

def existingGroups = existing?.ATTR_GROUPS ?: []

def groupsToReturn = []

groupsToReturn.addAll(existingGroups)

if(mapping?.<your-attribute>?.first() == "Internal") {
    groupsToReturn.add("jira-users")
    }
else {
    groupsToReturn.add("jira-external-users")
    }

return groupsToReturn.toUnique()
GROOVY

Variant

  • If SAML attribute <your-attribute> is null
    • The user will be added to the group 'jira-empty'


def existingGroups = existing?.ATTR_GROUPS ?: []
def groupsToReturn = []

groupsToReturn.addAll(existingGroups)

// Check <your-attribute> and if it is not null check if it contains Internal
// Assign group jira-empty, if it is null.
// Assign group jira-users, if it is not null but does contain Internal
// Assign group jira-external-users, if it is not null but does not contain Internal

if (mapping.<your-attribute> == null) 
{
    groupsToReturn.add("jira-empty")
}  

else if (mapping.<your-attribute>.first() == "Internal") 
{
    groupsToReturn.add("jira-users")
} 

else 
{
    groupsToReturn.add("jira-external-users")
}

return groupsToReturn.toUnique()
GROOVY


Examples User Sync

Assign a license group based on some conditions and only if user login via SAML

In the following example, we are going to assign the Jira license group 'jira-users' or jira-external-users' to the users if the following conditions are met:

  • The user login via SAML
  • The WorkerType attribute has a value of 'Internal'
    • The user will be added to the group 'jira-users'
  • The WorkerType attribute starts with 'E[ABCD]'
    • The user will be added to the group 'jira-external-users'
  • If the user is already part of the other group (e.g. jira-users) the group will be removed. So, the user can only be part of one group.

Since we are using Group Management, we moved the Group Management configuration (UI) to the groovy script, too. Please remove the UI Group Management configuration and modify the script to your needs.

Assuming the attribute WorkerType is returned by the IdP:

"loginInformation" : {
	"nameId" : "M1234",
	"attributes" : {
		"ATTR_NAMEID" : [ "M1234" ],
		"email" : [ "john.doe@example.com" ],
        "fullname" : [ "John Doe" ],
		"WorkerType" : [ "Internal" ]
      }
    }
CODE


This script should be mapped to the Application-attribute Groups:

// Groups to never remove (can be overriden below)
def groupsToKeep = [ /jira-.*/ ]

// Groups in this list are filtered out
def groupBlacklist = [ /\[TBD.*/ ]

// Groups not in this list are filtered out
def groupWhitelist = [ /\[TBD\].*/, "jira-users", "jira-external-users" ]

def existingGroups = existing?.ATTR_GROUPS?.asStringList() ?: []
def connectorGroups =  con?.GROUPS?.asStringList() ?: []

def groupsToReturn = []

def existingGroupsToKeep = existingGroups.findAll {
    egroup -> groupsToKeep.any {keep -> egroup ==~ keep } }

groupsToReturn.addAll(existingGroupsToKeep)

// Every group-pattern in the blacklist must NOT match a group's name

def groupsAfterBlacklist = connectorGroups.findAll {
    cgroup -> groupBlacklist.every {bl -> !(cgroup ==~ bl) } }

// Any matching group-pattern in the whitelist is sufficient 
// be aware that the blacklisted groups are filtered out already,
// so the blacklist has higher precedence than the whitelist.

def groupsAfterWhitelist = groupsAfterBlacklist.findAll {
    cgroup -> groupWhitelist.any {wl ->   cgroup ==~ wl  } }

groupsToReturn.addAll(groupsAfterWhitelist)

String workerType = saml.'WorkerType'?.first()?.asString()

// When saml is not empty, this
// is a single user update during saml login
boolean isLogin = !saml?.isEmpty()

if(isLogin) {
    if(workerType ==~ /^Internal.*/ ) {
        groupsToReturn.add("jira-users")
        groupsToReturn.remove("jira-external-users") }
    else if(workerType ==~ /^E[ABC].*/) {
        groupsToReturn.add("jira-external-users")
        groupsToReturn.remove("jira-users")
    }
}

return groupsToReturn.toUnique()
GROOVY

Remove any user from the group confluence-users if the user is disabled on the IdP side.

The script could be helpful since Confluence is sometimes displaying an incorrect number of active users. License will display the correct number of users only if the users are not part of any group with CAN-USE permission (in our case 'confluence-users').

This script should be mapped to the Application-attribute Groups:

def groups = existing?.ATTR_GROUPS ?: []
groups.addAll(con?.GROUPS ?: [])

// Check if the account is disabled and the user is part of the group confluence-users
if (con.accountEnabled == "false" && groups.contains("confluence-users")) { 
// If the criterias match remove the user from the group confluence-users
groups.remove("confluence-users")
}

return groups.toUnique()
GROOVY

Transform group names using regular expressions

Replacing attributes using regular expressions can be done in a Groovy-transformation instead of adding Regexes to the configuration. Especially when a large number of regular expressions need to be applied it can be helpful to have all of them in a piece of code

// collect{} applies the given closure to all
// elements in a list and returns a list containing 
// all the transformed elements. 
// Using safe-dereference ?. to avoid a NullPointerException if GROUPS is not present
return con.GROUPS?.collect{
    grp -> grp.toString()                 // toString() is required because the elements are StringStructuredData
        .replaceAll(/Pink Floyd/,'def')   // using Slashy Strings for the regex avoids some escaping 
        .replaceAll(/Blind(.*)/,'xyz$1')  // using '' instead of "" for the replacements allows using $ without escaping 
        .replaceAll(/\./,'%')             // replacing . requires escaping (\.), in a normal String this would be \\., but not required in Slashy Strings
        // add more as needed
    } ?: []                               // return an empty list of the GROUP-attribute is not present
CODE

Assuming the user data looks like this

{
  "con": {
    "USERID": "damaris.rau",
    "Unique_ID": "b9e13bcc-3df9-4eaf-ab8c-c8e77485d484",
    "EMAIL": "damaris.rau@example.org",
    "FULLNAME": "Damaris Rau",
    "Manager": "zane.maggio",
    "GROUPS": [
      "Pink Floyd",
      "Blind Faith",
      "R.E.M."
    ]
  },
  "saml": {},
  "existing": {}
}
CODE

This transformation returns

["def","xyz Faith","R%E%M%"]
CODE

Apply the Cleanup Behaviour (e.g. Disable) when a user is a member of a certain group

Use the following Groovy code for the Groups attribute mapping in the connector:

// Replace the <> placeholder with the group name below
def exclusionGroup = "<enter the group name here>"

if (con.GROUPS.contains(exclusionGroup)) {
    return DROP_ALL
}

return GROUPS
GROOVY

Assign default groups only to a certain domain in the email address

Use the following Groovy code for the Groups attribute mapping in the connector:

// default groups to assign under specific criteria
// change the group names as per your requirement
def defaultGroups = ["jira-users", "confluence-users" ]

// mapping the IdP groups
def connectorGroups =  con?.GROUPS?.asStringList() ?: []

// mapping the email address
// change userPrincipalName below to your email attribute
def email = con?.userPrincipalName?.first() ?: []

// check if the email address has a specific domain
// change example below to your actual domain
if (email ==~ /.*@example\.com/) {
    // if true, add the default groups to the list of groups returned by the IdP
    connectorGroups.addAll(defaultGroups)
}

return connectorGroups
GROOVY

Assign a default group only upon a successful login via SAML

User should be assigned to a specific group, called groupA, only upon a successful SAML login (i.e. not via the regular sync of User Sync)

Use the following Groovy code for the Groups attribute mapping in the connector:

// mapping the IdP groups
def connectorGroups =  con?.GROUPS?.asStringList() ?: []

// When saml is not empty, this
// is a single user update during SAML SSO login
boolean isLogin = !saml?.isEmpty()

if (isLogin) {
    connectorGroups.addAll("groupA")
}
return connectorGroups
GROOVY