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")breakcase "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.AttributeOperationEnumimport org.openiam.idm.srvc.auth.dto.Loginimport org.openiam.idm.srvc.continfo.dto.Addressimport org.openiam.idm.srvc.mngsys.domain.ManagedSysEntityimport org.openiam.idm.srvc.mngsys.service.ManagedSystemServiceimport org.openiam.idm.srvc.res.service.ResourceServiceimport org.openiam.provision.dto.ProvisionUserimport org.openiam.idm.srvc.user.dto.UserStatusEnumimport org.openiam.idm.srvc.user.dto.UserAttributeimport java.text.SimpleDateFormatpublic 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 = 1for(String key: line.keySet()) {switch(key) {case "SamAccountName":addAttribute(pUser, "sAMAccountName", line.get("SamAccountName"))breakcase "EmployeeID":if(pUser.employeeId != line.get("EmployeeID")){pUser.employeeId = line.get("EmployeeID")retval = 0}addAttribute(pUser, "employeeID", line.get("EmployeeID"))breakcase "employeeType":if(pUser.employeeType != line.get("employeeType")){pUser.employeeType = line.get("employeeType")retval = 0}addAttribute(pUser, "employeeType", line.get("employeeType"))breakcase "GivenName":if(pUser.firstName != line.get("GivenName")){pUser.firstName = line.get("GivenName")retval = 0}addAttribute(pUser, "givenName", line.get("GivenName"))breakcase "Surname":if(pUser.lastName != line.get("Surname")){pUser.lastName = line.get("Surname")retval = 0}addAttribute(pUser, "sn", line.get("Surname"))breakcase "Initials":if(pUser.middleInit != line.get("Initials")){pUser.middleInit = line.get("Initials")retval = 0}addAttribute(pUser, "initials", line.get("Initials"))breakcase "OtherName":if(pUser.nickname != line.get("OtherName")){pUser.nickname = line.get("OtherName")retval = 0}addAttribute(pUser, "otherName", line.get("OtherName"))breakcase "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"))breakcase "City":if (insertPrimaryAddressItem(pUser, 'city', line.get("City"))) {retval = 0}addAttribute(pUser, "l", line.get("City"))breakcase "State":if (insertPrimaryAddressItem(pUser, 'state', line.get("State"))) {retval = 0}addAttribute(pUser, "st", line.get("State"))breakcase "HomeDirectory":addAttribute(pUser, "homeDirectory", line.get("HomeDirectory"))breakcase "homeDrive":addAttribute(pUser, "homeDrive", line.get("homeDrive"))breakcase "UserPrincipalName":addAttribute(pUser, "userPrincipalName", line.get("UserPrincipalName"))breakcase "DistinguishedName":addAttribute(pUser, "distinguishedName", line.get("DistinguishedName"))breakcase "ObjectGUID":addAttribute(pUser, "objectGUID", line.get("ObjectGUID"))breakcase "extensionAttribute1":addAttribute(pUser, "extensionAttribute1", line.get("extensionAttribute1"))breakcase "extensionAttribute2":addAttribute(pUser, "extensionAttribute2", line.get("extensionAttribute2"))breakcase "Manager"://TODO: Add supervisoraddAttribute(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.ADDpUser.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.ADDexchangeLogin.domainId = currentLogin.domainIdexchangeLogin.login = currentLogin.loginexchangeLogin.managedSysId = exchangeManagedSys.managedSysIdpUser.principalList.add(exchangeLogin)}}}boolean insertPrimaryAddressItem(ProvisionUser pUser, String item, String value) {def address = pUser.addresses?.find { Address a-> a.metadataTypeId == 'PRIMARY_LOCATION' }def isNew = falseif (!address) {isNew = trueaddress = 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" = valuereturn 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.AttributeOperationEnumimport org.openiam.dozer.converter.LoginDozerConverterimport org.openiam.dozer.converter.UserDozerConverterimport org.openiam.idm.searchbeans.LoginSearchBeanimport org.openiam.idm.searchbeans.UserSearchBeanimport org.openiam.idm.srvc.auth.dto.Loginimport org.openiam.idm.srvc.auth.login.LoginDataServiceimport org.openiam.idm.srvc.continfo.dto.Addressimport org.openiam.idm.srvc.mngsys.domain.ManagedSysEntityimport org.openiam.idm.srvc.mngsys.service.ManagedSystemServiceimport org.openiam.idm.srvc.res.service.ResourceServiceimport org.openiam.idm.srvc.user.domain.UserEntityimport org.openiam.idm.srvc.user.dto.Userimport org.openiam.idm.srvc.user.service.UserDataServiceimport org.openiam.idm.srvc.user.ws.UserDataWebServiceimport org.openiam.provision.dto.ProvisionUserimport org.openiam.idm.srvc.user.dto.UserStatusEnumimport org.openiam.idm.srvc.user.dto.UserAttributeimport java.text.SimpleDateFormatpublic 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 = 1for(String key: line.keySet()) {switch(key) {case "SamAccountName":addAttribute(pUser, "sAMAccountName", line.get("SamAccountName"))breakcase "EmployeeID":if(pUser.employeeId != line.get("EmployeeID")){pUser.employeeId = line.get("EmployeeID")retval = 0}addAttribute(pUser, "employeeID", line.get("EmployeeID"))breakcase "employeeType":if(pUser.employeeType != line.get("employeeType")){pUser.employeeType = line.get("employeeType")retval = 0}addAttribute(pUser, "employeeType", line.get("employeeType"))breakcase "GivenName":if(pUser.firstName != line.get("GivenName")){pUser.firstName = line.get("GivenName")retval = 0}addAttribute(pUser, "givenName", line.get("GivenName"))breakcase "Surname":if(pUser.lastName != line.get("Surname")){pUser.lastName = line.get("Surname")retval = 0}addAttribute(pUser, "sn", line.get("Surname"))breakcase "Initials":if(pUser.middleInit != line.get("Initials")){pUser.middleInit = line.get("Initials")retval = 0}addAttribute(pUser, "initials", line.get("Initials"))breakcase "OtherName":if(pUser.nickname != line.get("OtherName")){pUser.nickname = line.get("OtherName")retval = 0}addAttribute(pUser, "otherName", line.get("OtherName"))breakcase "AccountExpires":if (line.get("AccountExpires")) {def d = line.get("AccountExpires") as LongGregorianCalendar win32Epoch = new GregorianCalendar(1601, Calendar.JANUARY, 1);def ms = (d / 10000) + win32Epoch.getTimeInMillis() as Longdef accountExpiresDate = new Date(ms)if (!accountExpiresDate.after(dateFormat.parse('9999-12-31'))) {if (!pUser.lastDate?.equals(accountExpiresDate)) {pUser.lastDate = accountExpiresDateretval = 0}}}addAttribute(pUser, "accountExpires", line.get("AccountExpires"))breakcase "City":if (insertPrimaryAddressItem(pUser, 'city', line.get("City"))) {retval = 0}addAttribute(pUser, "l", line.get("City"))breakcase "State":if (insertPrimaryAddressItem(pUser, 'state', line.get("State"))) {retval = 0}addAttribute(pUser, "st", line.get("State"))breakcase "HomeDirectory":addAttribute(pUser, "homeDirectory", line.get("HomeDirectory"))breakcase "homeDrive":addAttribute(pUser, "homeDrive", line.get("homeDrive"))breakcase "UserPrincipalName":addAttribute(pUser, "userPrincipalName", line.get("UserPrincipalName"))breakcase "DistinguishedName":addAttribute(pUser, "distinguishedName", line.get("DistinguishedName"))breakcase "ObjectGUID":addAttribute(pUser, "objectGUID", line.get("ObjectGUID"))breakcase "extensionAttribute1":addAttribute(pUser, "extensionAttribute1", line.get("extensionAttribute1"))breakcase "extensionAttribute2":addAttribute(pUser, "extensionAttribute2", line.get("extensionAttribute2"))breakcase "Managers":if (line.get("Managers")) {def principalName = line.get("Managers")if (principalName) {def loginManager = context.getBean("loginManager") as LoginDataServicedef 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 anyit.operation = AttributeOperationEnum.DELETE}def userManager = context.getBean("userManager") as UserDataServicedef superior = userManager.getUserDto(login.userId)if (superior) {superior.operation = AttributeOperationEnum.ADDpUser.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.ADDpUser.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.domainIdexchangeLogin.login = currentLogin.loginexchangeLogin.managedSysId = exchangeManagedSys.idpUser.principalList.add(exchangeLogin)}}}boolean insertPrimaryAddressItem(ProvisionUser pUser, String item, String value) {def address = pUser.addresses?.find { Address a-> a.metadataTypeId == 'PRIMARY_LOCATION' }def isNew = falseif (!address) {isNew = trueaddress = 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" = valuereturn 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.BaseAttributeimport org.openiam.idm.srvc.grp.dto.Groupimport org.openiam.idm.srvc.recon.dto.ReconciliationSituationimport org.openiam.idm.srvc.recon.service.PopulationScriptimport org.openiam.idm.srvc.recon.service.ReconciliationObjectAbstractCommandimport org.openiam.provision.dto.ProvisionGroupimport org.openiam.provision.service.AbstractProvisioningServiceimport org.openiam.provision.type.ExtensibleAttributeimport org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.apache.commons.collections.CollectionUtils;import org.apache.commons.lang.StringUtilsimport org.openiam.script.ScriptIntegration;public class DoNothingGroupCommand extends ReconciliationObjectAbstractCommand<Group> {private static final Log log = LogFactory.getLog(DoNothingGroupCommand.class);@Overrideboolean 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.RoleAttributeoutput = "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.MailServiceimport org.openiam.idm.srvc.recon.dto.ReconciliationConfigimport org.openiam.idm.srvc.recon.service.ReconciliationServiceImplimport org.openiam.idm.srvc.res.dto.Resourceimport org.openiam.provision.service.AbstractPrePostExecutorimport org.apache.commons.lang.StringUtilspublic class PrePostExecutorSample extends AbstractPrePostExecutor {@Overrideint 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] {});}}}