Hello @ameet_shah
Thank you for taking the time to review my question and provide your feedback.
I’ve been testing a generic script as a business rule to cover scenarios where a user is onboarded, changes job position, or no longer has an assigned position.
Do you think it’s necessary for the script to explicitly handle all these cases, or does OpenIAM already cover them with the script you previously shared?
At the moment, the job role configuration is as follows:
Analista IT (Job Title)
→ Admin Role (Managed System A)
→ Analyst Role (Managed System B)
Please note that the target systems (platforms) do not have out-of-the-box connectors, so a custom connector was developed using microservices.
Additionally, we are evaluating how to handle the access reconciliation process so that it runs once per day.
The script I have been using is as follows:
import org.openiam.br.groovy.IActionExecutor
import org.openiam.base.AttributeOperationEnum
import org.openiam.common.beans.mq.RoleRabbitMQService
import org.openiam.common.beans.mq.GroupRabbitMQService
import org.openiam.idm.searchbeans.RoleSearchBean
import org.openiam.idm.searchbeans.GroupSearchBean
import org.openiam.idm.srvc.entitlements.EntitlementsCollection
import org.openiam.idm.srvc.grp.dto.Group
import org.openiam.idm.srvc.role.dto.Role
import org.openiam.idm.srvc.user.dto.User
import org.openiam.idm.srvc.user.dto.UserStatusEnum
import org.openiam.idm.srvc.membership.dto.MembershipXref
import org.openiam.idm.srvc.user.dto.UserToRoleMembershipXref
import org.openiam.idm.srvc.user.dto.UserToGroupMembershipXref
import org.apache.commons.collections.CollectionUtils
import org.springframework.context.ApplicationContext
class RecalculateAccessByCargo implements IActionExecutor {
ApplicationContext context
private static final Set<String> EXCLUDED_ROLE_IDS = new HashSet<String>()
private static final Set<String> EXCLUDED_GROUP_IDS = new HashSet<String>()
@Override
void perform(User user) {
println "========== RecalculateAccessByCargo START =========="
if (user == null) {
println "User is null. Skip."
return
}
if (user.getStatus() == UserStatusEnum.TERMINATED ||
user.getStatus() == UserStatusEnum.DISABLED) {
println "User terminated/disabled. Removing all roles/groups."
endDateAllRoles(user)
endDateAllGroups(user)
return
}
String cargoName = user.getTitle()?.trim()
println "Charge received from user.title: ${cargoName}"
if (!cargoName) {
println "user.title empty. Cannot be recalculated."
return
}
Role cargoRole = fetchCargoRoleByName(cargoName)
if (cargoRole == null) {
println "No parent role with name was found: ${cargoName}"
return
}
Set<String> expectedRoleIds = new LinkedHashSet<String>()
Set<String> certifiedRoleIds = new LinkedHashSet<String>()
Set<String> expectedGroupIds = new LinkedHashSet<String>()
buildExpectedAccessFromCargo(cargoRole, expectedRoleIds, certifiedRoleIds, expectedGroupIds)
println "Expected roles: ${expectedRoleIds}"
println "Expected groups: ${expectedGroupIds}"
reconcileRoles(user, expectedRoleIds)
reconcileGroups(user, expectedGroupIds)
addMissingRoles(user, expectedRoleIds, certifiedRoleIds)
addMissingGroups(user, expectedGroupIds)
println "========== RecalculateAccessByCargo END =========="
}
private void buildExpectedAccessFromCargo(Role cargoRole,
Set<String> expectedRoleIds,
Set<String> certifiedRoleIds,
Set<String> expectedGroupIds) {
/*
* Add the parent position
*/
expectedRoleIds.add(cargoRole.getId())
certifiedRoleIds.add(cargoRole.getId())
/*
* If the position had direct reports
*/
addGroupsFromRole(cargoRole, expectedGroupIds)
if (CollectionUtils.isEmpty(cargoRole.getChildRoles())) {
return
}
for (MembershipXref roleXref : cargoRole.getChildRoles()) {
Role childRole = fetchRoleById(roleXref.getEntityId())
if (childRole == null) {
continue
}
/*
* Direct son role of the position
*/
expectedRoleIds.add(childRole.getId())
certifiedRoleIds.add(childRole.getId())
addGroupsFromRole(childRole, expectedGroupIds)
if (CollectionUtils.isNotEmpty(childRole.getChildRoles())) {
for (MembershipXref perfilXref : childRole.getChildRoles()) {
Role perfilRole = fetchRoleById(perfilXref.getEntityId())
if (perfilRole == null) {
continue
}
/*
* Perfil
*/
expectedRoleIds.add(perfilRole.getId())
addGroupsFromRole(perfilRole, expectedGroupIds)
}
}
}
}
private void addGroupsFromRole(Role role, Set<String> expectedGroupIds) {
if (role == null || CollectionUtils.isEmpty(role.getGroups())) {
return
}
for (MembershipXref groupXref : role.getGroups()) {
if (groupXref.getEntityId() != null) {
expectedGroupIds.add(groupXref.getEntityId())
}
}
}
private void reconcileRoles(User user, Set<String> expectedRoleIds) {
if (CollectionUtils.isEmpty(user.getRoles())) {
return
}
for (UserToRoleMembershipXref existingRole : user.getRoles()) {
String roleId = existingRole.getEntityId()
if (!roleId) {
continue
}
if (existingRole.getOperation() == AttributeOperationEnum.DELETE) {
continue
}
if (EXCLUDED_ROLE_IDS.contains(roleId)) {
println "Role excluido, no se toca: ${roleId}"
continue
}
if (!expectedRoleIds.contains(roleId)) {
existingRole.setEndDate(new Date())
existingRole.setOperation(AttributeOperationEnum.DELETE)
println "Role removed because it does not correspond to the current position: ${roleId}"
} else {
println "Role is maintained: ${roleId}"
}
}
}
private void reconcileGroups(User user, Set<String> expectedGroupIds) {
if (CollectionUtils.isEmpty(user.getGroups())) {
return
}
for (UserToGroupMembershipXref existingGroup : user.getGroups()) {
String groupId = existingGroup.getEntityId()
if (!groupId) {
continue
}
if (existingGroup.getOperation() == AttributeOperationEnum.DELETE) {
continue
}
if (EXCLUDED_GROUP_IDS.contains(groupId)) {
println "Excluded group, untouchable: ${groupId}"
continue
}
if (!expectedGroupIds.contains(groupId)) {
existingGroup.setEndDate(new Date())
existingGroup.setOperation(AttributeOperationEnum.DELETE)
println "Group removed because it does not correspond to the current position: ${groupId}"
} else {
println "Group remains: ${groupId}"
}
}
}
private void addMissingRoles(User user,
Set<String> expectedRoleIds,
Set<String> certifiedRoleIds) {
for (String roleId : expectedRoleIds) {
if (hasActiveRole(user, roleId)) {
continue
}
Role role = fetchRoleById(roleId)
if (role == null) {
continue
}
Boolean certified = certifiedRoleIds.contains(roleId)
addRole(user, role, certified)
}
}
private void addMissingGroups(User user, Set<String> expectedGroupIds) {
for (String groupId : expectedGroupIds) {
if (hasActiveGroup(user, groupId)) {
continue
}
Group group = fetchGroupById(groupId)
if (group == null) {
continue
}
addGroup(user, group)
}
}
private Role fetchCargoRoleByName(String cargoName) {
println "Looking for a job title by exact job title.: ${cargoName}"
RoleRabbitMQService roleMQService =
context.getBean(RoleRabbitMQService.class) as RoleRabbitMQService
RoleSearchBean searchBean = new RoleSearchBean()
searchBean.setDeepCopy(false)
EntitlementsCollection[] collections = [
EntitlementsCollection.CHILDRENS,
EntitlementsCollection.GROUPS
]
List<Role> roles =
roleMQService.findBeans(searchBean, collections, 0, Integer.MAX_VALUE)
if (CollectionUtils.isEmpty(roles)) {
println "No se encontraron roles."
return null
}
for (Role role : roles) {
if (role.getName() != null &&
role.getName().trim().equalsIgnoreCase(cargoName.trim())) {
println "Role found: ${role.getName()}"
return fetchRoleById(role.getId())
}
}
println "Role with name not found: ${cargoName}"
return null
}
private Role fetchRoleById(String roleId) {
if (!roleId) {
return null
}
RoleRabbitMQService roleMQService =
context.getBean(RoleRabbitMQService.class) as RoleRabbitMQService
RoleSearchBean searchBean = new RoleSearchBean()
searchBean.setDeepCopy(false)
searchBean.setKeySet(List.of(roleId))
EntitlementsCollection[] collections = [
EntitlementsCollection.CHILDRENS,
EntitlementsCollection.GROUPS
]
List<Role> roles =
roleMQService.findBeans(searchBean, collections, 0, 1)
if (CollectionUtils.isNotEmpty(roles)) {
return roles.get(0)
}
return null
}
private Group fetchGroupById(String groupId) {
if (!groupId) {
return null
}
GroupRabbitMQService groupMQService =
context.getBean(GroupRabbitMQService.class) as GroupRabbitMQService
GroupSearchBean searchBean = new GroupSearchBean()
searchBean.setKeySet(List.of(groupId))
List<Group> groups =
groupMQService.findBeans(searchBean, 0, 1)
if (CollectionUtils.isNotEmpty(groups)) {
return groups.get(0)
}
return null
}
private void addRole(User user, Role role, Boolean certified) {
if (role == null) {
return
}
Date startDate = getStartDate(user)
if (certified) {
user.addRole(role, Set.of("IS_CERTIFIED"), startDate, null)
} else {
user.addRole(role, null, startDate, null)
}
println "Role agregado: ${role.getName()}"
}
private void addGroup(User user, Group group) {
if (group == null) {
return
}
Date startDate = getStartDate(user)
user.addGroup(group, null, startDate, null)
println "Group agregado: ${group.getName()}"
}
private boolean hasActiveRole(User user, String roleId) {
if (CollectionUtils.isEmpty(user.getRoles())) {
return false
}
for (UserToRoleMembershipXref existingRole : user.getRoles()) {
if (existingRole.getEntityId() != null &&
existingRole.getEntityId().equals(roleId) &&
existingRole.getOperation() != AttributeOperationEnum.DELETE) {
return true
}
}
return false
}
private boolean hasActiveGroup(User user, String groupId) {
if (CollectionUtils.isEmpty(user.getGroups())) {
return false
}
for (UserToGroupMembershipXref existingGroup : user.getGroups()) {
if (existingGroup.getEntityId() != null &&
existingGroup.getEntityId().equals(groupId) &&
existingGroup.getOperation() != AttributeOperationEnum.DELETE) {
return true
}
}
return false
}
private Date getStartDate(User user) {
if (user.getStartDate() != null && user.getStartDate().after(new Date())) {
return user.getStartDate()
}
return new Date()
}
private void endDateAllRoles(User user) {
if (CollectionUtils.isEmpty(user.getRoles())) {
return
}
for (UserToRoleMembershipXref existingRole : user.getRoles()) {
existingRole.setEndDate(new Date())
existingRole.setOperation(AttributeOperationEnum.DELETE)
}
}
private void endDateAllGroups(User user) {
if (CollectionUtils.isEmpty(user.getGroups())) {
return
}
for (UserToGroupMembershipXref existingGroup : user.getGroups()) {
existingGroup.setEndDate(new Date())
existingGroup.setOperation(AttributeOperationEnum.DELETE)
}
}
}