Groovy Scripts for Reconciliation

This section provides the overview of how to use Groovy Scripts for Reconciliation. There are examples of things that the user can do via these scripts. This information might come in handy with creating user's own script.

Developing a Script

There are 4 different Groovy scripts for each reconciliation case:

  • IDM (deleted) and Resource (exists)
  • IDM (exists) and Resource (exists)
  • IDM (exists) and Resource (not exists)
  • IDM (not exists) and Resource (exists)

Each Groovy script implements PopulationScript.java (we advise to extend AbstractPopulationScript) interface with the only method:

public int execute(Map<String, String> line, ProvisionUser pUser);

Each record (User) from the target system is represented by the Map of attribute names - value pairs.

The second parameter in the execute function is provUser which we need to fill in with values from the Map. If the User already exists in OpenIAM, it will be pre-filled with values from OpenIAM database. So we can always compare old values with the new ones.

Here is the example from the PopulationScript:

public int execute(Map<String, String> line, ProvisionUser pUser) {
for(String key: line.keySet()) {
switch(key) {
case "EmployeeID":
pUser.employeeId = line.get("EmployeeID")
break
case "employeeType":
pUser.employeeType = line.get("employeeType")
break
....
}
return 0
}

From the AbstractPopulationScript we have access to Spring applicationContext and managedSysId.

All groovy scripts have the "ApplicationContext context" field. And the best practice to initialize Services via ApplicationContext is via spring beans instead webservice communication through network. Read more in Initializing Services via ApplicationContext.

We need to provide a valid provisionUser after reconciliation of each record and return "0" if everything is OK.

Once provUser has been passed to provisioningService, the User is created/updated with the new values from the target system. If we need to change or add items to the provUser collections, we have to set up correct operation properties for items.

For example, here is a snippet that adds a custom attribute to User:

def addAttribute(ProvisionUser pUser, String attributeName, String attributeValue) {
def userAttr = new UserAttribute(attributeName, attributeValue)
if (!pUser.userAttributes.containsKey(attributeName)) {
userAttr.operation = AttributeOperationEnum.ADD
} else {
if (userAttr.value != attributeValue) {
userAttr.operation = AttributeOperationEnum.REPLACE
}
}
pUser.userAttributes.put(attributeName, userAttr)
}

We add an attribute to the userAttributes collection but before we set up operation - AttributeOperationEnum.ADD/REPLACE/DELETE. If we don't specify an operation for item, no changes will be applied.

Scripts Examples

Population sciprts for AD Powershell.

The population script is used for creating a new user or editing the existing user's attributes received from the target system.

import org.openiam.base.AttributeOperationEnum
import org.openiam.idm.srvc.auth.dto.Login
import org.openiam.idm.srvc.continfo.dto.Address
import org.openiam.idm.srvc.mngsys.domain.ManagedSysEntity
import org.openiam.idm.srvc.mngsys.service.ManagedSystemService
import org.openiam.idm.srvc.res.service.ResourceService
import org.openiam.provision.dto.ProvisionUser
import org.openiam.idm.srvc.user.dto.UserStatusEnum
import org.openiam.idm.srvc.user.dto.UserAttribute
import java.text.SimpleDateFormat
public class ADPopulationScript extends org.openiam.idm.srvc.recon.service.AbstractPopulationScript {
def dateFormat = new SimpleDateFormat("yyyy-MM-dd")
public int execute(Map<String, String> line, ProvisionUser pUser) {
int retval = 1
for(String key: line.keySet()) {
switch(key) {
case "SamAccountName":
addAttribute(pUser, "sAMAccountName", line.get("SamAccountName"))
break
case "EmployeeID":
if(pUser.employeeId != line.get("EmployeeID")){
pUser.employeeId = line.get("EmployeeID")
retval = 0
}
addAttribute(pUser, "employeeID", line.get("EmployeeID"))
break
case "employeeType":
if(pUser.employeeType != line.get("employeeType")){
pUser.employeeType = line.get("employeeType")
retval = 0
}
addAttribute(pUser, "employeeType", line.get("employeeType"))
break
case "GivenName":
if(pUser.firstName != line.get("GivenName")){
pUser.firstName = line.get("GivenName")
retval = 0
}
addAttribute(pUser, "givenName", line.get("GivenName"))
break
case "Surname":
if(pUser.lastName != line.get("Surname")){
pUser.lastName = line.get("Surname")
retval = 0
}
addAttribute(pUser, "sn", line.get("Surname"))
break
case "Initials":
if(pUser.middleInit != line.get("Initials")){
pUser.middleInit = line.get("Initials")
retval = 0
}
addAttribute(pUser, "initials", line.get("Initials"))
break
case "OtherName":
if(pUser.nickname != line.get("OtherName")){
pUser.nickname = line.get("OtherName")
retval = 0
}
addAttribute(pUser, "otherName", line.get("OtherName"))
break
case "AccountExpires":
//TODO: What's the date format?
/*
if(line.get("AccountExpires") && pUser.lastDate != line.get("AccountExpires")) {
pUser.lastDate = new Date(line.get("AccountExpires") as Long)
retval = 0
}
*/
addAttribute(pUser, "accountExpires", line.get("AccountExpires"))
break
case "City":
if (insertPrimaryAddressItem(pUser, 'city', line.get("City"))) {
retval = 0
}
addAttribute(pUser, "l", line.get("City"))
break
case "State":
if (insertPrimaryAddressItem(pUser, 'state', line.get("State"))) {
retval = 0
}
addAttribute(pUser, "st", line.get("State"))
break
case "HomeDirectory":
addAttribute(pUser, "homeDirectory", line.get("HomeDirectory"))
break
case "homeDrive":
addAttribute(pUser, "homeDrive", line.get("homeDrive"))
break
case "UserPrincipalName":
addAttribute(pUser, "userPrincipalName", line.get("UserPrincipalName"))
break
case "DistinguishedName":
addAttribute(pUser, "distinguishedName", line.get("DistinguishedName"))
break
case "ObjectGUID":
addAttribute(pUser, "objectGUID", line.get("ObjectGUID"))
break
case "extensionAttribute1":
addAttribute(pUser, "extensionAttribute1", line.get("extensionAttribute1"))
break
case "extensionAttribute2":
addAttribute(pUser, "extensionAttribute2", line.get("extensionAttribute2"))
break
case "Manager":
//TODO: Add supervisor
addAttribute(pUser, "manager", line.get("Manager"))
break
}
}
def managedSystemService = context.getBean(ManagedSystemService.class)
def resourceService = context.getBean(ResourceService.class)
def currentManagedSys = managedSystemService.getManagedSysById(managedSysId)
def currentResource = resourceService.getResourceDTO(currentManagedSys.resourceId)
if (!pUser?.resources?.find { it.resourceId == currentResource.resourceId }) {
currentResource.operation = AttributeOperationEnum.ADD
pUser.resources.add(currentResource)
}
//set status to active: IMPORTANT!!!!
if (!pUser.status) {
pUser.status = UserStatusEnum.PENDING_INITIAL_LOGIN
}
if (!pUser.metadataTypeId) {
pUser.metadataTypeId = "Contractor"
}
//addExchangePrincipal(pUser, currentManagedSys, managedSystemService)
return retval
}
def addExchangePrincipal(ProvisionUser pUser, ManagedSysEntity currentManagedSys, ManagedSystemService managedSystemService) {
def exchangeManagedSys = managedSystemService.getManagedSysByName('MTS-WIN02-POWERSHELL-EXCHANGE2010')
def found = pUser.principalList.find {Login l-> l.managedSysId == exchangeManagedSys.managedSysId }
if (!found) {
def currentLogin = pUser.principalList.find {Login l-> l.managedSysId == currentManagedSys.managedSysId }
if (currentLogin) {
def exchangeLogin = new Login()
exchangeLogin.operation = AttributeOperationEnum.ADD
exchangeLogin.domainId = currentLogin.domainId
exchangeLogin.login = currentLogin.login
exchangeLogin.managedSysId = exchangeManagedSys.managedSysId
pUser.principalList.add(exchangeLogin)
}
}
}
boolean insertPrimaryAddressItem(ProvisionUser pUser, String item, String value) {
def address = pUser.addresses?.find { Address a-> a.metadataTypeId == 'PRIMARY_LOCATION' }
def isNew = false
if (!address) {
isNew = true
address = new Address()
address.metadataTypeId = 'PRIMARY_LOCATION'
pUser.addresses.add(address)
}
if (address."$item" != value) {
if (address.operation == AttributeOperationEnum.NO_CHANGE) {
address.operation = isNew ? AttributeOperationEnum.ADD : AttributeOperationEnum.REPLACE
}
address."$item" = value
return true
}
return false
}
def addAttribute(ProvisionUser pUser, String attributeName, String attributeValue) {
def userAttr = new UserAttribute(attributeName, attributeValue)
if (!pUser.userAttributes.containsKey(attributeName)) {
userAttr.operation = AttributeOperationEnum.ADD
} else {
if (userAttr.value != attributeValue) {
userAttr.operation = AttributeOperationEnum.REPLACE
}
}
pUser.userAttributes.put(attributeName, userAttr)
}
}

For different situations one can create separate scripts. Or one can have one script for all situations like this one.

import org.openiam.base.AttributeOperationEnum
import org.openiam.dozer.converter.LoginDozerConverter
import org.openiam.dozer.converter.UserDozerConverter
import org.openiam.idm.searchbeans.LoginSearchBean
import org.openiam.idm.searchbeans.UserSearchBean
import org.openiam.idm.srvc.auth.dto.Login
import org.openiam.idm.srvc.auth.login.LoginDataService
import org.openiam.idm.srvc.continfo.dto.Address
import org.openiam.idm.srvc.mngsys.domain.ManagedSysEntity
import org.openiam.idm.srvc.mngsys.service.ManagedSystemService
import org.openiam.idm.srvc.res.service.ResourceService
import org.openiam.idm.srvc.user.domain.UserEntity
import org.openiam.idm.srvc.user.dto.User
import org.openiam.idm.srvc.user.service.UserDataService
import org.openiam.idm.srvc.user.ws.UserDataWebService
import org.openiam.provision.dto.ProvisionUser
import org.openiam.idm.srvc.user.dto.UserStatusEnum
import org.openiam.idm.srvc.user.dto.UserAttribute
import java.text.SimpleDateFormat
public class PowershellADPopulationScript extends org.openiam.idm.srvc.recon.service.AbstractPopulationScript {
def dateFormat = new SimpleDateFormat("yyyy-MM-dd")
public int execute(Map<String, String> line, ProvisionUser pUser) {
int retval = 1
for(String key: line.keySet()) {
switch(key) {
case "SamAccountName":
addAttribute(pUser, "sAMAccountName", line.get("SamAccountName"))
break
case "EmployeeID":
if(pUser.employeeId != line.get("EmployeeID")){
pUser.employeeId = line.get("EmployeeID")
retval = 0
}
addAttribute(pUser, "employeeID", line.get("EmployeeID"))
break
case "employeeType":
if(pUser.employeeType != line.get("employeeType")){
pUser.employeeType = line.get("employeeType")
retval = 0
}
addAttribute(pUser, "employeeType", line.get("employeeType"))
break
case "GivenName":
if(pUser.firstName != line.get("GivenName")){
pUser.firstName = line.get("GivenName")
retval = 0
}
addAttribute(pUser, "givenName", line.get("GivenName"))
break
case "Surname":
if(pUser.lastName != line.get("Surname")){
pUser.lastName = line.get("Surname")
retval = 0
}
addAttribute(pUser, "sn", line.get("Surname"))
break
case "Initials":
if(pUser.middleInit != line.get("Initials")){
pUser.middleInit = line.get("Initials")
retval = 0
}
addAttribute(pUser, "initials", line.get("Initials"))
break
case "OtherName":
if(pUser.nickname != line.get("OtherName")){
pUser.nickname = line.get("OtherName")
retval = 0
}
addAttribute(pUser, "otherName", line.get("OtherName"))
break
case "AccountExpires":
if (line.get("AccountExpires")) {
def d = line.get("AccountExpires") as Long
GregorianCalendar win32Epoch = new GregorianCalendar(1601, Calendar.JANUARY, 1);
def ms = (d / 10000) + win32Epoch.getTimeInMillis() as Long
def accountExpiresDate = new Date(ms)
if (!accountExpiresDate.after(dateFormat.parse('9999-12-31'))) {
if (!pUser.lastDate?.equals(accountExpiresDate)) {
pUser.lastDate = accountExpiresDate
retval = 0
}
}
}
addAttribute(pUser, "accountExpires", line.get("AccountExpires"))
break
case "City":
if (insertPrimaryAddressItem(pUser, 'city', line.get("City"))) {
retval = 0
}
addAttribute(pUser, "l", line.get("City"))
break
case "State":
if (insertPrimaryAddressItem(pUser, 'state', line.get("State"))) {
retval = 0
}
addAttribute(pUser, "st", line.get("State"))
break
case "HomeDirectory":
addAttribute(pUser, "homeDirectory", line.get("HomeDirectory"))
break
case "homeDrive":
addAttribute(pUser, "homeDrive", line.get("homeDrive"))
break
case "UserPrincipalName":
addAttribute(pUser, "userPrincipalName", line.get("UserPrincipalName"))
break
case "DistinguishedName":
addAttribute(pUser, "distinguishedName", line.get("DistinguishedName"))
break
case "ObjectGUID":
addAttribute(pUser, "objectGUID", line.get("ObjectGUID"))
break
case "extensionAttribute1":
addAttribute(pUser, "extensionAttribute1", line.get("extensionAttribute1"))
break
case "extensionAttribute2":
addAttribute(pUser, "extensionAttribute2", line.get("extensionAttribute2"))
break
case "Managers":
if (line.get("Managers")) {
def principalName = line.get("Managers")
if (principalName) {
def loginManager = context.getBean("loginManager") as LoginDataService
def principals = loginManager.getLoginDetailsByManagedSys(principalName, managedSysId)
if (principals) {
def login = principals.get(0)
if (!pUser.superiors?.find {it.userId == login.userId}) {
pUser.superiors?.each { // remove previous managers if any
it.operation = AttributeOperationEnum.DELETE
}
def userManager = context.getBean("userManager") as UserDataService
def superior = userManager.getUserDto(login.userId)
if (superior) {
superior.operation = AttributeOperationEnum.ADD
pUser.addSuperior(superior)
}
}
}
}
}
addAttribute(pUser, "managers", line.get("Managers"))
break
}
}
def managedSystemService = context.getBean(ManagedSystemService.class)
def resourceService = context.getBean(ResourceService.class)
def currentManagedSys = managedSystemService.getManagedSysById(managedSysId)
def currentResource = resourceService.getResourceDTO(currentManagedSys.resourceId)
if (!pUser?.resources?.find { it.resourceId == currentResource.resourceId }) {
currentResource.operation = AttributeOperationEnum.ADD
pUser.resources.add(currentResource)
}
//set status to active: IMPORTANT!!!!
if (!pUser.status) {
pUser.status = UserStatusEnum.PENDING_INITIAL_LOGIN
}
if (!pUser.metadataTypeId) {
pUser.metadataTypeId = "Contractor"
}
//addExchangePrincipal(pUser, currentManagedSys, managedSystemService)
return retval
}
def addExchangePrincipal(ProvisionUser pUser, ManagedSysEntity currentManagedSys, ManagedSystemService managedSystemService) {
def exchangeManagedSys = managedSystemService.getManagedSysByName('MTS-WIN02-POWERSHELL-EXCHANGE2010')
def found = pUser.principalList.find {Login l-> l.managedSysId == exchangeManagedSys.id }
if (!found) {
def currentLogin = pUser.principalList.find {Login l-> l.managedSysId == currentManagedSys.id }
if (currentLogin) {
def exchangeLogin = new Login()
exchangeLogin.operation = AttributeOperationEnum.ADD
// exchangeLogin.domainId = currentLogin.domainId
exchangeLogin.login = currentLogin.login
exchangeLogin.managedSysId = exchangeManagedSys.id
pUser.principalList.add(exchangeLogin)
}
}
}
boolean insertPrimaryAddressItem(ProvisionUser pUser, String item, String value) {
def address = pUser.addresses?.find { Address a-> a.metadataTypeId == 'PRIMARY_LOCATION' }
def isNew = false
if (!address) {
isNew = true
address = new Address()
address.metadataTypeId = 'PRIMARY_LOCATION'
pUser.addresses.add(address)
}
if (address."$item" != value) {
if (address.operation == AttributeOperationEnum.NO_CHANGE) {
address.operation = isNew ? AttributeOperationEnum.ADD : AttributeOperationEnum.REPLACE
}
address."$item" = value
return true
}
return false
}
def addAttribute(ProvisionUser pUser, String attributeName, String attributeValue) {
def userAttr = new UserAttribute(attributeName, attributeValue)
if (!pUser.userAttributes.containsKey(attributeName)) {
userAttr.operation = AttributeOperationEnum.ADD
} else {
if (userAttr.value != attributeValue) {
userAttr.operation = AttributeOperationEnum.REPLACE
}
}
pUser.userAttributes.put(attributeName, userAttr)
}
}

Do Nothing Script

import org.openiam.base.BaseAttribute
import org.openiam.idm.srvc.grp.dto.Group
import org.openiam.idm.srvc.recon.dto.ReconciliationSituation
import org.openiam.idm.srvc.recon.service.PopulationScript
import org.openiam.idm.srvc.recon.service.ReconciliationObjectAbstractCommand
import org.openiam.provision.dto.ProvisionGroup
import org.openiam.provision.service.AbstractProvisioningService
import org.openiam.provision.type.ExtensibleAttribute
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils
import org.openiam.script.ScriptIntegration;
public class DoNothingGroupCommand extends ReconciliationObjectAbstractCommand<Group> {
private static final Log log = LogFactory.getLog(DoNothingGroupCommand.class);
@Override
boolean execute(ReconciliationSituation config, String principal, String mSysID, Group group, List<ExtensibleAttribute> attributes) {
log.debug("Entering DoNothingCommand Groovy");
log.debug("Do nothing for Group :" + principal);
ProvisionGroup pGroup = new ProvisionGroup(group);
pGroup.setSrcSystemId(mSysID);
if(StringUtils.isNotEmpty(config.getScript())){
try {
Map<String, String> line = new HashMap<String, String>();
for (ExtensibleAttribute attr : attributes) {
if (attr.getValue() != null) {
line.put(attr.getName(), attr.getValue());
} else if (attr.getAttributeContainer() != null &&
CollectionUtils.isNotEmpty(attr.getAttributeContainer().getAttributeList()) &&
line.get(attr.getName()) == null) {
StringBuilder value = new StringBuilder();
boolean isFirst = true;
for (BaseAttribute ba : attr.getAttributeContainer().getAttributeList()) {
if (!isFirst) {
value.append('^');
} else {
isFirst = false;
}
value.append(ba.getValue());
}
line.put(attr.getName(), value.toString());
}
}
Map<String, Object> bindingMap = new HashMap<String, Object>();
bindingMap.put(AbstractProvisioningService.TARGET_SYS_MANAGED_SYS_ID, mSysID);
ScriptIntegration scriptRunner = context.getBean("configurableGroovyScriptEngine");
PopulationScript<ProvisionGroup> script = (PopulationScript<ProvisionGroup>) scriptRunner.instantiateClass(bindingMap, config.getScript());
int retval = script.execute(line, pGroup);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
return true;
}
}

Search Query Script

A Groovy script to extract data from target system for reconciliation. At the output we obtain a search string (used in LDAP/AD).

def sf = binding.hasVariable("searchFilter")? searchFilter : "{Name -like '*'}"
def us = binding.hasVariable("updatedSince")? updatedSince : ""
//import org.openiam.idm.srvc.role.dto.RoleAttribute
output = "Get-ADUser -Filter ${sf} -Properties *" as String
/*if(role != null) {
Set<RoleAttribute> attributes = role.getRoleAttributes();
for(RoleAttribute attr : attributes) {
if("AD_OU".equals(attr.getName())) {
output="Get-ADUser -Filter {Name -like '*'} -SearchBase '"+attr.getValue()+"' -Properties * -searchscope 1";
break;
};
}
} else if(group != null) {
output="Get-ADGroupMember -Identity "+group.name;
}*/

Pre-/PostExecutor Script Example

import org.openiam.idm.srvc.msg.service.MailService
import org.openiam.idm.srvc.recon.dto.ReconciliationConfig
import org.openiam.idm.srvc.recon.service.ReconciliationServiceImpl
import org.openiam.idm.srvc.res.dto.Resource
import org.openiam.provision.service.AbstractPrePostExecutor
import org.apache.commons.lang.StringUtils
public class PrePostExecutorSample extends AbstractPrePostExecutor {
@Override
int execute(Map<String, Object> bindingMap) {
ReconciliationConfig config = (ReconciliationConfig)bindingMap.get(ReconciliationServiceImpl.RECONCILIATION_CONFIG)
this.sendMail(config);
return 0;
}
private void sendMail(ReconciliationConfig config) {
StringBuilder message = new StringBuilder();
if (!StringUtils.isEmpty(config.getNotificationEmailAddress())) {
message.append("Resource: " + config.managedSysId + ".\n");
message.append("Uploaded CSV file: " + config.managedSysId + ".csv was successfully reconciled.\n");
MailService mailService = context.getBean("mailService");
mailService.sendEmails(null, new String[1] { config.getNotificationEmailAddress() }, null, null,
"CSVConnector", message.toString(), false, new String[0] {});
}
}
}