Some time ago we’ve discussed how to generate e-mail address for resource target attribute. But almost everytime you would need to store user’s e-mail address in midPoint to push it anywhere you need. So we need to enter the value in midPoint (we have fancy “emailAddress” attribute handy) and let the resource schema handling mappings do its best.
But why should we always enter the e-mail address manually when we can do it automatically – and we can do it in midPoint!
All we need is:
- Object template for users (UserType) set as default user template in System Configuration
- One mapping to generate the address
- One mapping to validate the address to be unique in midPoint
Create a new object template and add the following mapping:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | < objectTemplate < name >My User Template</ name > < mapping > < name >My User Template: Generate proposed emailAddress: value givenName.familyName@example.com</ name > < description >For Employees only.</ description > < source > < path >$user/familyName</ path > </ source > < source > < path >$user/givenName</ path > </ source > < source > < path >$user/employeeType</ path > </ source > < expression > < script > < code > // Use the following if you don't want to capitalize the Given and Family names in e-mail address // Example: ivan.noris@example.com // tmp = basic.norm(basic.stringify(givenName)) + '.' + basic.norm(basic.stringify(familyName)) + '@example.com' // Use the following if you do want to capitalize the Given and Family names in e-mail address // Example: Ivan.Noris@example.com tmp = basic.norm(basic.stringify(givenName))?.replaceAll(/w+/, { it[0].toUpperCase() + ((it.size() > 1) ? it[1..-1] : '') } ) + '.' + basic.norm(basic.stringify(familyName))?.replaceAll(/w+/, { it[0].toUpperCase() + ((it.size() > 1) ? it[1..-1] : '') } ) + '@example.com' // Replace all spaces by empty string (use anything you want for replacement) return tmp?.replaceAll(' ', '') </ code > </ script > </ expression > < target > < path >$user/emailAddress</ path > </ target > < condition > < script > < code >employeeType == 'EMPLOYEE'</ code > </ script > </ condition > </ mapping > </ objectTemplate > |
The e-mail address generator is very simple. It will generate a value for “emailAddress” user attribute by taking user’s givenName and familyName attributes and concatenate them with “@example.com” domain. See the inline comments in the Groovy expressions to see how the value is being constructed.
To avoid generating e-mail addresses for system users, this mapping has a condition, so only users where employeeType attribute is set to “EMPLOYEE” will match.
You can try to save the object template, import it to midPoint and make it default for Users: go to Configuration tab, click Basic, then click the “+” button near the “Object Policies” section:
- Select “UserType”
- Search for your “My User Template” object
- Save and then Save the page
Of course, you can do it quickly also by editing “System Configuration” object and adding:
1 2 3 4 | < defaultObjectPolicyConfiguration > < type >UserType</ type > < objectTemplateRef oid = "......." /> <!-- Put OID of "My User Template" here --> </ defaultObjectPolicyConfiguration > |
You can try to create a new user in midPoint or change existing user givenName/familyName to see that the address is being generated. Just don’t forget to set the employeeType
Now we can attempt to make the address unique. Using Unique property value HOWTO it’s quite simple.
Add another mapping to “My User Template”:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | < mapping > < name >My Object Template: Validate emailAddress uniqueness</ name > < description >For all User objects. Regardless of type. If you don't need uniqueness, to not enter the address.</ description > < source > < path >$user/emailAddress</ path > </ source > < expression > < script > < code > boolean isNew = com.evolveum.midpoint.model.common.expression.script.ScriptExpressionEvaluationContext.getThreadLocal().isEvaluateNew() if (isNew && emailAddress != null) { // last parameter of getObjectsInConflictOnPropertyValue: whether you want to see all conflicts (true) or the first one (false) List conflicts = midpoint.getObjectsInConflictOnPropertyValue(user, "emailAddress", emailAddress, "stringIgnoreCase", true) if (!conflicts.isEmpty()) { throw new com.evolveum.midpoint.model.api.PolicyViolationException("Email address " + emailAddress + " is already used as primary address; conflicting objects = " + conflicts); } } </ code > </ script > </ expression > </ mapping > |
Yes, this mapping has no target. In this case it’ correct, it’s by design. It will not store the value anywhere, just throw an exception if the address is not unique. This mapping will ensure that for each added e-mail address (boolean isNew …) midPoint will try to find an existing user with the same (case-insensitive) e-mail address. If there is another user (not the currently checked), the e-mail address is not unique.
Please note that the previous mapping is lacking the condition for employeeType. This is also by design; I simply assume that if I put e-mail address value to midPoint, I want to validate it for uniqueness even if the value was not generated. Adding the condition is just a simple copy/paste work.
You can create another user with the same givenName/familyName (and employeeType) attributes and see for yourselves. You should not be able to save the second user, because midPoint will generate the emailAddress attribute according to givenName and familyName attributes, and then the validation will fail. User will not be saved.
I agree this is quite simple. Maybe too simple. You can argue that you need multi-value e-mail address, both generated and manually entered; that you need to maintain the old e-mail address when user changes his/her familyName attribute… I say, everything is possible in midPoint, and just by changing the configuration. No coding needed. (Except Groovy expressions, of course