Importing groups from application

When synchronizing groups to OpenIAM from the AD Powershell target system, the transformation script typically utilizes a member attribute. However, in some cases, another attribute needs to be retrieved, such as MemberOf, etc.

In such instances, the transformation and provisioning scripts will require modification to accommodate this requirement. Therefore, after the AD Groups are synchronized, the following steps are necessary.

  1. The groups need to display all the other groups they belong to using the MemberOf attribute.
  2. It is essential to ensure that when a user is granted access to Group A, they also become a member of all the groups that Group A is a part of.
  3. Similarly, when a user's access to Group A is revoked, their membership in all the groups that Group A is a part of should also be removed.

For this purpose, the Group transformation script must be updated to synchronize groups with the MemberOf attribute and establish a parent-child relationship in OpenIAM.

The process should function as follows:

  • Group A has Groups B, C, and D listed in the MemberOf attribute in AD.
  • After the groups are synced into OpenIAM, Groups B, C, and D will be considered child groups of Group A.

The transformation script used is provided below.

package org.openiam.sync.service.impl
import org.apache.commons.collections.CollectionUtils
import org.openiam.idm.searchbeans.GroupSearchBean
import org.openiam.idm.srvc.grp.dto.Group
import org.openiam.idm.srvc.synch.dto.LineObject
import org.openiam.sync.service.impl.service.AbstractGroupTransformScript
class ADGroupSampleTransformationScript extends AbstractGroupTransformScript {
@Override
int execute(LineObject rowObj, Group group) {
println "** - Group Transformation script called."
try {
group.setPolicyId("4000");
group.setMdTypeId("AD_GROUP");
populateObject(rowObj, group)
} catch (Exception ex) {
ex.printStackTrace();
println "** - Transformation script error."
return -1;
}
println "** - Transformation script completed."
return NO_DELETE
}
private void populateObject(LineObject rowObj, Group group) {
def columnMap = rowObj.columnMap
if (isNewUser) {
group.id = null
}
group.name = columnMap.get("cn")?.value
group.description = columnMap.get("description")?.value
if (group.getDescription() && group.getDescription().length() > 254) {
group.setDescription(group.getDescription().substring(0, 254));
}
group.status = "ACTIVE"
addGroupAttribute(group, "sAMAccountName", columnMap.get("sAMAccountName")?.value);
addGroupAttribute(group, "DistinguishedName", columnMap.get("DistinguishedName")?.value);
def scope = columnMap.get("groupScope")?.value
if (scope) {
switch (scope) {
case "0":
group.setAdGroupScopeId("Domain_Local")
break
case "1":
group.setAdGroupScopeId("Global")
break
case "2":
group.setAdGroupScopeId("Universal")
break
}
}
def category = columnMap.get("groupCategory")?.value
if (category) {
switch (category) {
case "0":
group.setAdGroupTypeId("DISTRIBUTION_GROUP")
break
case "1":
group.setAdGroupTypeId("SECURITY_GROUP")
break
}
}
// Get the members of the group from the row data
def memberOf = columnMap.get("memberOf")
if (memberOf) {
final Set<String> groupSet = new HashSet<>()
// Check if the members field is a multi-valued field or a single value
if (memberOf.isMultiValued()) {
groupSet.addAll(memberOf.getValueList())
} else {
groupSet.add(memberOf.getValue())
}
// Iterate over the member DNs and add them as child groups to the current group
groupSet.each{ dn ->
final Group g = getGroupByDn(dn);
if (g != null) {
addChildGroup(group,g.getId())
}
}
}
}
/**
* Retrieves a {@code Group} object based on its distinguished name (DN).
*
* This method first checks a cache for the presence of the group with the specified DN.
* If the group is found in the cache, it is returned immediately. Otherwise, a search
* is performed using the provided distinguished name. If a single group is found in the
* search results, it is added to the cache before being returned.
*
* @param dn The distinguished name of the group to retrieve.
* @return The {@code Group} object corresponding to the specified DN, or {@code null}
* if the group is not found.
*/
private Group getGroupByDn(final String dn) {
Group group = groupsByName.get(dn);
if (group == null) {
final GroupSearchBean groupSearchBean = new GroupSearchBean()
groupSearchBean.addAttribute("DistinguishedName", dn)
final List<Group> groupList = groupRabbitMQService.findBeans(groupSearchBean, 0, 1)
if (CollectionUtils.isNotEmpty(groupList) && groupList.size() == 1) {
groupsByName.put(dn, groupList.get(0));
group = groupList.get(0);
}
}
return group;
}
@Override
void init() {}
}

The preprocessor script to handle addition/removal of child groups need also be amended as follows.

