Expression Evolution In Real Life

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 &amp;&amp; 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 &amp;&amp; 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.

Leave a Reply

Your email address will not be published. Required fields are marked *