When customizing midPoint for deployment, you will need expressions sooner or later. In this post, I would like to present one possible way of the email expression evolution. Let’s suppose that our target system has an account attribute “InternetAddress” to store user’s e-mail address.
Goal
- generate e-mail address based on user’s givenName and familyName attributes.
The goal is very simple (and very vague, which is, by the way, the reason why I want to write this blog post). Just take two attributes and concat them in some way.
Oh yes. The email address mapping could then look like this (in Groovy):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | < attribute > < ref >ri:InternetAddress</ ref > < displayName >Internet Address</ displayName > < outbound > < source > < name >givenName</ name > < path >$user/givenName</ path > </ source > < source > < name >familyName</ name > < path >$user/familyName</ path > </ source > < expression > < script > < code > givenName + '.' + familyName + '@example.com' </ code > </ script > </ expression > </ outbound > </ attribute > |
In the XML fragment above, we have defined an outbound mapping for attribute “InternetAddress”. The source attributes for this mapping are user’s givenName and familyName. The mapping will evaluate whenever any of the source attribute value changes.
Let’s test this mapping a little.
Example 1:
- $user/givenName: John
- $user/familyName: Smith
- InternetAddress: John.Smith@example.com
Example 2 (see how upper/lower case is copied):
- $user/givenName: john
- $user/familyName: sMith
- InternetAddress: john.sMith@example.com
Example 3 (see how national characters are copied):
- $user/givenName: Tomáš
- $user/familyName: Čučoriedka
- InternetAddress: Tomáš.Čučoriedka@example.com
As you can see, the e-mail expression is very simple, yet wrong. The givenName/familyName attribute values are copied from midPoint user, leaving the case as is. In addition, e-mail address should not contain characters except the first 127 ASCII characters. So we need to fix the Groovy expression by using “basic.stringify” function to convert givenName/familyName PolyString attributes to their String form, and “basic.norm” function to normalize them (replace national characters by its ASCII representation, lowercase and compress spaces):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | < attribute > < ref >ri:InternetAddress</ ref > < displayName >Internet Address</ displayName > < outbound > < source > < name >givenName</ name > < path >$user/givenName</ path > </ source > < source > < name >familyName</ name > < path >$user/familyName</ path > </ source > < expression > < script > < code > basic.norm(basic.stringify(givenName)) + '.' + basic.norm(basic.stringify(familyName)) + '@example.com' </ code > </ script > </ expression > </ outbound > </ attribute > |
The output is now a lot more interesting:
Example 4 (national characters stripped, all lowercased):
- givenName: Tomáš
- familyName: Čučoriedka
- InternetAddress: tomas.cucoriedka@example.com
But, there are still potential problems:
Example 5 (space-separated givenName/familyName):
- givenName: Tomáš Boris
- familyName: Čučoriedka Papek
- InternetAddress: tomas boris.cucoriedka papek@example.com
As spaces are illegal in e-mail address, we need another fix:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | < attribute > < ref >ri:InternetAddress</ ref > < displayName >Internet Address</ displayName > < outbound > < source > < name >givenName</ name > < path >$user/givenName</ path > </ source > < source > < name >familyName</ name > < path >$user/familyName</ path > </ source > < expression > < script > < code > basic.norm(basic.stringify(givenName))?.replaceAll(' ', '') + '.' + basic.norm(basic.stringify(familyName))?.replaceAll(' ', '') + '@example.com' </ code > </ script > </ expression > </ outbound > </ attribute > |
The “?.” Groovy operator will only call “replaceAll” method if the object is not null, preventing Null Pointer Exception. Now it should work like this:
Example 6:
- givenName: Tomáš
- familyName: Čučoriedka
- InternetAddress: tomas.cucoriedka@example.com
Example 7:
- givenName: Tomáš Boris
- familyName: Čučoriedka Papek
- InternetAddress: tomasboris.cucoriedkapapek@example.com
Of course you could replace the space by ‘.’ character instead of the empty string in the “replaceAll” method above.
But there are still some potential problems The givenName attribute may be empty:
Example 8:
- givenName:
- familyName: Čučoriedka
- InternetAddress: .cucoriedka@example.com
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | < attribute > < ref >ri:InternetAddress</ ref > < displayName >Internet Address</ displayName > < outbound > < source > < name >givenName</ name > < path >$user/givenName</ path > </ source > < source > < name >familyName</ name > < path >$user/familyName</ path > </ source > < expression > < script > < code > if (givenName && familyName) dot = '.' else dot = '' basic.norm(basic.stringify(givenName))?.replaceAll(' ', '') + dot + basic.norm(basic.stringify(familyName))?.replaceAll(' ', '') + '@example.com' </ code > </ script > </ expression > </ outbound > </ attribute > |
The above expression will not use the ‘.’ character if at least one of the givenName/familyName attributes is null. (Although “familyName” cannot be empty in midPoint.)
Example 9:
- givenName:
- familyName: Čučoriedka
- InternetAddress: cucoriedka@example.com
The latest excercise is to capitalize each value of givenName/familyName to make more elegant e-mail addresses:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | < attribute > < ref >ri:InternetAddress</ ref > < displayName >Internet Address</ displayName > < outbound > < source > < name >givenName</ name > < path >$user/givenName</ path > </ source > < source > < name >familyName</ name > < path >$user/familyName</ path > </ source > < expression > < script > < code > if (givenName && familyName) dot = '.' else dot = '' basic.norm(basic.stringify(givenName))?.replaceAll(/w+/, { it[0].toUpperCase() + ((it.size() > 1) ? it[1..-1] : '') } )?.replace(' ', '') + dot + basic.norm(basic.stringify(familyName))?.replaceAll(/w+/, { it[0].toUpperCase() + ((it.size() > 1) ? it[1..-1] : '') } )?.replace(' ', '') + '@example.com' </ code > </ script > </ expression > </ outbound > </ attribute > |
Well, the mapping produces more elegant e-mail addresses for the price of being a lot geekish (at least for me, as this was Groovy specialty). Be sure to use “replaceAll” method on already normalized string, or you will loose the capital letters
But the results are now:
Example 10:
- givenName:
- familyName: Čučoriedka
- InternetAddress: Cucoriedka@example.com
Example 11:
- givenName: Tomáš
- familyName: Čučoriedka
- InternetAddress: Tomas.Cucoriedka@example.com
Example 12:
- givenName: Tomáš Boris
- familyName: Čučoriedka Papek
- InternetAddress: TomasBoris.CucoriedkaPapek@example.com
Example 13:
- givenName: Tomáš
- familyName: Van Der Berghoffen
- InternetAddress: Tomas.VanDerBerghoffen@example.com
So, in the end, we have achieved:
Goals:
- generate e-mail address based on user’s givenName and familyName attributes.
- use only lower 127 characters of ASCII character set
- avoid using leading dot/trailing dot for users without givenName/familyName value
- capitalize each value of givenName/familyName for users with space-separated values of these attributes
- avoid using spaces in e-mail address
In the next blog posts, we will demonstrate how to generate unique e-mail address and how to automatically store it in midPoint.