diff --git a/lib/ldap_fluff/active_directory.rb b/lib/ldap_fluff/active_directory.rb index 999b8f3..7aa05bc 100644 --- a/lib/ldap_fluff/active_directory.rb +++ b/lib/ldap_fluff/active_directory.rb @@ -44,6 +44,13 @@ def users_from_search_results(search, method) end end + # In AD, the relationship between a user account and the "Primary Group" for that account + # is not included in the member and memberof attributes. + if search.respond_to? 'primarygrouptoken' + primary_users = @ldap.search(:base => @ldap.base, :filter => Net::LDAP::Filter.eq('primarygroupid',search['primarygrouptoken'].first)) + users += primary_users.map { |user| @member_service.get_login_from_entry(user) } + end + users.flatten.uniq end diff --git a/lib/ldap_fluff/ad_member_service.rb b/lib/ldap_fluff/ad_member_service.rb index ae1f42c..aff8a49 100644 --- a/lib/ldap_fluff/ad_member_service.rb +++ b/lib/ldap_fluff/ad_member_service.rb @@ -8,6 +8,12 @@ def initialize(ldap, config) super end + def find_group(gid) + group = @ldap.search(:filter => group_filter(gid), :base => @group_base, :attributes => ['*','primaryGroupToken']) + raise self.class::GIDNotFoundException if (group.nil? || group.empty?) + group + end + # get a list [] of ldap groups for a given user # in active directory, this means a recursive lookup def find_user_groups(uid) @@ -19,9 +25,20 @@ def find_user_groups(uid) def _groups_from_ldap_data(payload) data = [] if !payload.nil? - first_level = payload[:memberof] - total_groups, _ = _walk_group_ancestry(first_level, first_level) - data = (get_groups(first_level + total_groups)).uniq + first_level = payload[:memberof] + normal_groups, _ = _walk_group_ancestry(first_level, first_level) + # In AD, a user's primary group is not included in the memberOf list, and must be handled separately. + # By default, a new user's primary group is 'Domain Users' + primary_groups = [] + primary_first_level = [] + if !payload[:primarygroupid].nil? + domain_sid = _get_sid_string(payload[:objectsid].first).split('-')[0..-2].join('-') + primary_sid = domain_sid + '-' + payload[:primarygroupid].first + primary_group = @ldap.search(:filter => Net::LDAP::Filter.eq('objectsid', primary_sid), :base => @group_base, :attributes => ['memberof']).first + primary_first_level = primary_group[:dn] + primary_groups, _ = _walk_group_ancestry(primary_first_level, primary_first_level) + end + data = (get_groups(first_level + normal_groups + primary_first_level + primary_groups)).uniq end data end @@ -43,6 +60,23 @@ def _walk_group_ancestry(group_dns = [], known_groups = []) [set, known_groups] end + def _get_sid_string(sid_bin) + sid = [] + + # Byte 1: SID structure revision number (always 1 so far...) + sid << sid_bin[0].unpack("H2").first.to_i + + # Skip byte 2 + # Bytes 3-8: Identifier Authority + sid << sid_bin[2,6].unpack("H*").first.to_i + + # Remaining bytes: list of unsigned, 32-bit, little-endian ints + sid += sid_bin.unpack("@8V*") + + # Put it all together. + "S-" + sid.join('-') + end + def class_filter Net::LDAP::Filter.eq("objectclass", "group") end diff --git a/test/ad_member_services_test.rb b/test/ad_member_services_test.rb index c58afc8..58cd4c1 100644 --- a/test/ad_member_services_test.rb +++ b/test/ad_member_services_test.rb @@ -15,7 +15,7 @@ def basic_user end def basic_group - @ldap.expect(:search, ad_group_payload, [:filter => ad_group_filter("broze"), :base => @config.group_base]) + @ldap.expect(:search, ad_group_payload, [:filter => ad_group_filter("broze"), :base => @config.group_base, :attributes=>["*", "primaryGroupToken"]]) end def nest_deep(n) @@ -116,7 +116,7 @@ def test_find_good_group end def test_find_missing_group - @ldap.expect(:search, nil, [:filter => ad_group_filter("broze"), :base => @config.group_base]) + @ldap.expect(:search, nil, [:filter => ad_group_filter("broze"), :base => @config.group_base, :attributes=>["*", "primaryGroupToken"]]) @adms.ldap = @ldap assert_raises(LdapFluff::ActiveDirectory::MemberService::GIDNotFoundException) do @adms.find_group('broze') @@ -161,4 +161,25 @@ def test_get_login_from_entry_missing_attr assert_nil(@adms.get_login_from_entry(entry)) end + def test_find_primary_group + prim_group = Net::LDAP::Entry.new('p_group') + test_user = Net::LDAP::Entry.new('t_user') + + prim_group[:cn] = ['p_group'] + prim_group[:dn] = ['CN=p_group,DC=corp,DC=example,DC=com'] + prim_group[:primarygrouptoken] = ['12345'] + prim_group[:member] = [] + test_user[:objectsid] = ["\x01\x04\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\a\x87\x00\x00\xA0[\x00\x00\xE1\x10\x00\x00"] + test_user[:primarygroupid] = ['12345'] + test_user[:samaccountname] = ['tuser'] + test_user[:memberof] = [] + + @ldap.expect(:search, [test_user], [:filter => Net::LDAP::Filter.eq('samaccountname','tuser')]) + @ldap.expect(:search, [prim_group], [:filter => Net::LDAP::Filter.eq('objectsid', 'S-1-5-21-34567-23456-12345'), :base => @config.group_base, :attributes => ['memberof']]) + @ldap.expect(:search, [], [:base => 'CN=p_group,DC=corp,DC=example,DC=com', :scope => Net::LDAP::SearchScope_BaseObject, :attributes => ['memberof']]) + + @adms.ldap = @ldap + assert_equal(@adms.find_user_groups('tuser'), ['p_group']) + end + end diff --git a/test/ad_test.rb b/test/ad_test.rb index 28ebf1d..1435501 100644 --- a/test/ad_test.rb +++ b/test/ad_test.rb @@ -209,4 +209,25 @@ def test_find_users_with_empty_nested_group md.verify end + def test_find_user_from_primary_group + prim_group = Net::LDAP::Entry.new('p_group') + test_user = Net::LDAP::Entry.new('t_user') + + prim_group[:cn] = ['p_group'] + prim_group[:primarygrouptoken] = ['12345'] + prim_group[:member] = [] + test_user[:primarygroupid] = ['12345'] + test_user[:samaccountname] = ['tuser'] + + @ldap.expect(:auth, nil, %w(service pass)) + @ldap.expect(:bind, true) + 2.times { @ldap.expect(:search, [prim_group], [:filter => ad_group_filter('p_group'), :base => @config.group_base, :attributes=>["*", "primaryGroupToken"]]) } + @ldap.expect(:search, [test_user], [:base => @config.base_dn, :filter => Net::LDAP::Filter.eq('primarygroupid','12345')]) + @ldap.expect(:base, @config.base_dn) + + @ad.ldap = @ldap + @ad.member_service.ldap = @ldap + assert_equal(@ad.users_for_gid('p_group'), ['tuser']) + end + end