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..33f1b25 100644 --- a/lib/ldap_fluff/posix.rb +++ b/lib/ldap_fluff/posix.rb @@ -19,10 +19,14 @@ 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') | + Net::LDAP::Filter.eq('objectClass', 'organizationalunit') + 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 0be40c4..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 @@ -16,8 +17,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 => Net::LDAP::Filter.eq('memberuid', uid), + :filter => user_group_filter(uid, user[:dn].first), :base => @group_base, :attributes => ["cn"] ).map { |entry| entry[:cn][0] } end @@ -27,4 +29,17 @@ class UIDNotFoundException < LdapFluff::Error class GIDNotFoundException < LdapFluff::Error end + + private + + def user_group_filter(uid, user_dn) + 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 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/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_member_services_test.rb b/test/posix_member_services_test.rb index 2a49fe4..9d3c8b9 100644 --- a/test/posix_member_services_test.rb +++ b/test/posix_member_services_test.rb @@ -18,21 +18,54 @@ def test_find_user end def test_find_user_groups - user = posix_group_payload - @ldap.expect(:search, user, [:filter => @ms.name_filter('john'), + 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) + @ldap.expect(:search, group, [: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_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"]]) + @ms.ldap = @ldap + assert_equal ['broze'], @ms.find_user_groups(username) @ldap.verify end def test_find_no_groups - @ldap.expect(:search, [], [:filter => @ms.name_filter("john"), + user = posix_user_payload + username = 'john' + @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"]]) @ms.ldap = @ldap - assert_equal [], @ms.find_user_groups('john') + assert_equal [], @ms.find_user_groups(username) @ldap.verify end diff --git a/test/posix_test.rb b/test/posix_test.rb index d95faa8..1448313 100644 --- a/test/posix_test.rb +++ b/test/posix_test.rb @@ -119,13 +119,40 @@ def test_find_users_in_nested_groups 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') }]) + @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 + + 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')}]) + Net::LDAP::Filter.eq('objectClass', 'groupOfNames') + }]) @posix.ldap = @ldap md = Minitest::Mock.new