Important Update Effective February 1, 2024!
Due to recent changes in Jira and Confluence, we've made the tough decision to discontinue the OpenID Connect (OIDC)/OAuth app and no longer provide new versions for the newest Jira/Confluence releases as of January 31, 2024.
This is due to some necessary components no longer shipping with Jira/Confluence, which would require some extensive rewrites of the OIDC App.
Important Update! This app will be discontinued soon!
Due to recent changes in Jira, which no longer ships with some components required for our Read Receipts app to run, we've made the tough decision to discontinue the app, as of Februar 5, 2025.
Important Update! This app will be discontinued soon!
We've made the tough business decision to discontinue the app, as of January 11, 2025.
Transformations with Groovy
Starting with version SAML SingleSignOn 4.0.0 and UserSync 2.0, attributes can be transformed using Groovy-scripts.
About Groovy: https://groovy-lang.org/documentation.html
The Groovy-script contains a variable mapping
. This variable is a Map with strings as keys and string-lists as values. In SAML, it contains the attributes from the SAML-assertion sent by the IdP, in UserSync the attributes retrieved by the connector.
After the script is run, the values from the key groovyResult
is taken for the mapped attribute.
Scripts can be configured for any attribute. Theses scripts are independent from each other, each of them gets a fresh copy of the attribute-map.
The NameID-attribute from the SAML-assertion is accessible under mapping.ATTR_NAMEID
.
Each script must finish within 1 second, otherwise its cancelled and a TransformationFailedException
is thrown.
The SLF4J-logger de.resolution.retransform.impl.transformers.groovy.GroovyTransformerScript
is available as logger
.
Examples
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.contains("guests")) {
// Write the static value "guestuser" to groovyResult.
// [] are required because the values are lists
mapping.groovyResult = ["guestuser"]
} else {
// Otherwise use the value from the Name ID
// no [] needed because the value in the mapping is already a list
mapping.groovyResult = mapping.ATTR_NAMEID
}
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 used an empty string
def lastName = mapping.lastName[0] ? mapping.lastName[0].toUpperCase() : ""
// Groovy GStrings allow variable substition with $variable or ${expression}
// toString() is required here because GStrings must be explicitly turned into Java-Strings.
def fullName = "${mapping.firstName[0]} $lastName".toString()
// 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
logger.warn("########## This is the new full name: {}",fullName)
// Wrapping the value into a list
mapping.groovyResult = [fullName]
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"]
},
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");
}
logger.warn("Groups are {}", groups)
mapping.groovyResult = groups
Handle Groups Not Sent As Multivalue Attribute in SAML Response
def trafoMap = ["20368564" : "stash-users", "10096280" : "other-group"] // list of key/ value to replace group names after splitting
def splitted = mapping.Groups[0].split(",") // read "Groups" attribute from SAML Response split by comma
mapping.groovyResult = 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
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)
}
mapping.groovyResult = groups
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)
}
// returns all the groups
mapping.groovyResult = groups
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 write the value of the nameID to groovyResult
// [] are required because the values are lists
mapping.groovyResult = mapping.ATTR_NAMEID
} else {
// Otherwise drop the authentication
// no [] needed because the value in the mapping is already a list
mapping.drop = true
}
} else {
// if email-attribute and if it is empty drop the authentication and log a warning message
mapping.drop = true
logger.warn("Dropping User authentication due to missing SAML attribute email")
For using this script you need to run version 4.0.8 and later as the drop action is not going to work consistently in former versions.
Special Case for SSSOSUP 7515
//// FOR TESTING IN https://groovyconsole.appspot.com, remove this part
def mapping = [
//"mail": ["mail@example.com"],
//mail : [],
mail: ["bla@fasel.onmicrosoft.com"],
"extension_372c480931744b6c933a94ee08b563b2_extensionAttribute15" : ["ext@example.com"],
"userPrincipalName" : ["upm@example.com"]
]
//////
// ?. is a safe dereference, if mail is not present just return null instead of throwing an error
def mail = mapping?.mail?.getAt(0)
def ext = mapping?.extension_372c480931744b6c933a94ee08b563b2_extensionAttribute15?.getAt(0)
def upn = mapping?.userPrincipalName?.getAt(0)
// use userPrincipalName if nothing else is set
if(!mail && !ext && upn) {
mapping.groovyResult = [upn]
// If extension... is set and mail is not set or matches onmicrosoft.com use that value
} else if(ext && (!mail || mail =~ /onmicrosoft.com/) ) {
mapping.groovyResult = [ext]
// If extension is not set but mail matches onmicrosoft.com use special replacements
} else if(!ext && mail =~ /onmicrosoft.com/) {
switch (mail) {
case ~/abc@def.onmicrosoft.com/:
mapping.groovyResult = ["abc@def.com"]
break
case ~/bla@fasel.onmicrosoft.com/:
mapping.groovyResult = ["bla@fasel.com"]
break
default:
mapping.groovyResult = ["default@example.com"]
}
} else if (mail) {
mapping.groovyResult = [mail]
} else {
// drop the user if nothing matches
mapping.drop = true
}
//////// for testing, remove this
return mapping