This is an automated email from the ASF dual-hosted git repository.

pradeepagrawal8184 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ranger.git


The following commit(s) were added to refs/heads/master by this push:
     new 6847eb7b9 RANGER-5110: Prevent entity soft deletion when LDAP DN is 
updated (#1162)
6847eb7b9 is described below

commit 6847eb7b977b1027cdf8de68557299bc21492690
Author: KRISHNA CHAITANYA MUTTEVI 
<[email protected]>
AuthorDate: Tue Sep 29 14:58:03 2026 +0530

    RANGER-5110: Prevent entity soft deletion when LDAP DN is updated (#1162)
    
    * RANGER-5110: Prevent entity soft deletion when LDAP DN is updated with 
proper syncing of U&G
    
    * RANGER-5110: Added the support for inter-syncsource dn updates & 
addressed reviewer comments
    
    * RANGER-5110: Addressed the review comments by moving the skip condition
    
    * RANGER-5110: Resolved the Merge Conflicts & Checkstyle error
    
    * RANGER-5110: Gate name-map DN removal on allowSyncSourceOverwrite
---
 .../unixusersync/config/UserGroupSyncConfig.java   |  13 +
 .../process/PolicyMgrUserGroupBuilder.java         |  81 ++++--
 .../config/TestUserGroupSyncConfig.java            |  18 ++
 .../process/TestPolicyMgrUserGroupBuilder.java     | 272 +++++++++++++++++++++
 ugsync/src/test/resources/ranger-ugsync-site.xml   |  12 +-
 5 files changed, 371 insertions(+), 25 deletions(-)

diff --git 
a/ugsync/src/main/java/org/apache/ranger/unixusersync/config/UserGroupSyncConfig.java
 
b/ugsync/src/main/java/org/apache/ranger/unixusersync/config/UserGroupSyncConfig.java
index 991748264..3b190e3c8 100644
--- 
a/ugsync/src/main/java/org/apache/ranger/unixusersync/config/UserGroupSyncConfig.java
+++ 
b/ugsync/src/main/java/org/apache/ranger/unixusersync/config/UserGroupSyncConfig.java
@@ -71,6 +71,7 @@ public class UserGroupSyncConfig {
     public static final  String UGSYNC_SERVER_HA_ENABLED_PARAM         = 
"ranger-ugsync.server.ha.enabled";
     public static final  String UGSYNC_NAME_VALIDATION_ENABLED         = 
"ranger.usersync.name.validation.enabled";
     public static final  String UGSYNC_SYNC_SOURCE_VALIDATION_ENABLED  = 
"ranger.usersync.syncsource.validation.enabled";
+    public static final  String UGSYNC_DN_VALIDATION_ENABLED           = 
"ranger.usersync.dn.validation.enabled";
     private static final Logger LOG                                    = 
LoggerFactory.getLogger(UserGroupSyncConfig.class);
     private static final String CORE_SITE_CONFIG_FILE                  = 
"core-site.xml";
     private static final String UGSYNC_SOURCE_CLASS_PARAM              = 
"ranger.usersync.source.impl.class";
@@ -180,6 +181,7 @@ public class UserGroupSyncConfig {
     private static final long    
UGSYNC_INIT_SLEEP_TIME_IN_MILLIS_BETWEEN_CYCLE_MIN_VALUE_FOR_HA = 5_000L;
     private static final boolean DEFAULT_UGSYNC_NAME_VALIDATION_ENABLED        
                  = false;
     private static final boolean DEFAULT_UGSYNC_SYNC_SOURCE_VALIDATION_ENABLED 
                  = true;
+    private static final boolean DEFAULT_UGSYNC_DN_VALIDATION_ENABLED          
                  = false;
 
     private static final String SYNC_SOURCE                         = 
"ranger.usersync.sync.source";
     private static final String LGSYNC_PAGED_RESULTS_ENABLED        = 
"ranger.usersync.pagedresultsenabled";
@@ -1405,6 +1407,17 @@ public boolean isSyncSourceValidationEnabled() {
         return isSyncSourceValidationEnabled;
     }
 
+    public boolean isDnValidationEnabled() {
+        boolean isDnValidationEnabled = DEFAULT_UGSYNC_DN_VALIDATION_ENABLED;
+        String  val                   = 
prop.getProperty(UGSYNC_DN_VALIDATION_ENABLED);
+
+        if (StringUtils.isNotEmpty(val)) {
+            isDnValidationEnabled = Boolean.parseBoolean(val);
+        }
+
+        return isDnValidationEnabled;
+    }
+
     public boolean isLargeGroupSyncEnabled() {
         boolean largeGroupSyncEnabled;
         String  val = prop.getProperty(LGSYNC_LDAP_LARGEGROUPSYNC_ENABLED);
diff --git 
a/ugsync/src/main/java/org/apache/ranger/unixusersync/process/PolicyMgrUserGroupBuilder.java
 
b/ugsync/src/main/java/org/apache/ranger/unixusersync/process/PolicyMgrUserGroupBuilder.java
index 2eb21755f..248e43df7 100644
--- 
a/ugsync/src/main/java/org/apache/ranger/unixusersync/process/PolicyMgrUserGroupBuilder.java
+++ 
b/ugsync/src/main/java/org/apache/ranger/unixusersync/process/PolicyMgrUserGroupBuilder.java
@@ -130,6 +130,7 @@ public class PolicyMgrUserGroupBuilder extends 
AbstractUserGroupSource implement
     private boolean isRangerCookieEnabled;
     private boolean isUserSyncNameValidationEnabled;
     private boolean isSyncSourceValidationEnabled;
+    private boolean isDnValidationEnabled;
     private MetricCacheUtil metricCacheUtil = MetricCacheUtil.getInstance();
     private String  recordsToPullPerCall = "10";
     private String  currentSyncSource;
@@ -179,6 +180,7 @@ public static void main(String[] args) throws Throwable {
     public synchronized void init() throws Throwable {
         isUserSyncNameValidationEnabled = 
config.isUserSyncNameValidationEnabled();
         isSyncSourceValidationEnabled   = 
config.isSyncSourceValidationEnabled();
+        isDnValidationEnabled           = config.isDnValidationEnabled();
         recordsToPullPerCall            = config.getMaxRecordsPerAPICall();
         policyMgrBaseUrl                = config.getPolicyManagerBaseURL();
         isMockRun                       = config.isMockRunEnabled();
@@ -754,18 +756,16 @@ private void computeGroupDelta(Map<String, Map<String, 
String>> sourceGroups) {
                     deltaGroups.put(groupName, curGroup);
                     noOfModifiedGroups++;
                     groupNameMap.put(groupDN, groupName);
-                } else {
-                    if (MapUtils.isNotEmpty(curGroupAttrs) && 
!StringUtils.equalsIgnoreCase(groupDN, curGroupDN)) { // skip update
-                        LOG.debug("[{}]: SyncSource update skipped, current 
group DN = {} new user DN  = {}", groupName, curGroupDN, groupDN);
-
-                        if (StringUtils.equalsIgnoreCase(curGroupAttrsStr, 
newGroupAttrsStr)) {
-                            groupNameMap.put(groupDN, groupName);
-                        }
+                } else if (MapUtils.isNotEmpty(curGroupAttrs) && 
!StringUtils.equalsIgnoreCase(groupDN, curGroupDN) && !isDnValidationEnabled) {
+                    LOG.debug("[{}]: SyncSource update skipped, current group 
DN = {} new group DN = {}", groupName, curGroupDN, groupDN);
 
-                        continue;
+                    if (StringUtils.equalsIgnoreCase(curGroupAttrsStr, 
newGroupAttrsStr)) {
+                        groupNameMap.put(groupDN, groupName);
                     }
+                } else {
+                    boolean allowSyncSourceOverwrite = 
!isSyncSourceValidationEnabled && isDnValidationEnabled;
 
-                    if (StringUtils.isEmpty(curSyncSource) || 
(!StringUtils.equalsIgnoreCase(curGroupAttrsStr, newGroupAttrsStr) && 
StringUtils.equalsIgnoreCase(curSyncSource, newSyncSource))) { // update
+                    if (StringUtils.isEmpty(curSyncSource) || 
(!StringUtils.equalsIgnoreCase(curGroupAttrsStr, newGroupAttrsStr) && 
(StringUtils.equalsIgnoreCase(curSyncSource, newSyncSource) || 
allowSyncSourceOverwrite))) {
                         if (StringUtils.isEmpty(curSyncSource)) {
                             LOG.debug("[{}]: SyncSource updated to {}, 
previously empty", groupName, newSyncSource);
                         } else {
@@ -777,6 +777,10 @@ private void computeGroupDelta(Map<String, Map<String, 
String>> sourceGroups) {
                         deltaGroups.put(groupName, curGroup);
                         noOfModifiedGroups++;
                         groupNameMap.put(groupDN, groupName);
+
+                        if (allowSyncSourceOverwrite) {
+                            groupNameMap.remove(curGroupDN);
+                        }
                     } else {
                         if (!StringUtils.equalsIgnoreCase(curSyncSource, 
newSyncSource)) {
                             LOG.debug("[{}]: Different sync source exists, 
update skipped!", groupName);
@@ -838,26 +842,21 @@ private void computeUserDelta(Map<String, Map<String, 
String>> sourceUsers) {
 
                 if (isStartupFlag && !isSyncSourceValidationEnabled && 
(!StringUtils.equalsIgnoreCase(curSyncSource, newSyncSource))) {
                     LOG.debug("[{}]: SyncSource updated to {}, previous value: 
{}", userName, newSyncSource, curSyncSource);
-
                     curUser = setOtherAttributes(curUser, newSyncSource, 
newUserAttrs, newUserAttrsStr);
 
                     curUser.setUserSource(SOURCE_EXTERNAL);
                     deltaUsers.put(userName, curUser);
-                    noOfModifiedGroups++;
+                    noOfModifiedUsers++;
                     userNameMap.put(userDN, userName);
-                } else {
-                    if (MapUtils.isNotEmpty(curUserAttrs) && 
!StringUtils.equalsIgnoreCase(userDN, curUserDN)) { // skip update
-                        // Same username with different DN already exists
-                        LOG.debug("[{}]: SyncSource update skipped, current 
user DN = {} new user DN  = {}", userName, curUserDN, userDN);
-
-                        if (StringUtils.equalsIgnoreCase(curUserAttrsStr, 
newUserAttrsStr)) {
-                            userNameMap.put(userDN, userName);
-                        }
-
-                        continue;
+                } else if (MapUtils.isNotEmpty(curUserAttrs) && 
!StringUtils.equalsIgnoreCase(userDN, curUserDN) && !isDnValidationEnabled) {
+                    LOG.debug("[{}]: SyncSource update skipped, current user 
DN = {} new user DN = {}", userName, curUserDN, userDN);
+                    if (StringUtils.equalsIgnoreCase(curUserAttrsStr, 
newUserAttrsStr)) {
+                        userNameMap.put(userDN, userName);
                     }
+                } else {
+                    boolean allowSyncSourceOverwrite = 
!isSyncSourceValidationEnabled && isDnValidationEnabled;
 
-                    if (StringUtils.isEmpty(curSyncSource) || 
(!StringUtils.equalsIgnoreCase(curUserAttrsStr, newUserAttrsStr) && 
StringUtils.equalsIgnoreCase(curSyncSource, newSyncSource))) { // update
+                    if (StringUtils.isEmpty(curSyncSource) || 
(!StringUtils.equalsIgnoreCase(curUserAttrsStr, newUserAttrsStr) && 
(StringUtils.equalsIgnoreCase(curSyncSource, newSyncSource) || 
allowSyncSourceOverwrite))) { // update
                         if (StringUtils.isEmpty(curSyncSource)) {
                             LOG.debug("[{}]: SyncSource updated to {}, 
previously empty", userName, newSyncSource);
                         } else {
@@ -870,6 +869,10 @@ private void computeUserDelta(Map<String, Map<String, 
String>> sourceUsers) {
                         deltaUsers.put(userName, curUser);
                         noOfModifiedUsers++;
                         userNameMap.put(userDN, userName);
+
+                        if (allowSyncSourceOverwrite) {
+                            userNameMap.remove(curUserDN);
+                        }
                     } else {
                         if (!StringUtils.equalsIgnoreCase(curSyncSource, 
newSyncSource)) {
                             LOG.debug("[{}]: Different sync source exists, 
update skipped!", userName);
@@ -1774,14 +1777,29 @@ private void computeDeletedGroups(Map<String, 
Map<String, String>> sourceGroups)
 
         deletedGroups = new HashMap<>();
 
+        Set<String> sourceGroupNames = new HashSet<>();
+        for (Map<String, String> attrs : sourceGroups.values()) {
+            String originalName = 
attrs.get(UgsyncCommonConstants.ORIGINAL_NAME);
+            if (StringUtils.isNotEmpty(originalName)) {
+                sourceGroupNames.add(groupNameTransform(originalName.trim()));
+            }
+        }
+
         // Check if the group from cache exists in the sourceGroups. If not, 
mark as deleted group.
-        for (XGroupInfo groupInfo : groupCache.values()) {
+        for (Map.Entry<String, XGroupInfo> entry : groupCache.entrySet()) {
+            String              groupName       = entry.getKey();
+            XGroupInfo          groupInfo       = entry.getValue();
             Map<String, String> groupOtherAttrs = groupInfo.getOtherAttrsMap();
             String              groupDN         = groupOtherAttrs != null ? 
groupOtherAttrs.get(UgsyncCommonConstants.FULL_NAME) : null;
 
             if (StringUtils.isNotEmpty(groupDN) && 
!sourceGroups.containsKey(groupDN)
                     && 
StringUtils.equalsIgnoreCase(groupOtherAttrs.get(UgsyncCommonConstants.SYNC_SOURCE),
 currentSyncSource) &&
                     
StringUtils.equalsIgnoreCase(groupOtherAttrs.get(UgsyncCommonConstants.LDAP_URL),
 ldapUrl)) {
+                if (sourceGroupNames.contains(groupName)) {
+                    LOG.info("group {} with different DN exists. Skip and wait 
for it to be updated.", groupName);
+                    continue;
+                }
+
                 if (!ISHIDDEN.equals(groupInfo.getIsVisible())) {
                     groupInfo.setIsVisible(ISHIDDEN);
                     deletedGroups.put(groupInfo.getName(), groupInfo);
@@ -1893,14 +1911,29 @@ private void computeDeletedUsers(Map<String, 
Map<String, String>> sourceUsers) {
 
         deletedUsers = new HashMap<>();
 
+        Set<String> sourceUserNames = new HashSet<>();
+        for (Map<String, String> attrs : sourceUsers.values()) {
+            String originalName = 
attrs.get(UgsyncCommonConstants.ORIGINAL_NAME);
+            if (StringUtils.isNotEmpty(originalName)) {
+                sourceUserNames.add(userNameTransform(originalName.trim()));
+            }
+        }
+
         // Check if the group from cache exists in the sourceGroups. If not, 
mark as deleted group.
-        for (XUserInfo userInfo : userCache.values()) {
+        for (Map.Entry<String, XUserInfo> entry : userCache.entrySet()) {
+            String               userName = entry.getKey();
+            XUserInfo           userInfo = entry.getValue();
             Map<String, String> userOtherAttrs = userInfo.getOtherAttrsMap();
             String              userDN         = userOtherAttrs != null ? 
userOtherAttrs.get(UgsyncCommonConstants.FULL_NAME) : null;
 
             if (StringUtils.isNotEmpty(userDN) && 
!sourceUsers.containsKey(userDN)
                     && 
StringUtils.equalsIgnoreCase(userOtherAttrs.get(UgsyncCommonConstants.SYNC_SOURCE),
 currentSyncSource)
                     && 
StringUtils.equalsIgnoreCase(userOtherAttrs.get(UgsyncCommonConstants.LDAP_URL),
 ldapUrl)) {
+                if (sourceUserNames.contains(userName)) {
+                    LOG.info("user {} with different DN exists. Skip and wait 
for it to be updated.", userName);
+                    continue;
+                }
+
                 if (!ISHIDDEN.equals(userInfo.getIsVisible())) {
                     userInfo.setIsVisible(ISHIDDEN);
                     deletedUsers.put(userInfo.getName(), userInfo);
diff --git 
a/ugsync/src/test/java/org/apache/ranger/unixusersync/config/TestUserGroupSyncConfig.java
 
b/ugsync/src/test/java/org/apache/ranger/unixusersync/config/TestUserGroupSyncConfig.java
index 7a09ad431..7563d964a 100644
--- 
a/ugsync/src/test/java/org/apache/ranger/unixusersync/config/TestUserGroupSyncConfig.java
+++ 
b/ugsync/src/test/java/org/apache/ranger/unixusersync/config/TestUserGroupSyncConfig.java
@@ -30,6 +30,7 @@
 import java.nio.file.Path;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -129,4 +130,21 @@ public void 
testE_getUserSyncMetricsFileName_prefersConfiguredOrLogdir_thenDefau
         String p4 = cfg.getUserSyncMetricsFileName();
         assertNotNull(p4);
     }
+
+    @Test
+    public void testF_isDnValidationEnabled_defaultsAndParses() {
+        UserGroupSyncConfig cfg = UserGroupSyncConfig.getInstance();
+
+        cfg.setProperty(UserGroupSyncConfig.UGSYNC_DN_VALIDATION_ENABLED, 
"true");
+        assertTrue(cfg.isDnValidationEnabled());
+
+        cfg.setProperty(UserGroupSyncConfig.UGSYNC_DN_VALIDATION_ENABLED, 
"TRUE");
+        assertTrue(cfg.isDnValidationEnabled());
+
+        cfg.setProperty(UserGroupSyncConfig.UGSYNC_DN_VALIDATION_ENABLED, 
"false");
+        assertFalse(cfg.isDnValidationEnabled());
+
+        cfg.setProperty(UserGroupSyncConfig.UGSYNC_DN_VALIDATION_ENABLED, "");
+        assertFalse(cfg.isDnValidationEnabled());
+    }
 }
diff --git 
a/ugsync/src/test/java/org/apache/ranger/unixusersync/process/TestPolicyMgrUserGroupBuilder.java
 
b/ugsync/src/test/java/org/apache/ranger/unixusersync/process/TestPolicyMgrUserGroupBuilder.java
index 32da0b916..66b5622f7 100644
--- 
a/ugsync/src/test/java/org/apache/ranger/unixusersync/process/TestPolicyMgrUserGroupBuilder.java
+++ 
b/ugsync/src/test/java/org/apache/ranger/unixusersync/process/TestPolicyMgrUserGroupBuilder.java
@@ -1128,4 +1128,276 @@ public Response post(String relativeUrl, Map<String, 
String> params, Object obj,
             return null;
         }
     }
+
+    @Test
+    public void testAK_computeUserDelta_dnValidationEnabled_ouMoveUpdates() 
throws Exception {
+        PolicyMgrUserGroupBuilder builder = new PolicyMgrUserGroupBuilder();
+
+        String oldDn = "cn=john,ou=People,dc=example,dc=com";
+        String newDn = "cn=john,ou=Moved,dc=example,dc=com";
+
+        Map<String, XUserInfo> userCache = new HashMap<>();
+        XUserInfo              existing  = new XUserInfo();
+        existing.setName("john");
+        existing.setSyncSource("LDAP/AD");
+        Map<String, String> oldAttrs = new HashMap<>();
+        oldAttrs.put(UgsyncCommonConstants.ORIGINAL_NAME, "john");
+        oldAttrs.put(UgsyncCommonConstants.FULL_NAME, oldDn);
+        oldAttrs.put(UgsyncCommonConstants.SYNC_SOURCE, "LDAP/AD");
+        existing.setOtherAttrsMap(oldAttrs);
+        existing.setOtherAttributes(JsonUtils.objectToJson(oldAttrs));
+        userCache.put("john", existing);
+
+        setPrivate(builder, "userCache", userCache);
+        setPrivate(builder, "userNameMap", new HashMap<String, String>());
+        setPrivate(builder, "policyMgrUserName", "rangerusersync");
+        setPrivate(builder, "isStartupFlag", false);
+        setPrivate(builder, "isDnValidationEnabled", true);
+        setPrivate(builder, "isSyncSourceValidationEnabled", true);
+
+        Map<String, String> newAttrs = new HashMap<>();
+        newAttrs.put(UgsyncCommonConstants.ORIGINAL_NAME, "john");
+        newAttrs.put(UgsyncCommonConstants.FULL_NAME, newDn);
+        newAttrs.put(UgsyncCommonConstants.SYNC_SOURCE, "LDAP/AD");
+
+        Map<String, Map<String, String>> source = new HashMap<>();
+        source.put(newDn, newAttrs);
+
+        Method compute = 
PolicyMgrUserGroupBuilder.class.getDeclaredMethod("computeUserDelta", 
Map.class);
+        compute.setAccessible(true);
+        compute.invoke(builder, source);
+
+        Map<String, XUserInfo> deltaUsers  = getPrivate(builder, "deltaUsers", 
Map.class);
+        Map<String, String>    userNameMap = getPrivate(builder, 
"userNameMap", Map.class);
+
+        assertTrue(deltaUsers.containsKey("john"));
+        assertEquals("john", userNameMap.get(newDn));
+        assertFalse(userNameMap.containsKey(oldDn));
+    }
+
+    @Test
+    public void testAL_computeUserDelta_dnValidationDisabled_skipsOuMove() 
throws Exception {
+        PolicyMgrUserGroupBuilder builder = new PolicyMgrUserGroupBuilder();
+
+        String oldDn = "cn=john,ou=People,dc=example,dc=com";
+        String newDn = "cn=john,ou=Moved,dc=example,dc=com";
+
+        Map<String, XUserInfo> userCache = new HashMap<>();
+        XUserInfo              existing  = new XUserInfo();
+        existing.setName("john");
+        existing.setSyncSource("LDAP/AD");
+        Map<String, String> oldAttrs = new HashMap<>();
+        oldAttrs.put(UgsyncCommonConstants.ORIGINAL_NAME, "john");
+        oldAttrs.put(UgsyncCommonConstants.FULL_NAME, oldDn);
+        oldAttrs.put(UgsyncCommonConstants.SYNC_SOURCE, "LDAP/AD");
+        existing.setOtherAttrsMap(oldAttrs);
+        existing.setOtherAttributes(JsonUtils.objectToJson(oldAttrs));
+        userCache.put("john", existing);
+
+        setPrivate(builder, "userCache", userCache);
+        setPrivate(builder, "userNameMap", new HashMap<String, String>());
+        setPrivate(builder, "policyMgrUserName", "rangerusersync");
+        setPrivate(builder, "isStartupFlag", false);
+        setPrivate(builder, "isDnValidationEnabled", false);
+        setPrivate(builder, "isSyncSourceValidationEnabled", true);
+
+        Map<String, String> newAttrs = new HashMap<>();
+        newAttrs.put(UgsyncCommonConstants.ORIGINAL_NAME, "john");
+        newAttrs.put(UgsyncCommonConstants.FULL_NAME, oldDn);
+        newAttrs.put(UgsyncCommonConstants.SYNC_SOURCE, "LDAP/AD");
+
+        Map<String, Map<String, String>> source = new HashMap<>();
+        source.put(newDn, newAttrs);
+
+        Method compute = 
PolicyMgrUserGroupBuilder.class.getDeclaredMethod("computeUserDelta", 
Map.class);
+        compute.setAccessible(true);
+        compute.invoke(builder, source);
+
+        Map<String, XUserInfo> deltaUsers = getPrivate(builder, "deltaUsers", 
Map.class);
+        assertFalse(deltaUsers.containsKey("john"));
+    }
+
+    @Test
+    public void 
testAM_computeUserDelta_startupSyncSourceChange_dnValidationDisabled_updates() 
throws Exception {
+        PolicyMgrUserGroupBuilder builder = new PolicyMgrUserGroupBuilder();
+
+        String oldDn = "cn=john,ou=People,dc=example,dc=com";
+        String newDn = "cn=john,ou=Moved,dc=example,dc=com";
+
+        Map<String, XUserInfo> userCache = new HashMap<>();
+        XUserInfo              existing  = new XUserInfo();
+        existing.setName("john");
+        existing.setSyncSource("Unix");
+        Map<String, String> oldAttrs = new HashMap<>();
+        oldAttrs.put(UgsyncCommonConstants.ORIGINAL_NAME, "john");
+        oldAttrs.put(UgsyncCommonConstants.FULL_NAME, oldDn);
+        oldAttrs.put(UgsyncCommonConstants.SYNC_SOURCE, "Unix");
+        existing.setOtherAttrsMap(oldAttrs);
+        existing.setOtherAttributes(JsonUtils.objectToJson(oldAttrs));
+        userCache.put("john", existing);
+
+        setPrivate(builder, "userCache", userCache);
+        setPrivate(builder, "userNameMap", new HashMap<String, String>());
+        setPrivate(builder, "policyMgrUserName", "rangerusersync");
+        setPrivate(builder, "isStartupFlag", true);
+        setPrivate(builder, "isSyncSourceValidationEnabled", false);
+        setPrivate(builder, "isDnValidationEnabled", false);
+
+        Map<String, String> newAttrs = new HashMap<>();
+        newAttrs.put(UgsyncCommonConstants.ORIGINAL_NAME, "john");
+        newAttrs.put(UgsyncCommonConstants.FULL_NAME, newDn);
+        newAttrs.put(UgsyncCommonConstants.SYNC_SOURCE, "LDAP/AD");
+
+        Map<String, Map<String, String>> source = new HashMap<>();
+        source.put(newDn, newAttrs);
+
+        Method compute = 
PolicyMgrUserGroupBuilder.class.getDeclaredMethod("computeUserDelta", 
Map.class);
+        compute.setAccessible(true);
+        compute.invoke(builder, source);
+
+        Map<String, XUserInfo> deltaUsers  = getPrivate(builder, "deltaUsers", 
Map.class);
+        Map<String, String>    userNameMap = getPrivate(builder, 
"userNameMap", Map.class);
+
+        assertTrue(deltaUsers.containsKey("john"));
+        assertEquals("john", userNameMap.get(newDn));
+    }
+
+    @Test
+    public void testAN_computeGroupDelta_dnValidationEnabled_ouMoveUpdates() 
throws Exception {
+        UserGroupSyncConfig cfg = UserGroupSyncConfig.getInstance();
+        cfg.setProperty(UserGroupSyncConfig.UGSYNC_NAME_VALIDATION_ENABLED, 
"false");
+
+        PolicyMgrUserGroupBuilder builder = new PolicyMgrUserGroupBuilder();
+
+        String oldDn = "cn=engineering,ou=Groups,dc=example,dc=com";
+        String newDn = "cn=engineering,ou=Moved,dc=example,dc=com";
+
+        Map<String, XGroupInfo> groupCache = new HashMap<>();
+        XGroupInfo                existing   = new XGroupInfo();
+        existing.setName("engineering");
+        existing.setSyncSource("LDAP/AD");
+        Map<String, String> oldAttrs = new HashMap<>();
+        oldAttrs.put(UgsyncCommonConstants.ORIGINAL_NAME, "engineering");
+        oldAttrs.put(UgsyncCommonConstants.FULL_NAME, oldDn);
+        oldAttrs.put(UgsyncCommonConstants.SYNC_SOURCE, "LDAP/AD");
+        existing.setOtherAttrsMap(oldAttrs);
+        existing.setOtherAttributes(JsonUtils.objectToJson(oldAttrs));
+        groupCache.put("engineering", existing);
+
+        setPrivate(builder, "groupCache", groupCache);
+        setPrivate(builder, "groupNameMap", new HashMap<String, String>());
+        setPrivate(builder, "isStartupFlag", false);
+        setPrivate(builder, "isDnValidationEnabled", true);
+        setPrivate(builder, "isSyncSourceValidationEnabled", true);
+
+        Map<String, String> newAttrs = new HashMap<>();
+        newAttrs.put(UgsyncCommonConstants.ORIGINAL_NAME, "engineering");
+        newAttrs.put(UgsyncCommonConstants.FULL_NAME, newDn);
+        newAttrs.put(UgsyncCommonConstants.SYNC_SOURCE, "LDAP/AD");
+
+        Map<String, Map<String, String>> source = new HashMap<>();
+        source.put(newDn, newAttrs);
+
+        Method compute = 
PolicyMgrUserGroupBuilder.class.getDeclaredMethod("computeGroupDelta", 
Map.class);
+        compute.setAccessible(true);
+        compute.invoke(builder, source);
+
+        Map<String, XGroupInfo> deltaGroups  = getPrivate(builder, 
"deltaGroups", Map.class);
+        Map<String, String>     groupNameMap = getPrivate(builder, 
"groupNameMap", Map.class);
+
+        assertTrue(deltaGroups.containsKey("engineering"));
+        assertEquals("engineering", groupNameMap.get(newDn));
+        assertFalse(groupNameMap.containsKey(oldDn));
+    }
+
+    @Test
+    public void testAO_computeGroupDelta_dnValidationDisabled_skipsOuMove() 
throws Exception {
+        UserGroupSyncConfig cfg = UserGroupSyncConfig.getInstance();
+        cfg.setProperty(UserGroupSyncConfig.UGSYNC_NAME_VALIDATION_ENABLED, 
"false");
+
+        PolicyMgrUserGroupBuilder builder = new PolicyMgrUserGroupBuilder();
+
+        String oldDn = "cn=engineering,ou=Groups,dc=example,dc=com";
+        String newDn = "cn=engineering,ou=Moved,dc=example,dc=com";
+
+        Map<String, XGroupInfo> groupCache = new HashMap<>();
+        XGroupInfo                existing   = new XGroupInfo();
+        existing.setName("engineering");
+        existing.setSyncSource("LDAP/AD");
+        Map<String, String> oldAttrs = new HashMap<>();
+        oldAttrs.put(UgsyncCommonConstants.ORIGINAL_NAME, "engineering");
+        oldAttrs.put(UgsyncCommonConstants.FULL_NAME, oldDn);
+        oldAttrs.put(UgsyncCommonConstants.SYNC_SOURCE, "LDAP/AD");
+        existing.setOtherAttrsMap(oldAttrs);
+        existing.setOtherAttributes(JsonUtils.objectToJson(oldAttrs));
+        groupCache.put("engineering", existing);
+
+        setPrivate(builder, "groupCache", groupCache);
+        setPrivate(builder, "groupNameMap", new HashMap<String, String>());
+        setPrivate(builder, "isStartupFlag", false);
+        setPrivate(builder, "isDnValidationEnabled", false);
+        setPrivate(builder, "isSyncSourceValidationEnabled", true);
+
+        Map<String, String> newAttrs = new HashMap<>();
+        newAttrs.put(UgsyncCommonConstants.ORIGINAL_NAME, "engineering");
+        newAttrs.put(UgsyncCommonConstants.FULL_NAME, oldDn);
+        newAttrs.put(UgsyncCommonConstants.SYNC_SOURCE, "LDAP/AD");
+
+        Map<String, Map<String, String>> source = new HashMap<>();
+        source.put(newDn, newAttrs);
+
+        Method compute = 
PolicyMgrUserGroupBuilder.class.getDeclaredMethod("computeGroupDelta", 
Map.class);
+        compute.setAccessible(true);
+        compute.invoke(builder, source);
+
+        Map<String, XGroupInfo> deltaGroups = getPrivate(builder, 
"deltaGroups", Map.class);
+        assertFalse(deltaGroups.containsKey("engineering"));
+    }
+
+    @Test
+    public void 
testAP_computeGroupDelta_startupSyncSourceChange_dnValidationDisabled_skips() 
throws Exception {
+        UserGroupSyncConfig cfg = UserGroupSyncConfig.getInstance();
+        cfg.setProperty(UserGroupSyncConfig.UGSYNC_NAME_VALIDATION_ENABLED, 
"false");
+
+        PolicyMgrUserGroupBuilder builder = new PolicyMgrUserGroupBuilder();
+
+        String oldDn = "cn=engineering,ou=Groups,dc=example,dc=com";
+        String newDn = "cn=engineering,ou=Moved,dc=example,dc=com";
+
+        Map<String, XGroupInfo> groupCache = new HashMap<>();
+        XGroupInfo                existing   = new XGroupInfo();
+        existing.setName("engineering");
+        existing.setSyncSource("Unix");
+        Map<String, String> oldAttrs = new HashMap<>();
+        oldAttrs.put(UgsyncCommonConstants.ORIGINAL_NAME, "engineering");
+        oldAttrs.put(UgsyncCommonConstants.FULL_NAME, oldDn);
+        oldAttrs.put(UgsyncCommonConstants.SYNC_SOURCE, "Unix");
+        existing.setOtherAttrsMap(oldAttrs);
+        existing.setOtherAttributes(JsonUtils.objectToJson(oldAttrs));
+        groupCache.put("engineering", existing);
+
+        setPrivate(builder, "groupCache", groupCache);
+        setPrivate(builder, "groupNameMap", new HashMap<String, String>());
+        setPrivate(builder, "isStartupFlag", true);
+        setPrivate(builder, "isSyncSourceValidationEnabled", false);
+        setPrivate(builder, "isDnValidationEnabled", false);
+
+        Map<String, String> newAttrs = new HashMap<>();
+        newAttrs.put(UgsyncCommonConstants.ORIGINAL_NAME, "engineering");
+        newAttrs.put(UgsyncCommonConstants.FULL_NAME, newDn);
+        newAttrs.put(UgsyncCommonConstants.SYNC_SOURCE, "LDAP/AD");
+
+        Map<String, Map<String, String>> source = new HashMap<>();
+        source.put(newDn, newAttrs);
+
+        Method compute = 
PolicyMgrUserGroupBuilder.class.getDeclaredMethod("computeGroupDelta", 
Map.class);
+        compute.setAccessible(true);
+        compute.invoke(builder, source);
+
+        Map<String, XGroupInfo> deltaGroups  = getPrivate(builder, 
"deltaGroups", Map.class);
+        Map<String, String>     groupNameMap = getPrivate(builder, 
"groupNameMap", Map.class);
+
+        assertTrue(deltaGroups.containsKey("engineering"));
+        assertEquals("engineering", groupNameMap.get(newDn));
+    }
 }
diff --git a/ugsync/src/test/resources/ranger-ugsync-site.xml 
b/ugsync/src/test/resources/ranger-ugsync-site.xml
index 98a80ecf6..f7ec125e2 100644
--- a/ugsync/src/test/resources/ranger-ugsync-site.xml
+++ b/ugsync/src/test/resources/ranger-ugsync-site.xml
@@ -188,5 +188,15 @@
             - ""     : Uses both 'uSNChanged' and 'modifyTimestamp' attributes 
(default behavior).
         </description>
     </property>
-
+    <property>
+        <name>ranger.usersync.dn.validation.enabled</name>
+        <value></value>
+        <description>
+            Controls whether LDAP DN changes (e.g. OU moves) are synced to 
Ranger.
+            Accepted values:
+            - true  : DN changes are allowed and synced
+            - false : DN mismatch causes update to be skipped
+            - empty : treated as false (default); DN changes are skipped
+        </description>
+    </property>
   </configuration>

Reply via email to