Finding Users with Groovy Script in OpenIAM

OPENIAM
Published: December 16, 2022
Last Updated: January 12, 2023

This is part of a series of posts about OpenIAM. OpenIAM is an Identity Management platform that helps organisations manage the digital identities of its workforce and customers.

Check out the OpenIAM category for other posts.

Groovy scripts allow you to carry out complex logic, as well as add new functionality to OpenIAM. Though there are a lot of out of the box examples, these scripts should only really be considered as examples. Ideally you should write your own logic, but unfortunately this has a rather steep learning curve. In this post I'm going to show you the fundamentals of finding users within a groovy script in OpenIAM.

Before we get into any code, we need to understand a little bit of OpenIAM's internals.

OpenIAM consists of a number of microservices that are interconnected via a message bus (RabbitMQ). Each microservice use two queues, one to receive requests, and one to send responses. When we need to request something from another part of the stack, a message is placed into the receive queue for the microservice we need to interact with. The microservice watches the receive queue for messages and works through them First-in-First-out (FIFO). Once the microservice has generated a response, it posts the message onto the response queue. The requesting service can then pick up the response and carry on, as long as the timeout for the request has not yet passed.

Request
Response
Receive
Response
Receive
Response
Groovy Script
RabbitMQService
Queue
Microservice
OpenIAM's microservice communication flow

Now we know how OpenIAM's microservice architecture works, we know how to request data within our groovy scripts. Let's dive a little deeper to understand exactly how we call other microservices from a groovy script. OpenIAM has been written on top of the Spring Framework which has an Inversion of Control (IoC) container. The IoC container is used for dependency injection. We can call upon the IoC container to get classes and services that have already been instantiated, which OpenIAM does at boot. Imagine you want to perform a database query, without IoC you would need to create a new instance of the database class and provide it with configuration such as hostname, username, password and database name each time you wanted to talk to the database. With IoC, the database class is created at boot or the first time it's needed and put into the IoC container. Instead of creating a new class each time, you get it from the IoC container where it has already been created and configured (instantiated). OpenIAM instantiates a large number of classes and services when it starts, this includes a number of RabbitMQ services such as the UserRabbitMQService which we will be using to send and receive messages to the appropriate microservice. These RabbitMQServices are wrappers around the RabbitMQSender class, each one helps setup the underlying communication and adds boilerplate such as the name of the microservice you are sending your request to as well as what types of responses you are expecting.

One final note; I'm using OpenIAM 4.2.1.3 EE, however, there is no reason why the scripts here will not work in other versions.

Finding Users

One of the most basic things you need to be able to do within a groovy script with OpenIAM is to be able to search for users. Using our knowledge above we can use the UserRabbitMQService to search for a user.

There are a number of reasons why you might need to find a user, but most operations that center around a user within OpenIAM start by finding or getting the full details of a user. Within your managed system policy maps you only get a diff object, so it can sometimes be useful to fetch the full user.

One thing that is hard with OpenIAM is testing, to run the scripts within this article, we will be running the scripts we make from a batch task and outputting our results into the logs.

Finding a single user by criteria

Let us start by searching for a single user. Though you can search by lots of different criteria, we will search by name in this example. We will search for the out-of-the-box user Scott Nelson.

Log into the WebConsole and go to the Administration menu → Groovy Manager

OpenIAM Administration Menu → Groovy Manager

Save the following code as a new script at /batch/findUserTest.groovy

/batch/findUserTest.groovy
import java.util.List
import org.apache.commons.logging.Log
import org.apache.commons.logging.LogFactory
import org.openiam.base.ws.MatchType
import org.openiam.base.ws.SearchParam
import org.openiam.common.beans.mq.UserRabbitMQService
import org.openiam.idm.searchbeans.UserSearchBean
import org.openiam.idm.srvc.user.dto.User
import org.openiam.idm.srvc.user.dto.UserCollection
import org.openiam.idm.srvc.user.dto.UserStatusEnum
import org.springframework.context.ApplicationContext

// Dependencies
Log log = LogFactory.getLog("findUserTest") // Logging
ApplicationContext appContext = (ApplicationContext) context // IoC Container
UserRabbitMQService userRabbitMQService = appContext.getBean(UserRabbitMQService.class) // Docs - https://download.openiam.com/release/enterprise/4.2.1.2/javadoc/org/openiam/common/beans/mq/UserRabbitMQService.html

log.info("Finding Scott Nelson")
UserSearchBean usb = new UserSearchBean() // Docs - https://download.openiam.com/release/enterprise/4.2.1.2/javadoc/org/openiam/idm/searchbeans/UserSearchBean.html
usb.addFirstNameMatchToken(new SearchParam("Scott", MatchType.EXACT)) // Add Scott as the first name to look for
usb.addLastNameMatchToken(new SearchParam("Nelson", MatchType.EXACT)) // Add Nelson as the last name to look for

log.info("Performing search")

