From 3cacc6bde543dcd229841d8167c6a8ca44a04fcc Mon Sep 17 00:00:00 2001 From: Adam Ruzicka Date: Mon, 8 Apr 2024 17:07:51 +0200 Subject: [PATCH 1/5] Also find groups added through groupOfUniqueNames when looking up groups assigned to a user --- lib/ldap_fluff/posix_member_service.rb | 10 +++++++++- test/posix_member_services_test.rb | 20 ++++++++++++++++---- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/lib/ldap_fluff/posix_member_service.rb b/lib/ldap_fluff/posix_member_service.rb index 0be40c4..fbd8b3c 100644 --- a/lib/ldap_fluff/posix_member_service.rb +++ b/lib/ldap_fluff/posix_member_service.rb @@ -17,7 +17,7 @@ def find_user(uid, base_dn = @base) # note : this method is not particularly fast for large ldap systems def find_user_groups(uid) @ldap.search( - :filter => Net::LDAP::Filter.eq('memberuid', uid), + :filter => user_group_filter(uid), :base => @group_base, :attributes => ["cn"] ).map { |entry| entry[:cn][0] } end @@ -27,4 +27,12 @@ class UIDNotFoundException < LdapFluff::Error class GIDNotFoundException < LdapFluff::Error end + + private + + def user_group_filter(uid) + unique_filter = Net::LDAP::Filter.eq('uniquemember', "uid=#{uid},#{@base}") & + Net::LDAP::Filter.eq('objectClass', 'groupOfUniqueNames') + Net::LDAP::Filter.eq('memberuid', uid) | unique_filter + end end diff --git a/test/posix_member_services_test.rb b/test/posix_member_services_test.rb index 2a49fe4..2bc9d9a 100644 --- a/test/posix_member_services_test.rb +++ b/test/posix_member_services_test.rb @@ -19,20 +19,24 @@ def test_find_user def test_find_user_groups user = posix_group_payload - @ldap.expect(:search, user, [:filter => @ms.name_filter('john'), + username = 'john' + filter = @ms.send(:user_group_filter, username) + @ldap.expect(:search, user, [:filter => filter, :base => config.group_base, :attributes => ["cn"]]) @ms.ldap = @ldap - assert_equal ['broze'], @ms.find_user_groups('john') + assert_equal ['broze'], @ms.find_user_groups(username) @ldap.verify end def test_find_no_groups - @ldap.expect(:search, [], [:filter => @ms.name_filter("john"), + username = 'john' + filter = @ms.send(:user_group_filter, username) + @ldap.expect(:search, [], [:filter => filter, :base => config.group_base, :attributes => ["cn"]]) @ms.ldap = @ldap - assert_equal [], @ms.find_user_groups('john') + assert_equal [], @ms.find_user_groups(username) @ldap.verify end @@ -69,4 +73,12 @@ def test_group_doesnt_exists assert_raises(LdapFluff::Posix::MemberService::GIDNotFoundException) { @ms.find_group('broze') } @ldap.verify end + + def test_user_group_filter + username = 'john' + unique_filter = Net::LDAP::Filter.eq('uniquemember', "uid=#{username},#{config.base_dn}") & + Net::LDAP::Filter.eq('objectClass', 'groupOfUniqueNames') + expected = @ms.name_filter(username) | unique_filter + assert_equal expected, @ms.send(:user_group_filter, username) + end end From 907ac0d4247ff27785bc72fbdd5ccb0b17b0067f Mon Sep 17 00:00:00 2001 From: Adam Ruzicka Date: Tue, 9 Apr 2024 10:29:03 +0200 Subject: [PATCH 2/5] Look up user before looking for groups --- lib/ldap_fluff/posix_member_service.rb | 7 ++++--- test/lib/ldap_test_helper.rb | 2 +- test/posix_member_services_test.rb | 23 +++++++++++------------ 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/ldap_fluff/posix_member_service.rb b/lib/ldap_fluff/posix_member_service.rb index fbd8b3c..3301538 100644 --- a/lib/ldap_fluff/posix_member_service.rb +++ b/lib/ldap_fluff/posix_member_service.rb @@ -16,8 +16,9 @@ def find_user(uid, base_dn = @base) # return an ldap user with groups attached # note : this method is not particularly fast for large ldap systems def find_user_groups(uid) + user = find_user(uid).first @ldap.search( - :filter => user_group_filter(uid), + :filter => user_group_filter(uid, user[:dn].first), :base => @group_base, :attributes => ["cn"] ).map { |entry| entry[:cn][0] } end @@ -30,8 +31,8 @@ class GIDNotFoundException < LdapFluff::Error private - def user_group_filter(uid) - unique_filter = Net::LDAP::Filter.eq('uniquemember', "uid=#{uid},#{@base}") & + def user_group_filter(uid, user_dn) + unique_filter = Net::LDAP::Filter.eq('uniquemember', user_dn) & Net::LDAP::Filter.eq('objectClass', 'groupOfUniqueNames') Net::LDAP::Filter.eq('memberuid', uid) | unique_filter end diff --git a/test/lib/ldap_test_helper.rb b/test/lib/ldap_test_helper.rb index 1762513..20c542c 100644 --- a/test/lib/ldap_test_helper.rb +++ b/test/lib/ldap_test_helper.rb @@ -121,7 +121,7 @@ def netiq_group_payload end def posix_user_payload - [{ :cn => ["john"] }] + [{ :cn => ["john"], :dn => ["cn=john,ou=people,dc=internet,dc=com"] }] end def posix_group_payload diff --git a/test/posix_member_services_test.rb b/test/posix_member_services_test.rb index 2bc9d9a..8003fe3 100644 --- a/test/posix_member_services_test.rb +++ b/test/posix_member_services_test.rb @@ -18,10 +18,14 @@ def test_find_user end def test_find_user_groups - user = posix_group_payload + group = posix_group_payload + user = posix_user_payload username = 'john' - filter = @ms.send(:user_group_filter, username) - @ldap.expect(:search, user, [:filter => filter, + + @ldap.expect(:search, user, [:filter => @ms.name_filter(username), + :base => config.base_dn]) + filter = @ms.send(:user_group_filter, username, user.first[:dn].first) + @ldap.expect(:search, group, [:filter => filter, :base => config.group_base, :attributes => ["cn"]]) @ms.ldap = @ldap @@ -30,8 +34,11 @@ def test_find_user_groups end def test_find_no_groups + user = posix_user_payload username = 'john' - filter = @ms.send(:user_group_filter, username) + @ldap.expect(:search, user, [:filter => @ms.name_filter(username), + :base => config.base_dn]) + filter = @ms.send(:user_group_filter, username, user.first[:dn].first) @ldap.expect(:search, [], [:filter => filter, :base => config.group_base, :attributes => ["cn"]]) @@ -73,12 +80,4 @@ def test_group_doesnt_exists assert_raises(LdapFluff::Posix::MemberService::GIDNotFoundException) { @ms.find_group('broze') } @ldap.verify end - - def test_user_group_filter - username = 'john' - unique_filter = Net::LDAP::Filter.eq('uniquemember', "uid=#{username},#{config.base_dn}") & - Net::LDAP::Filter.eq('objectClass', 'groupOfUniqueNames') - expected = @ms.name_filter(username) | unique_filter - assert_equal expected, @ms.send(:user_group_filter, username) - end end From 683c0756377a5e352b8d842605db89ccc3ecbb15 Mon Sep 17 00:00:00 2001 From: Adam Ruzicka Date: Thu, 9 May 2024 17:04:54 +0200 Subject: [PATCH 3/5] Introduce a flag for enabling RFC4519 group membership --- README.rdoc | 2 ++ lib/ldap_fluff/config.rb | 5 +++-- lib/ldap_fluff/generic.rb | 1 + lib/ldap_fluff/posix.rb | 11 +++++++---- lib/ldap_fluff/posix_member_service.rb | 12 +++++++++--- 5 files changed, 22 insertions(+), 9 deletions(-) diff --git a/README.rdoc b/README.rdoc index 3e6ede1..6852672 100644 --- a/README.rdoc +++ b/README.rdoc @@ -53,6 +53,8 @@ Your global configuration must provide information about your LDAP host to funct group_base: # base DN for your LDAP groups, eg ou=Groups,dc=redhat,dc=com use_netgroups: # false by default, use true if you want to use netgroup triples, # supported only for server type :free_ipa and :posix + use_rfc4519_group_membership: # false by default, use true if you want to use group membership extensions from RFC4519. Typically you may want to use this if your LDAP server is based on 389DS. + # supported only for server type :posix server_type: # type of server. default == :posix. :active_directory, :posix, :free_ipa ad_domain: # domain for your users if using active directory, eg redhat.com service_user: # service account for authenticating LDAP calls. required unless you enable anon diff --git a/lib/ldap_fluff/config.rb b/lib/ldap_fluff/config.rb index ef09acc..cc359e2 100644 --- a/lib/ldap_fluff/config.rb +++ b/lib/ldap_fluff/config.rb @@ -4,7 +4,7 @@ class LdapFluff::Config ATTRIBUTES = %w[host port encryption base_dn group_base server_type service_user service_pass anon_queries attr_login search_filter - instrumentation_service use_netgroups].freeze + instrumentation_service use_netgroups use_rfc4519_group_membership].freeze ATTRIBUTES.each { |attr| attr_reader attr.to_sym } DEFAULT_CONFIG = { 'port' => 389, @@ -14,7 +14,8 @@ class LdapFluff::Config 'server_type' => :free_ipa, 'anon_queries' => false, 'instrumentation_service' => nil, - 'use_netgroups' => false }.freeze + 'use_netgroups' => false, + 'use_rfc4519_group_membership' => false }.freeze def initialize(config) raise ArgumentError unless config.respond_to?(:to_hash) diff --git a/lib/ldap_fluff/generic.rb b/lib/ldap_fluff/generic.rb index 0bf30d5..0bc3623 100644 --- a/lib/ldap_fluff/generic.rb +++ b/lib/ldap_fluff/generic.rb @@ -14,6 +14,7 @@ def initialize(config = {}) @base = config.base_dn @group_base = (config.group_base.empty? ? config.base_dn : config.group_base) @use_netgroups = config.use_netgroups + @use_rfc4519_group_membership = config.use_rfc4519_group_membership @member_service = create_member_service(config) end diff --git a/lib/ldap_fluff/posix.rb b/lib/ldap_fluff/posix.rb index be5e71b..1438fae 100644 --- a/lib/ldap_fluff/posix.rb +++ b/lib/ldap_fluff/posix.rb @@ -19,10 +19,13 @@ def users_from_search_results(search, method) filter = if @use_netgroups Net::LDAP::Filter.eq('objectClass', 'nisNetgroup') else - Net::LDAP::Filter.eq('objectClass', 'posixGroup') | - Net::LDAP::Filter.eq('objectClass', 'organizationalunit') | - Net::LDAP::Filter.eq('objectClass', 'groupOfUniqueNames') | - Net::LDAP::Filter.eq('objectClass', 'groupOfNames') + filter = Net::LDAP::Filter.eq('objectClass', 'posixGroup') + if @use_rfc4519_group_membership + filter = filter | + Net::LDAP::Filter.eq('objectClass', 'groupOfUniqueNames') | + Net::LDAP::Filter.eq('objectClass', 'groupOfNames') + end + filter end groups = @ldap.search(:base => search.dn, :filter => filter) members = groups.map { |group| group.send(method) }.flatten.uniq diff --git a/lib/ldap_fluff/posix_member_service.rb b/lib/ldap_fluff/posix_member_service.rb index 3301538..72a099d 100644 --- a/lib/ldap_fluff/posix_member_service.rb +++ b/lib/ldap_fluff/posix_member_service.rb @@ -4,6 +4,7 @@ class LdapFluff::Posix::MemberService < LdapFluff::GenericMemberService def initialize(ldap, config) @attr_login = (config.attr_login || 'memberuid') + @use_rfc4519_group_membership = config.use_rfc4519_group_membership super end @@ -32,8 +33,13 @@ class GIDNotFoundException < LdapFluff::Error private def user_group_filter(uid, user_dn) - unique_filter = Net::LDAP::Filter.eq('uniquemember', user_dn) & - Net::LDAP::Filter.eq('objectClass', 'groupOfUniqueNames') - Net::LDAP::Filter.eq('memberuid', uid) | unique_filter + by_member = Net::LDAP::Filter.eq('memberuid', uid) + return by_member unless @use_rfc4519_group_membership + + by_name = Net::LDAP::Filter.eq('member', user_dn) & + Net::LDAP::Filter.eq('objectClass', 'groupOfNames') + by_unique_name = Net::LDAP::Filter.eq('uniquemember', user_dn) & + Net::LDAP::Filter.eq('objectClass', 'groupOfUniqueNames') + by_member | by_name | by_unique_name end end From f1fb7dbea4e958cb99483575c80aa389fc50c3fe Mon Sep 17 00:00:00 2001 From: Adam Ruzicka Date: Wed, 23 Apr 2025 10:04:43 -0400 Subject: [PATCH 4/5] Add organizationalunit back and fix tests --- lib/ldap_fluff/posix.rb | 3 ++- test/netiq_test.rb | 4 +--- test/posix_test.rb | 4 +--- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/ldap_fluff/posix.rb b/lib/ldap_fluff/posix.rb index 1438fae..33f1b25 100644 --- a/lib/ldap_fluff/posix.rb +++ b/lib/ldap_fluff/posix.rb @@ -19,7 +19,8 @@ def users_from_search_results(search, method) filter = if @use_netgroups Net::LDAP::Filter.eq('objectClass', 'nisNetgroup') else - filter = Net::LDAP::Filter.eq('objectClass', 'posixGroup') + filter = Net::LDAP::Filter.eq('objectClass', 'posixGroup') | + Net::LDAP::Filter.eq('objectClass', 'organizationalunit') if @use_rfc4519_group_membership filter = filter | Net::LDAP::Filter.eq('objectClass', 'groupOfUniqueNames') | diff --git a/test/netiq_test.rb b/test/netiq_test.rb index c5cc678..d36d796 100644 --- a/test/netiq_test.rb +++ b/test/netiq_test.rb @@ -128,9 +128,7 @@ def test_find_users_in_nested_groups [nested_group], [{ :base => group.dn, :filter => Net::LDAP::Filter.eq('objectClass', 'posixGroup') | - Net::LDAP::Filter.eq('objectClass', 'organizationalunit') | - Net::LDAP::Filter.eq('objectClass', 'groupOfUniqueNames') | - Net::LDAP::Filter.eq('objectClass', 'groupOfNames') }]) + Net::LDAP::Filter.eq('objectClass', 'organizationalunit') }]) @netiq.ldap = @ldap md = Minitest::Mock.new diff --git a/test/posix_test.rb b/test/posix_test.rb index d95faa8..5bf896c 100644 --- a/test/posix_test.rb +++ b/test/posix_test.rb @@ -123,9 +123,7 @@ def test_find_users_in_nested_groups [nested_group], [{ :base => group.dn, :filter => Net::LDAP::Filter.eq('objectClass', 'posixGroup') | - Net::LDAP::Filter.eq('objectClass', 'organizationalunit') | - Net::LDAP::Filter.eq('objectClass', 'groupOfUniqueNames') | - Net::LDAP::Filter.eq('objectClass', 'groupOfNames')}]) + Net::LDAP::Filter.eq('objectClass', 'organizationalunit') }]) @posix.ldap = @ldap md = Minitest::Mock.new From fc6807df9e1d35cd5e4c0587250361955ee80f0d Mon Sep 17 00:00:00 2001 From: Adam Ruzicka Date: Wed, 23 Apr 2025 16:48:11 +0200 Subject: [PATCH 5/5] Add some tests --- test/posix_member_services_test.rb | 24 +++++++++++++++++++++++- test/posix_test.rb | 29 +++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/test/posix_member_services_test.rb b/test/posix_member_services_test.rb index 8003fe3..9d3c8b9 100644 --- a/test/posix_member_services_test.rb +++ b/test/posix_member_services_test.rb @@ -24,7 +24,29 @@ def test_find_user_groups @ldap.expect(:search, user, [:filter => @ms.name_filter(username), :base => config.base_dn]) - filter = @ms.send(:user_group_filter, username, user.first[:dn].first) + filter = Net::LDAP::Filter.eq('memberuid', username) + @ldap.expect(:search, group, [:filter => filter, + :base => config.group_base, + :attributes => ["cn"]]) + @ms.ldap = @ldap + assert_equal ['broze'], @ms.find_user_groups(username) + @ldap.verify + end + + def test_find_user_groups_rfc4519 + @ms.instance_variable_set('@use_rfc4519_group_membership', true) + + group = posix_group_payload + user = posix_user_payload + username = 'john' + + @ldap.expect(:search, user, [:filter => @ms.name_filter(username), + :base => config.base_dn]) + filter = [Net::LDAP::Filter.eq('memberuid', username), + Net::LDAP::Filter.eq('member', user.first[:dn].first) & Net::LDAP::Filter.eq('objectClass', 'groupOfNames'), + Net::LDAP::Filter.eq('uniquemember', user.first[:dn].first) & Net::LDAP::Filter.eq('objectClass', 'groupOfUniqueNames') + ].reduce(&:|) + @ldap.expect(:search, group, [:filter => filter, :base => config.group_base, :attributes => ["cn"]]) diff --git a/test/posix_test.rb b/test/posix_test.rb index 5bf896c..1448313 100644 --- a/test/posix_test.rb +++ b/test/posix_test.rb @@ -135,4 +135,33 @@ def test_find_users_in_nested_groups md.verify @ldap.verify end + + def test_find_users_in_nested_groups_with_rfc4519 + @posix.instance_variable_set('@use_rfc4519_group_membership', true) + + service_bind + group = Net::LDAP::Entry.new('CN=foremaners,DC=example,DC=com') + group[:memberuid] = ['katellers'] + nested_group = Net::LDAP::Entry.new('CN=katellers,CN=foremaners,DC=example,DC=com') + nested_group[:memberuid] = ['testuser'] + + @ldap.expect(:search, + [nested_group], + [{ :base => group.dn, + :filter => Net::LDAP::Filter.eq('objectClass', 'posixGroup') | + Net::LDAP::Filter.eq('objectClass', 'organizationalunit') | + Net::LDAP::Filter.eq('objectClass', 'groupOfUniqueNames') | + Net::LDAP::Filter.eq('objectClass', 'groupOfNames') + }]) + @posix.ldap = @ldap + + md = Minitest::Mock.new + 2.times { md.expect(:find_group, [group], ['foremaners']) } + @posix.member_service = md + + assert_equal @posix.users_for_gid('foremaners'), ['testuser'] + + md.verify + @ldap.verify + end end