import org.apache.commons.collections.CollectionUtils
import org.openiam.base.AttributeOperationEnum
import org.openiam.common.beans.mq.GroupRabbitMQService
import org.openiam.exception.BasicDataServiceException
import org.openiam.idm.provisioning.preprocessor.user.AbstractUserProvisioningPreProcessor
import org.openiam.idm.searchbeans.GroupSearchBean
import org.openiam.idm.srvc.auth.dto.Login
import org.openiam.idm.srvc.entitlements.EntitlementsCollection
import org.openiam.idm.srvc.grp.dto.Group
import org.openiam.idm.srvc.membership.dto.MembershipXref
import org.openiam.idm.srvc.user.dto.UserToGroupMembershipXref
import org.openiam.provision.dto.PasswordSync
import org.openiam.provision.dto.ProvisionUser
import org.openiam.provision.dto.user.request.DeleteUserProvisioningRequest
import org.springframework.beans.factory.annotation.Autowired
/**
* Pre-processor script that is used with the Provisioning service.
*/
public class ProvisionServicePreProcessor extends AbstractUserProvisioningPreProcessor {
@Autowired
GroupRabbitMQService grpService;
@Override
protected void add(ProvisionUser user) throws BasicDataServiceException {
println("ProvisionServicePreProcessor: add called.");
if(CollectionUtils.isNotEmpty(user.getGroups())){
List<String> grpIds = new ArrayList<>()
for(UserToGroupMembershipXref group : user.getGroups()) {
if(group.getOperation() == AttributeOperationEnum.ADD) {
grpIds.add(group.getEntityId());
}
}
if(CollectionUtils.isNotEmpty(grpIds)){
// Calling Method to add child group to the User
addChildGroups(user, grpIds,user.getGroups())
}
}
}
@Override
protected void modify(ProvisionUser user) throws BasicDataServiceException {
println("ProvisionServicePreProcessor: modify called.");
if(CollectionUtils.isNotEmpty(user.getGroups())){
List<String> grpIds = new ArrayList<>()
List<String> removalGrpIds = new ArrayList<>()
for(UserToGroupMembershipXref group : user.getGroups()) {
if(group.getOperation() == AttributeOperationEnum.ADD) {
grpIds.add(group.getEntityId());
}
if(group.getOperation() == AttributeOperationEnum.DELETE)
removalGrpIds.add(group.getEntityId())
}
if(CollectionUtils.isNotEmpty(grpIds)){
// Calling Method to add Child groups to the User
addChildGroups(user, grpIds,user.getGroups())
}
if(CollectionUtils.isNotEmpty(removalGrpIds)){
// Calling Method to remove Child groups from the User
removeChildGroups(user, removalGrpIds)
}
}
}
@Override
protected void enable(ProvisionUser user) throws BasicDataServiceException {
println("ProvisionServicePreProcessor: enable called.");
}
@Override
protected void disable(ProvisionUser user) throws BasicDataServiceException {
println("ProvisionServicePreProcessor: disable called.");
}
@Override
protected void delete(DeleteUserProvisioningRequest object, Login login) throws BasicDataServiceException {
println("ProvisionServicePreProcessor: delete called.");
}
@Override
protected void resume(ProvisionUser user) throws BasicDataServiceException {
println("ProvisionServicePreProcessor: resume called.");
}
@Override
protected void suspend(ProvisionUser user) throws BasicDataServiceException {
println("ProvisionServicePreProcessor: suspend called.");
}
@Override
protected void setPassword( PasswordSync passwordSync, Login login) throws BasicDataServiceException {
println("ProvisionServicePreProcessor: setPassword called.");
}
@Override
protected void resetPassword( PasswordSync passwordSync, Login login) throws BasicDataServiceException {
println("ProvisionServicePreProcessor: resetPassword called.");
}
// Method to Add Nested Groups if Parent Group is Added
void addChildGroups(ProvisionUser user, List<String> grpIds,Set<UserToGroupMembershipXref> list){
GroupSearchBean gsb = new GroupSearchBean()
gsb.setKeySet(grpIds)
// List of Immediate Child Groups of the Parent group to be added
List<Group> groupList = grpService.findBeans(gsb, EntitlementsCollection.CHILDRENS as EntitlementsCollection[],0,Integer.MAX_VALUE)
if(CollectionUtils.isNotEmpty(groupList)){
for (Group g : groupList){
Set<MembershipXref> xrefs = g.getChildGroups()
if(CollectionUtils.isNotEmpty(xrefs)){
List<String> childGroupIds = new ArrayList<>()
for (MembershipXref x : xrefs){
def membership = list.find({it -> it.getEntityId().equalsIgnoreCase(g.getId())})
user.addGroup(grpService.getGroup(x.getEntityId()),membership.getRightNames(),membership.getStartDate(),membership.getEndDate(),membership.getDescription())
println("ADDING GROUPS")
childGroupIds.add(x.getEntityId())
}
if(CollectionUtils.isNotEmpty(childGroupIds)){
// Recursive call to Add futher child groups
addChildGroups(user,childGroupIds,list)
}
}
}
}
}
// Method to Remove Nested Groups if Parent Group is Removed
void removeChildGroups(ProvisionUser user, List<String> grpIds){
GroupSearchBean gsb = new GroupSearchBean()
gsb.setKeySet(grpIds)
//List of Immediate Child Groups of the Parent Group to be removed
List<Group> groupList = grpService.findBeans(gsb, EntitlementsCollection.CHILDRENS as EntitlementsCollection[],0,Integer.MAX_VALUE)
if(CollectionUtils.isNotEmpty(groupList)){
for (Group g : groupList){
Set<MembershipXref> xrefs = g.getChildGroups()
if(CollectionUtils.isNotEmpty(xrefs)){
List<String> childGroupIds = new ArrayList<>()
for (MembershipXref x : xrefs){
user.removeGroup(x.getEntityId())
println("Removing GROUPS")
childGroupIds.add(x.getEntityId())
}
if(CollectionUtils.isNotEmpty(childGroupIds)){
// Recursive call to Remove further child groups
removeChildGroups(user,childGroupIds)
}
}
}
}
}
}