// Build a list of attributes you want returned for the user - Docs - https://download.openiam.com/release/enterprise/4.2.1.2/javadoc/org/openiam/idm/srvc/user/dto/UserCollection.html
List<UserCollection> userCollection = [UserCollection.PRINCIPALS, UserCollection.ATTRIBUTES, UserCollection.ORGANIZATIONS, UserCollection.EMAILS, UserCollection.SUPERVISORS]
List<User> users = userRabbitMQService.findBeans(usb, userCollection as UserCollection[], 0, 1) // Search Bean, UserCollection[], from, size
log.info("Found: ${users?.size() ?: 0}")
if (users.size() > 0) {
    User user = users.first() // Docs - https://download.openiam.com/release/enterprise/4.2.1.2/javadoc/org/openiam/idm/srvc/user/dto/User.html
    log.info("${user.getId()} - First Name: ${user.getFirstName()}")
    log.info("${user.getId()} - Last Name: ${user.getLastName()}")
    log.info("${user}")
} else {
    log.info("Could not find user")
}

The script above starts by getting some dependencies, it gets the UserRabbitMQService from the Spring Framework IoC container.

UserRabbitMQService userRabbitMQService = appContext.getBean(UserRabbitMQService.class) // Docs - https://download.openiam.com/release/enterprise/4.2.1.2/javadoc/org/openiam/common/beans/mq/UserRabbitMQService.html

It then creates a UserSearchBean, this allows us to specify the search criteria. Within the UserSearchBean we have provided it with a first and last name to look for. As noted in the comments in the code, you can find the currently published docs for the UserSearchBean class here.

UserSearchBean usb = new UserSearchBean() // Docs - https://download.openiam.com/release/enterprise/4.2.1.2/javadoc/org/openiam/idm/searchbeans/UserSearchBean.html
usb.addFirstNameMatchToken(new SearchParam("Scott", MatchType.EXACT)) // Add Scott as the first name to look for
usb.addLastNameMatchToken(new SearchParam("Nelson", MatchType.EXACT)) // Add Nelson as the last name to look for

Next we create a list containing one to more UserCollection enums. This specifies what attributes we want returned with the user. The fewer UserCollection enums provided, the quicker the results come back. The UserCollection class docs can be found here.

List<UserCollection> userCollection = [UserCollection.PRINCIPALS, UserCollection.ATTRIBUTES, UserCollection.ORGANIZATIONS, UserCollection.EMAILS, UserCollection.SUPERVISORS]

Finally, we make a request to the UserRabbitMQService and ask it to only return one result by setting the size to 1. The UserRabbitMQService class docs can be found here.

List<User> users = userRabbitMQService.findBeans(usb, userCollection as UserCollection[], 0, 1) // Search Bean, UserCollection[], from, size

Throughout the script you will find various log.info() methods, these give us a simple way to output what is going on and can be very useful when debugging your code.

Let's test this script out using a batch task.

Now go to the Administration Menu → Batch Task

OpenIAM Administration Menu → Batch Tasks

On the left-hand side click on New Batch Task

OpenIAM Batch Tasks

Create a new batch task with the following settings

FieldValue
Task NameFind User Test
Execution TimeCron Job
Cron Expression0 * * * * *
Execution ScriptGroovy Script
Groovy Script URL/batch/findUserTest.groovy
OpenIAM Create New Batch Task

Click Save. OpenIAM will create the Batch Task and redirect you to the Edit Task page.

OpenIAM Edit Batch Task

Underneath the Batch Task fields you will find some buttons including Execute, you can click on this to run the script. Before clicking Execute, we need to make sure we are looking at the logs.

Depending on how you've deployed OpenIAM will depend on where you will find your logs. It's probably worth pointing out that these logs are different to what you'll find in the Log Viewer within the WebConsole UI. The logs that we are going to generate go into the operating systems logs.

For Kubernetes you will need to run the following kubectl logs command:

Kubernetes
kubectl logs -l release=test --all-containers --max-log-requests 34 -f

Replace test with the same value set as APP_NAME within your env.sh file. If you are running lots of replicas, you might need to increase the --max-log-requests if you get an error.

For Docker you can run the following command:

Docker
docker logs --tail=0 --follow

For RPM installs, you'll find your logs in the /usr/local/openiam/logs/ folder and can tail them with:

