diff --git a/ugsync/src/main/java/org/apache/ranger/ldapusersync/process/LdapUserGroupBuilder.java b/ugsync/src/main/java/org/apache/ranger/ldapusersync/process/LdapUserGroupBuilder.java index ae860f27517..e8379b21139 100644 --- a/ugsync/src/main/java/org/apache/ranger/ldapusersync/process/LdapUserGroupBuilder.java +++ b/ugsync/src/main/java/org/apache/ranger/ldapusersync/process/LdapUserGroupBuilder.java @@ -513,11 +513,13 @@ private long getUsers(boolean computeDeletes) throws Throwable { } if (config.isDeltaSyncEnabled()) { - extendedUserSearchFilter = "(objectclass=" + userObjectClass + ")(|(uSNChanged>=" + deltaSyncUserTime + ")(modifyTimestamp>=" + deltaSyncUserTimeStamp + "Z))"; + extendedUserSearchFilter = "(objectclass=" + userObjectClass + ")" + getDeltaSyncFilter(deltaSyncUserTime, deltaSyncUserTimeStamp); } else { extendedUserSearchFilter = "(objectclass=" + userObjectClass + ")"; } + LOG.debug("custom userSearchFilter = {}, extendedUserSearchFilter = {}", userSearchFilter, extendedUserSearchFilter); + LOG.info("extendedUserSearchFilter = {}", extendedUserSearchFilter); if (userSearchFilter != null && !userSearchFilter.trim().isEmpty()) { String customFilter = userSearchFilter.trim(); @@ -758,11 +760,12 @@ private long getGroups(boolean computeDeletes) throws Throwable { } if (config.isDeltaSyncEnabled()) { - extendedAllGroupsSearchFilter = "(&" + extendedGroupSearchFilter + "(|(uSNChanged>=" + deltaSyncGroupTime + ")(modifyTimestamp>=" + deltaSyncGroupTimeStamp + "Z)))"; + extendedAllGroupsSearchFilter = "(&" + extendedGroupSearchFilter + getDeltaSyncFilter(deltaSyncGroupTime, deltaSyncGroupTimeStamp) + ")"; } else { extendedAllGroupsSearchFilter = "(&" + extendedGroupSearchFilter + ")"; } + LOG.debug("custom groupSearchFilter = {}, extendedGroupSearchFilter = {}", groupSearchFilter, extendedGroupSearchFilter); LOG.info("extendedAllGroupsSearchFilter = {}", extendedAllGroupsSearchFilter); for (int ou = 0; ou < groupSearchBase.length; ou++) { @@ -909,6 +912,21 @@ private long getGroups(boolean computeDeletes) throws Throwable { return highestdeltaSyncGroupTime; } + private String getDeltaSyncFilter(long usnChangedTime, String modifyTimestamp) { + String result = ""; + String serverType = config.getProperty("ranger.usersync.ldap.deltasync.server.type", "").trim(); + + if (serverType.equalsIgnoreCase("ad")) { + result = "(uSNChanged>=" + usnChangedTime + ")"; + } else if (serverType.equalsIgnoreCase("ldap")) { + result = "(modifyTimestamp>=" + modifyTimestamp + "Z)"; + } else { + result = "(|(uSNChanged>=" + usnChangedTime + ")(modifyTimestamp>=" + modifyTimestamp + "Z))"; + } + + return result; + } + private void goUpGroupHierarchy(Set groups, int groupHierarchyLevels, String groupSName) { if (groupHierarchyLevels <= 0 || groups.isEmpty()) { return; diff --git a/ugsync/src/test/java/org/apache/ranger/ldapusersync/process/TestLdapUserGroupBuilder.java b/ugsync/src/test/java/org/apache/ranger/ldapusersync/process/TestLdapUserGroupBuilder.java index 0d677c38409..0fae39013c2 100644 --- a/ugsync/src/test/java/org/apache/ranger/ldapusersync/process/TestLdapUserGroupBuilder.java +++ b/ugsync/src/test/java/org/apache/ranger/ldapusersync/process/TestLdapUserGroupBuilder.java @@ -420,6 +420,97 @@ public void testU_user_config_fields_reflect_properties_and_timestamps_initializ assertEquals(STR_EPOCH, ts.get(b)); } + @Test + void testUG_getUsers_ad_filter_uses_uSNChanged_only() throws Throwable { + resetConfig(); + configureMinimalLdapConfig(); + UserGroupSyncConfig cfg = UserGroupSyncConfig.getInstance(); + cfg.setProperty("ranger.usersync.ldap.deltasync", "true"); + cfg.setProperty("ranger.usersync.ldap.deltasync.server.type", "ad"); + + LdapUserGroupBuilder b = new LdapUserGroupBuilder(); + b.init(); + + Field fGroupEnabled = LdapUserGroupBuilder.class.getDeclaredField("groupSearchEnabled"); + fGroupEnabled.setAccessible(true); + fGroupEnabled.setBoolean(b, false); + + Field gutF = LdapUserGroupBuilder.class.getDeclaredField("groupUserTable"); + Field srcUsersF = LdapUserGroupBuilder.class.getDeclaredField("sourceUsers"); + Field srcGroupsF = LdapUserGroupBuilder.class.getDeclaredField("sourceGroups"); + gutF.setAccessible(true); + srcUsersF.setAccessible(true); + srcGroupsF.setAccessible(true); + gutF.set(b, HashBasedTable.create()); + srcUsersF.set(b, new HashMap<>()); + srcGroupsF.set(b, new HashMap<>()); + + final String userDn = "cn=User1000,ou=people,dc=example,dc=com"; + Attributes attrs = new BasicAttributes(true); + attrs.put(new BasicAttribute("cn", "User1000")); + attrs.put(new BasicAttribute("uSNChanged", "2001")); + + class TestSearchResult extends SearchResult { + private final String nameInNs; + + TestSearchResult(String name, Attributes a, String nameInNs) { + super(name, null, a); + this.nameInNs = nameInNs; + } + + @Override + public String getNameInNamespace() { + return nameInNs; + } + } + + final SearchResult sr = new TestSearchResult(userDn, attrs, userDn); + + class SingleEnum implements NamingEnumeration { + private boolean consumed; + + @Override + public SearchResult next() { + consumed = true; + return sr; + } + + @Override + public boolean hasMore() { + return !consumed; + } + + @Override + public void close() {} + + @Override + public boolean hasMoreElements() { + return hasMore(); + } + + @Override + public SearchResult nextElement() { + return next(); + } + } + + try (MockedConstruction mocked = Mockito.mockConstruction(InitialLdapContext.class, (mock, ctx) -> { + Mockito.when(mock.search(Mockito.anyString(), Mockito.anyString(), Mockito.any(SearchControls.class))) + .thenReturn(new SingleEnum()); + Mockito.when(mock.getResponseControls()).thenReturn(null); + })) { + Method getUsers = LdapUserGroupBuilder.class.getDeclaredMethod("getUsers", boolean.class); + getUsers.setAccessible(true); + getUsers.invoke(b, false); + + Field extFilF = LdapUserGroupBuilder.class.getDeclaredField("extendedUserSearchFilter"); + extFilF.setAccessible(true); + String ext = (String) extFilF.get(b); + assertTrue(ext.contains("uSNChanged>="), ext); + assertFalse(ext.contains("modifyTimestamp>="), ext); + } + } + @Test public void testV_getUsers_processes_user_and_group_memberships() throws Throwable { resetConfig(); diff --git a/ugsync/src/test/resources/ranger-ugsync-site.xml b/ugsync/src/test/resources/ranger-ugsync-site.xml index 5ed504b8694..98a80ecf6c1 100644 --- a/ugsync/src/test/resources/ranger-ugsync-site.xml +++ b/ugsync/src/test/resources/ranger-ugsync-site.xml @@ -177,4 +177,16 @@ ranger.usersync.group.hierarchylevels 2 + + + ranger.usersync.ldap.deltasync.server.type + + Specifies the LDAP server type for delta sync attribute selection. + Accepted values: + - "AD" : Uses only 'uSNChanged' attribute for delta sync (Active Directory). + - "LDAP" : Uses only 'modifyTimestamp' attribute for delta sync (OpenLDAP). + - "" : Uses both 'uSNChanged' and 'modifyTimestamp' attributes (default behavior). + + +