RPM
tail -f /usr/local/openiam/logs/*

Click the Execute button.

OpenIAM Execute Batch Task Confirmation Modal

Click Yes to execute the batch task.

After a few seconds you should see the following in your logs (I've stripped the timestamp and a few bits from the output to make it easier to read).

Output
Finding Scott Nelson
Performing search
Found: 1
3006 - First Name: Scott
3006 - Last Name: Nelson
User(super=AbstractMetadataTypeDTO(super=KeyNameDTO [name=null, id=3006], mdTypeId=DEFAULT_USER, metadataTypeName=null, mdGrouping=null), operation=NO_CHANGE, birthdate=null, companyOwnerId=null, createDate=Mon Dec 05 16:36:23 GMT 2022, createdBy=null, employeeId=null, employeeTypeId=null, firstName=Scott, jobCodeId=null, lastName=Nelson, lastUpdate=Mon Dec 05 16:38:29 GMT 2022, lastUpdatedBy=null, locationCd=null, locationName=null, classification=null, middleInit=null, prefix=null, sex=null, status=ACTIVE, secondaryStatus=null, positionStatus=null, previousPosition=null, suffix=null, title=null, userTypeInd=null, mailCode=null, costCenter=null, startDate=null, lastDate=null, claimDate=null, nickname=null, maidenName=null, passwordTheme=null, principalList=[Login(super=AbstractLogin(super=KeyDTO(super=BaseObject(objectState=NEW, requestorSessionID=null, requestorUserId=null, requestorLogin=null, requestClientIP=null, parentAuditLogId=null, testRequest=false), id=7), managedSysId=0, operation=NO_CHANGE, active=true, provStatus=null), login=snelson, lowerCaseLogin=snelson, userId=3006, password=60d151ee0b0760ad204df60cc533d40d00d3ceb324b3bdef573deca79be6c5c0, pwdChanged=null, pwdExp=null, firstTimeLogin=true, resetPasswordEnum=DEFAULT, resetPassword=false, locked=false, gracePeriod=null, createDate=Mon Dec 05 16:36:23 GMT 2022, createdBy=null, currentLoginHost=null, authFailCount=0, lastAuthAttempt=null, lastLogin=null, passwordChangeCount=0, lastLoginIP=null, prevLogin=null, prevLoginIP=null, pswdResetToken=null, pswdResetTokenExp=null, managedSysName=null, lastUpdate=Mon Dec 05 16:38:39 GMT 2022, smsCodeExpiration=null, otpActive=false, totpActive=false)], alternateContactId=null, alternativeStartDate=null, alternativeEndDate=null, certificationDelegateId=null, certificationDelegateStartDate=null, certificationDelegateEndDate=null, userOwnerId=null, datePasswordChanged=null, dateChallengeRespChanged=null, dateITPolicyApproved=null, roles=[], affiliations=[UserToOrganizationMembershipXref(super=MembershipXref(super=KeyNameDTO [name=null, id=null], startDate=null, endDate=null, entityId=100, memberEntityId=3006, rights=[AccessRight(super=KeyNameDTO [name=null, id=null], managedSysId=null, managedSysName=null)], operation=NO_CHANGE, description=null), organizationTypeId=null, organizationName=OpenIAM)], groups=[], resources=[], notifyUserViaEmail=true, relatedAccounts=null, primaryAccount=null, fromActivitiCreation=false, userSubTypeId=null, partnerName=null, prefixPartnerName=null, prefixLastName=null, displayNameFormat=null, accessRightIds=null, oauthCodes=null, accessRightStartDate=null, accessRightEndDate=null, visible=true, applicationIds=null, applicationNames=null)

The groovy script found the user Scott Nelson and output details of the user to the logs including a print of the entire User object.

Be careful with what you send to your logs. Using logging to output user information isn't a good idea as it may commit personal, identifiable or sensitive information such as passwords into storage. If you need to identify the user affected by an issue, use the getId() method to output their OpenIAM ID only.

Finding a single user by ID

If you already know the internal ID of the user, you can fetch the user without having to search. This can be useful when working with a diff-object, such as those used in managed system policy maps. You can use the ID to get the full user. From the previous code, we know that Scott Nelson has an ID of 3006, we can use this to fetch the user.

/batch/findUserTest.groovy
import java.util.List
import org.apache.commons.logging.Log
import org.apache.commons.logging.LogFactory
import org.openiam.common.beans.mq.UserRabbitMQService
import org.openiam.idm.srvc.user.dto.User
import org.openiam.idm.srvc.user.dto.UserCollection
import org.springframework.context.ApplicationContext

// Dependencies
Log log = LogFactory.getLog("findUserTest") // Logging
ApplicationContext appContext = (ApplicationContext) context // IoC Container
UserRabbitMQService userRabbitMQService = appContext.getBean(UserRabbitMQService.class) // Docs - https://download.openiam.com/release/enterprise/4.2.1.2/javadoc/org/openiam/common/beans/mq/UserRabbitMQService.html

log.info("Finding Scott Nelson by ID")

log.info("Performing search")

// Build a list of attributes you want returned for the user - Docs - https://download.openiam.com/release/enterprise/4.2.1.2/javadoc/org/openiam/idm/srvc/user/dto/UserCollection.html
List<UserCollection> userCollection = [UserCollection.PRINCIPALS, UserCollection.ATTRIBUTES, UserCollection.ORGANIZATIONS, UserCollection.EMAILS, UserCollection.SUPERVISORS]
User user = userRabbitMQService.getUser("3006", userCollection as UserCollection[])
if (user) {
    log.info("${user.getId()} - First Name: ${user.getFirstName()}")
    log.info("${user.getId()} - Last Name: ${user.getLastName()}")
    log.info("${user}")
} else {
    log.info("Could not find user")
}
output = 0

As we already know the ID of the OpenIAM user we can significantly slim down the code required to get the user. The UserSearchBean can be removed and we use a different method from UserRabbitMQService.

User user = userRabbitMQService.getUser("3006", userCollection as UserCollection[])

This gets the user and outputs the following.

Output
Finding Scott Nelson by ID
Performing search
3006 - First Name: Scott
3006 - Last Name: Nelson
User(super=AbstractMetadataTypeDTO(super=KeyNameDTO [name=null, id=3006], mdTypeId=DEFAULT_USER, metadataTypeName=null, mdGrouping=null), operation=NO_CHANGE, birthdate=null, companyOwnerId=null, createDate=Mon Dec 05 16:36:23 GMT 2022, createdBy=null, employeeId=null, employeeTypeId=null, firstName=Scott, jobCodeId=null, lastName=Nelson, lastUpdate=Mon Dec 05 16:38:29 GMT 2022, lastUpdatedBy=null, locationCd=null, locationName=null, classification=null, middleInit=null, prefix=null, sex=null, status=ACTIVE, secondaryStatus=null, positionStatus=null, previousPosition=null, suffix=null, title=null, userTypeInd=null, mailCode=null, costCenter=null, startDate=null, lastDate=null, claimDate=null, nickname=null, maidenName=null, passwordTheme=null, principalList=[Login(super=AbstractLogin(super=KeyDTO(super=BaseObject(objectState=NEW, requestorSessionID=null, requestorUserId=null, requestorLogin=null, requestClientIP=null, parentAuditLogId=null, testRequest=false), id=7), managedSysId=0, operation=NO_CHANGE, active=true, provStatus=null), login=snelson, lowerCaseLogin=snelson, userId=3006, password=60d151ee0b0760ad204df60cc533d40d00d3ceb324b3bdef573deca79be6c5c0, pwdChanged=null, pwdExp=null, firstTimeLogin=true, resetPasswordEnum=DEFAULT, resetPassword=false, locked=false, gracePeriod=null, createDate=Mon Dec 05 16:36:23 GMT 2022, createdBy=null, currentLoginHost=null, authFailCount=0, lastAuthAttempt=null, lastLogin=null, passwordChangeCount=0, lastLoginIP=null, prevLogin=null, prevLoginIP=null, pswdResetToken=null, pswdResetTokenExp=null, managedSysName=null, lastUpdate=Mon Dec 05 16:38:39 GMT 2022, smsCodeExpiration=null, otpActive=false, totpActive=false)], alternateContactId=null, alternativeStartDate=null, alternativeEndDate=null, certificationDelegateId=null, certificationDelegateStartDate=null, certificationDelegateEndDate=null, userOwnerId=null, datePasswordChanged=null, dateChallengeRespChanged=null, dateITPolicyApproved=null, roles=[], affiliations=[UserToOrganizationMembershipXref(super=MembershipXref(super=KeyNameDTO [name=null, id=null], startDate=null, endDate=null, entityId=100, memberEntityId=3006, rights=[AccessRight(super=KeyNameDTO [name=null, id=null], managedSysId=null, managedSysName=null)], operation=NO_CHANGE, description=null), organizationTypeId=null, organizationName=OpenIAM)], groups=[], resources=[], notifyUserViaEmail=true, relatedAccounts=null, primaryAccount=null, fromActivitiCreation=false, userSubTypeId=null, partnerName=null, prefixPartnerName=null, prefixLastName=null, displayNameFormat=null, accessRightIds=null, oauthCodes=null, accessRightStartDate=null, accessRightEndDate=null, visible=true, applicationIds=null, applicationNames=null)

Finding multiple users

In the previous example, we assumed that there was only a single user with the name Scott Nelson, but what do we do if there are several people with that name? We can add an iterator, or loop to go over multiple users. For this example I've added a new user to OpenIAM also called Scott Nelson. I've set both job title and primary email on both users so we can clearly see which one is which.

OpenIAM User Search, showing two Scott Nelsons

I'm going to assume you've been following along so won't cover how to run the batch task or view the output, both of which are covered above.

Go back to the Groovy Manager and update the /batch/findUserTest.groovy script with the following.

/batch/findUserTest.groovy
import java.util.List
import org.apache.commons.logging.Log
import org.apache.commons.logging.LogFactory
import org.openiam.base.ws.MatchType
import org.openiam.base.ws.SearchParam
import org.openiam.common.beans.mq.UserRabbitMQService
import org.openiam.idm.searchbeans.UserSearchBean
import org.openiam.idm.srvc.user.dto.User
import org.openiam.idm.srvc.user.dto.UserCollection
import org.openiam.idm.srvc.user.dto.UserStatusEnum
import org.springframework.context.ApplicationContext

// Dependencies
Log log = LogFactory.getLog("findUserTest") // Logging
ApplicationContext appContext = (ApplicationContext) context // IoC Container
UserRabbitMQService userRabbitMQService = appContext.getBean(UserRabbitMQService.class) // Docs - https://download.openiam.com/release/enterprise/4.2.1.2/javadoc/org/openiam/common/beans/mq/UserRabbitMQService.html

log.info("Finding Scott Nelson")
UserSearchBean usb = new UserSearchBean() // Docs - https://download.openiam.com/release/enterprise/4.2.1.2/javadoc/org/openiam/idm/searchbeans/UserSearchBean.html
usb.addFirstNameMatchToken(new SearchParam("Scott", MatchType.EXACT)) // Add Scott as the first name to look for
usb.addLastNameMatchToken(new SearchParam("Nelson", MatchType.EXACT)) // Add Nelson as the last name to look for

log.info("Performing search")

// Build a list of attributes you want returned for the user - Docs - https://download.openiam.com/release/enterprise/4.2.1.2/javadoc/org/openiam/idm/srvc/user/dto/UserCollection.html
List<UserCollection> userCollection = [UserCollection.PRINCIPALS, UserCollection.ATTRIBUTES, UserCollection.ORGANIZATIONS, UserCollection.EMAILS, UserCollection.SUPERVISORS]
List<User> users = userRabbitMQService.findBeans(usb, userCollection as UserCollection[], 0, 50) // Search Bean, UserCollection[], from, size
log.info("Found: ${users?.size() ?: 0}")
if (users.size() > 0) {
    for (User user : users) {
        log.info("${user.getId()} - First Name: ${user.getFirstName()}")
        log.info("${user.getId()} - Last Name: ${user.getLastName()}")
        log.info("${user.getId()} - Job Title: ${user.getTitle()}")
        log.info("${user.getId()} - Email Address: ${user.emailAddresses?.find({ a -> a.mdTypeId = 'PRIMARY_EMAIL' }).emailAddress ?: 'Not found'}")
        log.info("${user}")
    }
} else {
    log.info("Could not find user")
}

There are a couple of differences between this and the previous version of the script. I've changed the size parameter for the userRabbitMQService.findBeans() method from 1 to 50 as this sets the maximum number of results you want back. If you need to get lots of users you should look at paginating rather than setting the size too high as you may run into resource issues or issues with RabbitMQ.

List<User> users = userRabbitMQService.findBeans(usb, userCollection as UserCollection[], 0, 50) // Search Bean, UserCollection[], from, size

I've also added an iterator, as well as some additional log lines to output the user's job title and primary email address.

for (User user : users) {
    log.info("${user.getId()} - First Name: ${user.getFirstName()}")
    log.info("${user.getId()} - Last Name: ${user.getLastName()}")
    log.info("${user.getId()} - Job Title: ${user.getTitle()}")
    log.info("${user.getId()} - Email Address: ${user.emailAddresses?.find({ a -> a.mdTypeId = 'PRIMARY_EMAIL' }).emailAddress ?: 'Not found'}")
    log.info("${user}")
}

Run the batch task again, this time the output shows:

Output
Finding Scott Nelson
Performing search
Found: 2
8a67dccb851566a40185158774c401e2 - First Name: Scott
8a67dccb851566a40185158774c401e2 - Last Name: Nelson
8a67dccb851566a40185158774c401e2 - Job Title: Superhero
8a67dccb851566a40185158774c401e2 - Email Address: scott-the-superhero@example.com
User(super=AbstractMetadataTypeDTO(super=KeyNameDTO [name=null, id=8a67dccb851566a40185158774c401e2], mdTypeId=DEFAULT_USER, metadataTypeName=null, mdGrouping=null), operation=NO_CHANGE, birthdate=null, companyOwnerId=null, createDate=Thu Dec 15 11:24:36 GMT 2022, createdBy=3000, employeeId=null, employeeTypeId=null, firstName=Scott, jobCodeId=null, lastName=Nelson, lastUpdate=Thu Dec 15 11:26:16 GMT 2022, lastUpdatedBy=null, locationCd=null, locationName=null, classification=null, middleInit=null, prefix=null, sex=null, status=ACTIVE, secondaryStatus=null, positionStatus=null, previousPosition=null, suffix=null, title=Superhero, userTypeInd=null, mailCode=null, costCenter=null, startDate=null, lastDate=null, claimDate=null, nickname=null, maidenName=null, passwordTheme=null, principalList=[Login(super=AbstractLogin(super=KeyDTO(super=BaseObject(objectState=NEW, requestorSessionID=null, requestorUserId=null, requestorLogin=null, requestClientIP=null, parentAuditLogId=null, testRequest=false), id=8a67dccb851566a40185158774c501e3), managedSysId=0, operation=NO_CHANGE, active=true, provStatus=SAVED), login=Scott.Nelson, lowerCaseLogin=scott.nelson, userId=8a67dccb851566a40185158774c401e2, password=13d9758b9b18ebe2f10aa8b2f3487c6a6483c1bd3c914690e4fe4f1322a1b6df, pwdChanged=null, pwdExp=Wed Mar 15 11:24:36 GMT 2023, firstTimeLogin=false, resetPasswordEnum=DEFAULT, resetPassword=false, locked=false, gracePeriod=Fri Dec 16 11:24:36 GMT 2022, createDate=Thu Dec 15 11:24:36 GMT 2022, createdBy=null, currentLoginHost=null, authFailCount=0, lastAuthAttempt=null, lastLogin=null, passwordChangeCount=0, lastLoginIP=null, prevLogin=null, prevLoginIP=null, pswdResetToken=null, pswdResetTokenExp=null, managedSysName=null, lastUpdate=Thu Dec 15 11:24:36 GMT 2022, smsCodeExpiration=null, otpActive=false, totpActive=false)], alternateContactId=null, alternativeStartDate=null, alternativeEndDate=null, certificationDelegateId=null, certificationDelegateStartDate=null, certificationDelegateEndDate=null, userOwnerId=null, datePasswordChanged=null, dateChallengeRespChanged=null, dateITPolicyApproved=null, roles=[], affiliations=[], groups=[], resources=[], notifyUserViaEmail=true, relatedAccounts=null, primaryAccount=null, fromActivitiCreation=false, userSubTypeId=null, partnerName=null, prefixPartnerName=null, prefixLastName=null, displayNameFormat=null, accessRightIds=null, oauthCodes=null, accessRightStartDate=null, accessRightEndDate=null, visible=true, applicationIds=null, applicationNames=null)
3006 - First Name: Scott
3006 - Last Name: Nelson
3006 - Job Title: Office Worker
3006 - Email Address: scott-the-office-worker@example.com
User(super=AbstractMetadataTypeDTO(super=KeyNameDTO [name=null, id=3006], mdTypeId=DEFAULT_USER, metadataTypeName=null, mdGrouping=null), operation=NO_CHANGE, birthdate=null, companyOwnerId=null, createDate=Thu Dec 15 10:45:57 GMT 2022, createdBy=NA, employeeId=null, employeeTypeId=null, firstName=Scott, jobCodeId=null, lastName=Nelson, lastUpdate=Thu Dec 15 11:27:21 GMT 2022, lastUpdatedBy=null, locationCd=null, locationName=null, classification=null, middleInit=null, prefix=null, sex=null, status=ACTIVE, secondaryStatus=null, positionStatus=null, previousPosition=null, suffix=null, title=Office Worker, userTypeInd=null, mailCode=null, costCenter=null, startDate=null, lastDate=null, claimDate=null, nickname=null, maidenName=null, passwordTheme=null, principalList=[Login(super=AbstractLogin(super=KeyDTO(super=BaseObject(objectState=NEW, requestorSessionID=null, requestorUserId=null, requestorLogin=null, requestClientIP=null, parentAuditLogId=null, testRequest=false), id=7), managedSysId=0, operation=NO_CHANGE, active=true, provStatus=null), login=snelson, lowerCaseLogin=snelson, userId=3006, password=9a129b07b74b694be62ecb74df148e2f7d6085e1ff6271247dbdc39ee03429a9, pwdChanged=null, pwdExp=null, firstTimeLogin=true, resetPasswordEnum=DEFAULT, resetPassword=false, locked=false, gracePeriod=null, createDate=Thu Dec 15 10:45:57 GMT 2022, createdBy=null, currentLoginHost=null, authFailCount=0, lastAuthAttempt=null, lastLogin=null, passwordChangeCount=0, lastLoginIP=null, prevLogin=null, prevLoginIP=null, pswdResetToken=null, pswdResetTokenExp=null, managedSysName=null, lastUpdate=Thu Dec 15 10:49:15 GMT 2022, smsCodeExpiration=null, otpActive=false, totpActive=false)], alternateContactId=null, alternativeStartDate=null, alternativeEndDate=null, certificationDelegateId=null, certificationDelegateStartDate=null, certificationDelegateEndDate=null, userOwnerId=null, datePasswordChanged=null, dateChallengeRespChanged=null, dateITPolicyApproved=null, roles=[], affiliations=[UserToOrganizationMembershipXref(super=MembershipXref(super=KeyNameDTO [name=null, id=null], startDate=null, endDate=null, entityId=100, memberEntityId=3006, rights=[AccessRight(super=KeyNameDTO [name=null, id=null], managedSysId=null, managedSysName=null)], operation=NO_CHANGE, description=null), organizationTypeId=null, organizationName=OpenIAM)], groups=[], resources=[], notifyUserViaEmail=true, relatedAccounts=null, primaryAccount=null, fromActivitiCreation=false, userSubTypeId=null, partnerName=null, prefixPartnerName=null, prefixLastName=null, displayNameFormat=null, accessRightIds=null, oauthCodes=null, accessRightStartDate=null, accessRightEndDate=null, visible=true, applicationIds=null, applicationNames=null)

User Search Criteria

From the previous examples, we have only searched by First and Last names. This isn't the most useful of search criteria but is a good example of how to find people. The code we've used for these search criteria performs an exact match but this can be changed to a number of different MatchType such as BETWEEN, CONTAINS, STARTS_WITH, ENDS_WITH, EXACT, the full list can be found within the docs here.

The UserSearchBean class has a number of different methods which configure the search criteria, here are some examples which can all be combined.

First Name

Search for a user by first name

EXACT
usb.addFirstNameMatchToken(new SearchParam("Scott", MatchType.EXACT))
STARTS_WITH
usb.addFirstNameMatchToken(new SearchParam("Sc", MatchType.STARTS_WITH))

Last Name

Search for a user by last name

EXACT
usb.addLastNameMatchToken(new SearchParam("Nelson", MatchType.EXACT))
STARTS_WITH
usb.addLastNameMatchToken(new SearchParam("Nel", MatchType.STARTS_WITH))

Maiden Name

Search for a user by maiden name

EXACT
usb.addMaidenNameMatchToken(new SearchParam("Nelson", MatchType.EXACT))
STARTS_WITH
usb.addMaidenNameMatchToken(new SearchParam("Nel", MatchType.STARTS_WITH))

Employee ID

Search for a user by their employee ID.

EXACT
usb.setEmployeeIdMatchTokens(new SearchParam("ID_HERE", MatchType.EXACT))
STARTS_WITH
usb.setEmployeeIdMatchTokens(new SearchParam("ID_HE", MatchType.STARTS_WITH))

Employee Type

Search for a user by their employee type

EXACT
usb.setEmployeeType("Student")

Job Title

Search for a user by their Job Title.

EXACT
usb.setTitle("Exact Job Title Here")

User Type

When importing or creating users, set the userTypeInd field to the name of the metadatatype you set the user as. This is easier to search than metadatatype as it is stored as a string.

EXACT
usb.setUserType("Student")

User Status

Search for a user based on their account status. Statuses are defined within UserStatusEnum such as ACTIVE, DISABLED, INACTIVE, LOCKED, PENDING_INITIAL_LOGIN, PENDING_START_DATE, TERMINATED, a full list can be found on the docs here.

Searching for users based on User Status could result in a large number of results. Consider using pagination to get the results.

ACTIVE
usb.setUserStatus(UserStatusEnum.ACTIVE.getValue())
DISABLED
usb.setUserStatus(UserStatusEnum.DISABLED.getValue())
INACTIVE
usb.setUserStatus(UserStatusEnum.INACTIVE.getValue())
PENDING_INITIAL_LOGIN
usb.setUserStatus(UserStatusEnum.PENDING_INITIAL_LOGIN.getValue())
PENDING_START_DATE
usb.setUserStatus(UserStatusEnum.PENDING_START_DATE.getValue())
TERMINATED
usb.setUserStatus(UserStatusEnum.TERMINATED.getValue())

Start Date

Search for a user based on their Start Date.

The setStartDateToken() method within UserSearchBean currently does not work. We will need to substitute the UserRabbitMQService with another service.

import org.openiam.srvc.user.UserDataWebService
...
UserDataWebService userDataWebService = appContext.getBean("userWS") as UserDataWebService
...
// Date to Search from (Today)
Calendar from = Calendar.getInstance()
from.set(Calendar.HOUR_OF_DAY, 0)
from.set(Calendar.MINUTE, 0)
from.set(Calendar.SECOND, 0)
from.set(Calendar.MILLISECOND, 000)

// Date to search until (3 Days from now)
Calendar to = Calendar.getInstance()
to.add(Calendar.DAY_OF_MONTH, 3) // Add 3 days to today's date
to.set(Calendar.HOUR_OF_DAY, 23)
to.set(Calendar.MINUTE, 59)
to.set(Calendar.SECOND, 59)
to.set(Calendar.MILLISECOND, 999)
...
List<User> users = userDataWebService.getUserBetweenStartDate(from.getTime(), to.getTime())

This method of retrieving users cannot be combined with other search criteria and cannot be paginated. You also cannot specify which properties/attributes you want to return. If further criteria is required, you will need to filter the results with code.

Last Date

Search for a user based on their Last (End) Date.

The setLastDateToken() method within UserSearchBean currently does not work. We will need to substitute the UserRabbitMQService with another service.

import org.openiam.srvc.user.UserDataWebService
...
UserDataWebService userDataWebService = appContext.getBean("userWS") as UserDataWebService
...
// Date to Search from (Today)
Calendar from = Calendar.getInstance()
from.set(Calendar.HOUR_OF_DAY, 0)
from.set(Calendar.MINUTE, 0)
from.set(Calendar.SECOND, 0)
from.set(Calendar.MILLISECOND, 000)

// Date to search until (3 Days from now)
Calendar to = Calendar.getInstance()
to.add(Calendar.DAY_OF_MONTH, 3) // Add 3 days to today's date
to.set(Calendar.HOUR_OF_DAY, 23)
to.set(Calendar.MINUTE, 59)
to.set(Calendar.SECOND, 59)
to.set(Calendar.MILLISECOND, 999)
...
List<User> users = userDataWebService.getUserBetweenLastDate(from.getTime(), to.getTime())

This method of retrieving users cannot be combined with other search criteria and cannot be paginated. You also cannot specify which properties/attributes you want to return. If further criteria is required, you will need to filter the results with code.

Pagination

In the examples above, we've only been expecting a small number of results. If you have the potential of getting large numbers of results, you should paginate your requests to UserRabbitMQService. Asking for too many results in one go can cause resource issues and potentially issues with RabbitMQ due to the size of data being stored such as hitting the storage limits of a single message or exceeding the response timeout limit.

Go to Groovy Manager and replace the script from above with the following

/batch/findUserTest.groovy
import java.util.List
import org.apache.commons.logging.Log
import org.apache.commons.logging.LogFactory
import org.openiam.base.request.BaseSearchServiceRequest
import org.openiam.base.request.BaseServiceRequest
import org.openiam.base.response.data.IntResponse
import org.openiam.base.response.data.BaseDataResponse
import org.openiam.base.ws.MatchType
import org.openiam.base.ws.SearchParam
import org.openiam.common.beans.mq.RabbitMQSender
import org.openiam.common.beans.mq.UserRabbitMQService
import org.openiam.idm.searchbeans.UserSearchBean
import org.openiam.idm.srvc.user.dto.User
import org.openiam.idm.srvc.user.dto.UserCollection
import org.openiam.idm.srvc.user.dto.UserStatusEnum
import org.openiam.mq.constants.api.OpenIAMAPI
import org.openiam.mq.constants.api.user.UserServiceAPI
import org.openiam.mq.constants.queue.user.UserServiceQueue
import org.springframework.context.ApplicationContext

// Dependencies
Log log = LogFactory.getLog("findUserTest") // Logging
ApplicationContext appContext = (ApplicationContext) context // IoC Container
UserRabbitMQService userRabbitMQService = appContext.getBean(UserRabbitMQService.class) // Docs - https://download.openiam.com/release/enterprise/4.2.1.2/javadoc/org/openiam/common/beans/mq/UserRabbitMQService.html
RabbitMQSender rabbitMQSender = appContext.getBean(RabbitMQSender.class) // Underlying RabbitMQSender 
UserServiceQueue queue = appContext.getBean(UserServiceQueue.class) // Queue to use

log.info("Finding All Users")
UserSearchBean usb = new UserSearchBean() // Docs - https://download.openiam.com/release/enterprise/4.2.1.2/javadoc/org/openiam/idm/searchbeans/UserSearchBean.html

// Build a list of attributes you want returned for the user - Docs - https://download.openiam.com/release/enterprise/4.2.1.2/javadoc/org/openiam/idm/srvc/user/dto/UserCollection.html
List<UserCollection> userCollection = [UserCollection.PRINCIPALS, UserCollection.ATTRIBUTES, UserCollection.ORGANIZATIONS, UserCollection.EMAILS, UserCollection.SUPERVISORS]

log.info("Getting total results from RabbitMQSender")
Integer totalCount = 0
BaseDataResponse response = (BaseDataResponse) rabbitMQSender.sendAndReceive((UserServiceQueue) queue, (OpenIAMAPI) UserServiceAPI.Count, new BaseSearchServiceRequest(usb), IntResponse.class)
if (response.isFailure()) {
    log.info(String.format("Can't getValue: code %s, errorText %s", response.getErrorCode().name(), response.getErrorText()));
} else {
    totalCount = (Integer) response.getValue()
}
log.info("There are ${totalCount} results")

int total, from = 0
int size = 5 // Number of results to get each time
List<User> users = new ArrayList<>() // Empty list to hold users
while (total < totalCount) {
    List<User> results = userRabbitMQService.findBeans(usb, userCollection as UserCollection[], from, size) // Get page
    if(results.size() > 0) {
        total += results.size()
        from += size
        users.addAll(results) // Add results to users array
        log.info("${total}/${totalCount} users retrieved")
    } else {
        log.info("Done")
        break
    }
}

log.info("There are ${users.size()} users")

for (User user : users) {
    log.info(user.getId())
}

output = 0

There are a number of new imports required for this code, each of these new imports are required to get the total number of results based on your search criteria.

import org.openiam.base.request.BaseServiceRequest
import org.openiam.base.response.data.BaseDataResponse
import org.openiam.base.response.data.IntResponse
import org.openiam.common.beans.mq.RabbitMQSender
import org.openiam.mq.constants.api.OpenIAMAPI
import org.openiam.mq.constants.queue.user.UserServiceQueue

We also need to get a couple of new classes from the IoC container. RabbitMQSender is the underlying class that the UserRabbitMQService class uses to make requests. UserServiceQueue is where to put the requests in RabbitMQ.

RabbitMQSender rabbitMQSender = appContext.getBean(RabbitMQSender.class) // Underlying RabbitMQSender 
UserServiceQueue queue = appContext.getBean(UserServiceQueue.class) // Queue to use

As there are only a small number of users out of the box, I've removed the search criteria so this script gets all users.

The first step to paginate the results is to get a total count based on your search criteria.

log.info("Getting total results from RabbitMQSender")
Integer totalCount = 0
BaseDataResponse response = (BaseDataResponse) rabbitMQSender.sendAndReceive((UserServiceQueue) queue, (OpenIAMAPI) UserServiceAPI.Count, new BaseSearchServiceRequest(usb), IntResponse.class)
if (response.isFailure()) {
    log.info(String.format("Can't getValue: code %s, errorText %s", response.getErrorCode().name(), response.getErrorText()));
} else {
    totalCount = (Integer) response.getValue()
}
log.info("There are ${totalCount} results")

Now we have a total number of results to fetch, we perform a while loop to get batches of users until there are no more results. I've set the size variable to 5 as this test system only has a small number of users. I would set this to a maximum of around 500.

int total, from = 0
int size = 5 // Number of results to get each time
List<User> users = new ArrayList<>() // Empty list to hold users
while (total < totalCount) {
    List<User> results = userRabbitMQService.findBeans(usb, userCollection as UserCollection[], from, size) // Get page
    if(results.size() > 0) {
        total += results.size()
        from += size
        users.addAll(results) // Add results to users array
        log.info("${total}/${totalCount} users retrieved")
    } else {
        log.info("Done")
        break
    }
}

log.info("There are ${users.size()} users")

The last part of the code loops through the users list and puts their ID into the logs resulting in the following output.

Output
Finding All Users
Getting total results from RabbitMQSender
There are 9 results
5/9 users retrieved
9/9 users retrieved
There are 9 users
3009
3008
3007
0001
3001
3010
3000
8a67dccb851566a40185158774c401e2
3006

Summary

Though the code I've shared isn't exhaustive, it should give you a good starting point when you need to find users matching certain criteria within your groovy scripts. In a future post I'll share how to make changes to users which is another fundamental task of an IDM system.

Need help with OpenIAM? Make sure you join the official OpenIAM Community where you can ask for help from other community members.