ldap_fluff 0.4.4 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/README.rdoc +11 -3
- data/lib/ldap_fluff.rb +2 -0
- data/lib/ldap_fluff/active_directory.rb +10 -8
- data/lib/ldap_fluff/ad_member_service.rb +38 -16
- data/lib/ldap_fluff/config.rb +6 -5
- data/lib/ldap_fluff/error.rb +0 -1
- data/lib/ldap_fluff/freeipa.rb +16 -30
- data/lib/ldap_fluff/freeipa_member_service.rb +13 -4
- data/lib/ldap_fluff/freeipa_netgroup_member_service.rb +12 -0
- data/lib/ldap_fluff/generic.rb +46 -11
- data/lib/ldap_fluff/generic_member_service.rb +8 -4
- data/lib/ldap_fluff/ldap_fluff.rb +9 -9
- data/lib/ldap_fluff/posix.rb +12 -20
- data/lib/ldap_fluff/posix_member_service.rb +4 -6
- data/lib/ldap_fluff/posix_netgroup_member_service.rb +14 -0
- data/test/ad_member_services_test.rb +19 -4
- data/test/ad_test.rb +33 -21
- data/test/config_test.rb +5 -5
- data/test/ipa_member_services_test.rb +2 -3
- data/test/ipa_netgroup_member_services_test.rb +67 -0
- data/test/ipa_test.rb +21 -17
- data/test/ldap_test.rb +8 -11
- data/test/lib/ldap_test_helper.rb +32 -17
- data/test/posix_member_services_test.rb +17 -8
- data/test/posix_netgroup_member_services_test.rb +74 -0
- data/test/posix_test.rb +17 -23
- metadata +26 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 8c3b572c980fa3c48ede92f936858eb045cf54f6b507e6c7767de0751f85d9cc
|
4
|
+
data.tar.gz: 9280821c5a7ecc20c2300421d9a5947820de407da3480143b08c6ec146675dcb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4d74d42c9156af61cc485d6e8c1a99d1945aa97157659e627323f60e5b09807ea2c860fd10c62e9ce209d393200a5b57ef20205e8fc66eecf7762af7be56ee22
|
7
|
+
data.tar.gz: 1228b0a8730546ec3e38658bb3a86166bc10cb2af607a8d6e3ee77e3dd8c9ee465c0bbd2383ff1d8f5cdeed3053fd6a649dbdc8b145ccb29e47321ec7ef3e973
|
data/README.rdoc
CHANGED
@@ -51,7 +51,9 @@ Your global configuration must provide information about your LDAP host to funct
|
|
51
51
|
encryption: # blank, :simple_tls, or :start_tls
|
52
52
|
base_dn: # base DN for LDAP auth, eg dc=redhat,dc=com
|
53
53
|
group_base: # base DN for your LDAP groups, eg ou=Groups,dc=redhat,dc=com
|
54
|
-
|
54
|
+
use_netgroups: # false by default, use true if you want to use netgroup triples,
|
55
|
+
# supported only for server type :free_ipa and :posix
|
56
|
+
server_type: # type of server. default == :posix. :active_directory, :posix, :free_ipa
|
55
57
|
ad_domain: # domain for your users if using active directory, eg redhat.com
|
56
58
|
service_user: # service account for authenticating LDAP calls. required unless you enable anon
|
57
59
|
service_pass: # service password for authenticating LDAP calls. required unless you enable anon
|
@@ -61,7 +63,7 @@ Your global configuration must provide information about your LDAP host to funct
|
|
61
63
|
You can pass these arguments as a hash to LdapFluff to get a valid LdapFluff object.
|
62
64
|
|
63
65
|
ldap_config = { :host => "freeipa.localdomain", :port => 389, :encryption => nil, :base_dn => "DC=mydomain,DC=com",
|
64
|
-
:group_base => "DC=groups,DC=mydomain,DC=com", :attr_login => "uid", :server_type => :
|
66
|
+
:group_base => "DC=groups,DC=mydomain,DC=com", :attr_login => "uid", :server_type => :free_ipa,
|
65
67
|
:service_user => "admin", :search_filter => "(objectClass=*)", :service_pass => "mypass",
|
66
68
|
:anon_queries => false }
|
67
69
|
|
@@ -81,6 +83,8 @@ ldap_fluff does not support searching/binding global catalogs
|
|
81
83
|
|
82
84
|
service_user (formatted as "ad_domain/username") and service_pass OR anon_queries are required for AD support
|
83
85
|
|
86
|
+
Group membership searches will use "msds-memberOfTransitive" where possible, and will fall back to a recursive lookup
|
87
|
+
|
84
88
|
=== A note on FreeIPA
|
85
89
|
|
86
90
|
ldap_fluff appends cn=groups,cn=accounts to the beginning of all BIND calls. You do not need to
|
@@ -97,6 +101,10 @@ ActiveSupport::Notifications. ldap_fluff will use this and also pass it to net-
|
|
97
101
|
When using Rails, pass `:instrumentation_service => ActiveSupport::Notifications` and then subscribe to, and
|
98
102
|
optionally log events (e.g. https://gist.github.com/mnutt/566725).
|
99
103
|
|
100
|
-
|
104
|
+
== Contributing
|
105
|
+
|
106
|
+
Feel free to file PR against our github repository.
|
107
|
+
|
108
|
+
== License
|
101
109
|
|
102
110
|
ldap_fluff is licensed under the GPLv2. Please read LICENSE for more information.
|
data/lib/ldap_fluff.rb
CHANGED
@@ -7,5 +7,7 @@ require 'ldap_fluff/active_directory'
|
|
7
7
|
require 'ldap_fluff/ad_member_service'
|
8
8
|
require 'ldap_fluff/posix'
|
9
9
|
require 'ldap_fluff/posix_member_service'
|
10
|
+
require 'ldap_fluff/posix_netgroup_member_service'
|
10
11
|
require 'ldap_fluff/freeipa'
|
11
12
|
require 'ldap_fluff/freeipa_member_service'
|
13
|
+
require 'ldap_fluff/freeipa_netgroup_member_service'
|
@@ -1,10 +1,9 @@
|
|
1
1
|
class LdapFluff::ActiveDirectory < LdapFluff::Generic
|
2
|
-
|
3
2
|
def bind?(uid = nil, password = nil, opts = {})
|
4
3
|
unless uid.include?(',') || uid.include?('\\') || opts[:search] == false
|
5
4
|
service_bind
|
6
5
|
user = @member_service.find_user(uid)
|
7
|
-
uid = user.first.dn if user
|
6
|
+
uid = user.first.dn if user&.first
|
8
7
|
end
|
9
8
|
@ldap.auth(uid, password)
|
10
9
|
@ldap.bind
|
@@ -18,9 +17,9 @@ class LdapFluff::ActiveDirectory < LdapFluff::Generic
|
|
18
17
|
begin
|
19
18
|
groups = @member_service.find_user_groups(uid)
|
20
19
|
intersection = gids & groups
|
21
|
-
|
20
|
+
(all ? intersection == gids : intersection.size > 0)
|
22
21
|
rescue MemberService::UIDNotFoundException
|
23
|
-
|
22
|
+
false
|
24
23
|
end
|
25
24
|
end
|
26
25
|
|
@@ -30,17 +29,20 @@ class LdapFluff::ActiveDirectory < LdapFluff::Generic
|
|
30
29
|
users = []
|
31
30
|
|
32
31
|
search.send(method).each do |member|
|
33
|
-
|
32
|
+
begin
|
33
|
+
entry = @member_service.find_by_dn(member).first
|
34
|
+
rescue MemberService::UIDNotFoundException
|
35
|
+
next
|
36
|
+
end
|
34
37
|
objectclasses = entry.objectclass.map(&:downcase)
|
35
38
|
|
36
|
-
if (%w
|
39
|
+
if (%w[organizationalperson person userproxy] & objectclasses).present?
|
37
40
|
users << @member_service.get_login_from_entry(entry)
|
38
|
-
elsif (%w
|
41
|
+
elsif (%w[organizationalunit group] & objectclasses).present?
|
39
42
|
users << users_for_gid(entry.cn.first)
|
40
43
|
end
|
41
44
|
end
|
42
45
|
|
43
46
|
users.flatten.uniq
|
44
47
|
end
|
45
|
-
|
46
48
|
end
|
@@ -2,26 +2,49 @@ require 'net/ldap'
|
|
2
2
|
|
3
3
|
# Naughty bits of active directory ldap queries
|
4
4
|
class LdapFluff::ActiveDirectory::MemberService < LdapFluff::GenericMemberService
|
5
|
-
|
6
5
|
def initialize(ldap, config)
|
7
6
|
@attr_login = (config.attr_login || 'samaccountname')
|
8
7
|
super
|
9
8
|
end
|
10
9
|
|
11
10
|
# get a list [] of ldap groups for a given user
|
12
|
-
#
|
11
|
+
# try to use msds-memberOfTransitive if it is supported, otherwise do a recursive loop
|
13
12
|
def find_user_groups(uid)
|
14
|
-
|
15
|
-
|
13
|
+
user_data = find_user(uid).first
|
14
|
+
|
15
|
+
if _get_domain_func_level >= 6
|
16
|
+
user_dn = user_data[:distinguishedname].first
|
17
|
+
search = @ldap.search(:base => user_dn, :scope => Net::LDAP::SearchScope_BaseObject, :attributes => ['msds-memberOfTransitive'])
|
18
|
+
if !search.nil? && !search.first.nil?
|
19
|
+
return get_groups(search.first['msds-memberoftransitive'])
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Fall back to recursive lookup
|
24
|
+
_groups_from_ldap_data(user_data)
|
25
|
+
end
|
26
|
+
|
27
|
+
# return the domain functionality level, default to 0
|
28
|
+
def _get_domain_func_level
|
29
|
+
return @domain_functionality unless @domain_functionality.nil?
|
30
|
+
|
31
|
+
@domain_functionality = 0
|
32
|
+
|
33
|
+
search = @ldap.search(:base => "", :scope => Net::LDAP::SearchScope_BaseObject, :attributes => ['domainFunctionality'])
|
34
|
+
if !search.nil? && !search.first.nil?
|
35
|
+
@domain_functionality = search.first[:domainfunctionality].first.to_i
|
36
|
+
end
|
37
|
+
|
38
|
+
@domain_functionality
|
16
39
|
end
|
17
40
|
|
18
41
|
# return the :memberof attrs + parents, recursively
|
19
42
|
def _groups_from_ldap_data(payload)
|
20
43
|
data = []
|
21
|
-
|
22
|
-
first_level
|
23
|
-
total_groups,
|
24
|
-
data
|
44
|
+
unless payload.nil?
|
45
|
+
first_level = payload[:memberof]
|
46
|
+
total_groups, = _walk_group_ancestry(first_level, first_level)
|
47
|
+
data = get_groups(first_level + total_groups).uniq
|
25
48
|
end
|
26
49
|
data
|
27
50
|
end
|
@@ -31,14 +54,13 @@ class LdapFluff::ActiveDirectory::MemberService < LdapFluff::GenericMemberServic
|
|
31
54
|
set = []
|
32
55
|
group_dns.each do |group_dn|
|
33
56
|
search = @ldap.search(:base => group_dn, :scope => Net::LDAP::SearchScope_BaseObject, :attributes => ['memberof'])
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
end
|
57
|
+
next unless !search.nil? && !search.first.nil?
|
58
|
+
groups = search.first[:memberof] - known_groups
|
59
|
+
known_groups += groups
|
60
|
+
next_level, new_known_groups = _walk_group_ancestry(groups, known_groups)
|
61
|
+
set += next_level
|
62
|
+
set += groups
|
63
|
+
known_groups += next_level
|
42
64
|
end
|
43
65
|
[set, known_groups]
|
44
66
|
end
|
data/lib/ldap_fluff/config.rb
CHANGED
@@ -3,8 +3,8 @@ require 'active_support/core_ext/hash'
|
|
3
3
|
|
4
4
|
class LdapFluff::Config
|
5
5
|
ATTRIBUTES = %w[host port encryption base_dn group_base server_type service_user
|
6
|
-
|
7
|
-
|
6
|
+
service_pass anon_queries attr_login search_filter
|
7
|
+
instrumentation_service use_netgroups].freeze
|
8
8
|
ATTRIBUTES.each { |attr| attr_reader attr.to_sym }
|
9
9
|
|
10
10
|
DEFAULT_CONFIG = { 'port' => 389,
|
@@ -13,7 +13,8 @@ class LdapFluff::Config
|
|
13
13
|
'group_base' => 'dc=company,dc=com',
|
14
14
|
'server_type' => :free_ipa,
|
15
15
|
'anon_queries' => false,
|
16
|
-
'instrumentation_service' => nil
|
16
|
+
'instrumentation_service' => nil,
|
17
|
+
'use_netgroups' => false }.freeze
|
17
18
|
|
18
19
|
def initialize(config)
|
19
20
|
raise ArgumentError unless config.respond_to?(:to_hash)
|
@@ -64,9 +65,9 @@ class LdapFluff::Config
|
|
64
65
|
end
|
65
66
|
|
66
67
|
def correct_server_type?(config)
|
67
|
-
unless [
|
68
|
+
unless %i[posix active_directory free_ipa].include?(config['server_type'])
|
68
69
|
raise ConfigError, 'config key server_type has to be :active_directory, :posix, :free_ipa ' +
|
69
|
-
|
70
|
+
"but was #{config['server_type']}"
|
70
71
|
end
|
71
72
|
end
|
72
73
|
|
data/lib/ldap_fluff/error.rb
CHANGED
data/lib/ldap_fluff/freeipa.rb
CHANGED
@@ -1,40 +1,20 @@
|
|
1
1
|
class LdapFluff::FreeIPA < LdapFluff::Generic
|
2
|
-
|
3
2
|
def bind?(uid = nil, password = nil, opts = {})
|
4
3
|
unless uid.include?(',')
|
5
4
|
unless opts[:search] == false
|
6
5
|
service_bind
|
7
6
|
user = @member_service.find_user(uid)
|
8
7
|
end
|
9
|
-
uid = user
|
8
|
+
uid = user&.first ? user.first.dn : "uid=#{uid},cn=users,cn=accounts,#{@base}"
|
10
9
|
end
|
11
10
|
@ldap.auth(uid, password)
|
12
11
|
@ldap.bind
|
13
12
|
end
|
14
13
|
|
15
14
|
def groups_for_uid(uid)
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
raise UnauthenticatedException, "Insufficient Privileges to query groups data"
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
# In freeipa, a simple user query returns a full set
|
24
|
-
# of nested groups! yipee
|
25
|
-
#
|
26
|
-
# gids should be an array of group common names
|
27
|
-
#
|
28
|
-
# returns true if owner is in ALL of the groups if all=true, otherwise
|
29
|
-
# returns true if owner is in ANY of the groups
|
30
|
-
def is_in_groups(uid, gids = [], all = true)
|
31
|
-
service_bind
|
32
|
-
groups = @member_service.find_user_groups(uid)
|
33
|
-
if all
|
34
|
-
return groups & gids == gids
|
35
|
-
else
|
36
|
-
return groups & gids != []
|
37
|
-
end
|
15
|
+
super
|
16
|
+
rescue MemberService::InsufficientQueryPrivilegesException
|
17
|
+
raise UnauthenticatedException, "Insufficient Privileges to query groups data"
|
38
18
|
end
|
39
19
|
|
40
20
|
private
|
@@ -43,12 +23,18 @@ class LdapFluff::FreeIPA < LdapFluff::Generic
|
|
43
23
|
# Member results come in the form uid=sampleuser,cn=users, etc.. or gid=samplegroup,cn=groups
|
44
24
|
users = []
|
45
25
|
|
46
|
-
search.send(method)
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
26
|
+
members = search.send(method)
|
27
|
+
|
28
|
+
if method == :nisnetgrouptriple
|
29
|
+
users = @member_service.get_netgroup_users(members)
|
30
|
+
else
|
31
|
+
members.each do |member|
|
32
|
+
type = member.downcase.split(',')[1]
|
33
|
+
if type == 'cn=users'
|
34
|
+
users << @member_service.get_logins([member])
|
35
|
+
elsif type == 'cn=groups'
|
36
|
+
users << users_for_gid(member.split(',')[0].split('=')[1])
|
37
|
+
end
|
52
38
|
end
|
53
39
|
end
|
54
40
|
|
@@ -1,8 +1,6 @@
|
|
1
1
|
require 'net/ldap'
|
2
2
|
|
3
|
-
# handles the naughty bits of posix ldap
|
4
3
|
class LdapFluff::FreeIPA::MemberService < LdapFluff::GenericMemberService
|
5
|
-
|
6
4
|
def initialize(ldap, config)
|
7
5
|
@attr_login = (config.attr_login || 'uid')
|
8
6
|
super
|
@@ -19,6 +17,19 @@ class LdapFluff::FreeIPA::MemberService < LdapFluff::GenericMemberService
|
|
19
17
|
get_groups(user[0][:memberof])
|
20
18
|
end
|
21
19
|
|
20
|
+
# extract the group names from the LDAP style response,
|
21
|
+
# return string will be something like
|
22
|
+
# CN=bros,OU=bropeeps,DC=jomara,DC=redhat,DC=com
|
23
|
+
def get_groups(grouplist)
|
24
|
+
grouplist.map(&:downcase).collect do |g|
|
25
|
+
if /.*?ipauniqueid=(.*?)/.match?(g)
|
26
|
+
@ldap.search(:base => g)[0][:cn][0]
|
27
|
+
else
|
28
|
+
g.sub(/.*?cn=(.*?),.*/, '\1')
|
29
|
+
end
|
30
|
+
end.compact
|
31
|
+
end
|
32
|
+
|
22
33
|
class UIDNotFoundException < LdapFluff::Error
|
23
34
|
end
|
24
35
|
|
@@ -27,6 +38,4 @@ class LdapFluff::FreeIPA::MemberService < LdapFluff::GenericMemberService
|
|
27
38
|
|
28
39
|
class InsufficientQueryPrivilegesException < LdapFluff::Error
|
29
40
|
end
|
30
|
-
|
31
41
|
end
|
32
|
-
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'net/ldap'
|
2
|
+
|
3
|
+
class LdapFluff::FreeIPA::NetgroupMemberService < LdapFluff::FreeIPA::MemberService
|
4
|
+
def find_user_groups(uid)
|
5
|
+
groups = []
|
6
|
+
@ldap.search(:filter => Net::LDAP::Filter.eq('objectClass', 'nisNetgroup'), :base => @group_base).each do |entry|
|
7
|
+
members = get_netgroup_users(entry[:nisnetgrouptriple])
|
8
|
+
groups << entry[:cn][0] if members.include? uid
|
9
|
+
end
|
10
|
+
groups
|
11
|
+
end
|
12
|
+
end
|
data/lib/ldap_fluff/generic.rb
CHANGED
@@ -7,13 +7,14 @@ class LdapFluff::Generic
|
|
7
7
|
:port => config.port,
|
8
8
|
:encryption => config.encryption,
|
9
9
|
:instrumentation_service => config.instrumentation_service)
|
10
|
-
@bind_user
|
11
|
-
@bind_pass
|
12
|
-
@anon
|
10
|
+
@bind_user = config.service_user
|
11
|
+
@bind_pass = config.service_pass
|
12
|
+
@anon = config.anon_queries
|
13
13
|
@attr_login = config.attr_login
|
14
14
|
@base = config.base_dn
|
15
15
|
@group_base = (config.group_base.empty? ? config.base_dn : config.group_base)
|
16
|
-
@
|
16
|
+
@use_netgroups = config.use_netgroups
|
17
|
+
@member_service = create_member_service(config)
|
17
18
|
end
|
18
19
|
|
19
20
|
def user_exists?(uid)
|
@@ -36,19 +37,35 @@ class LdapFluff::Generic
|
|
36
37
|
service_bind
|
37
38
|
@member_service.find_user_groups(uid)
|
38
39
|
rescue self.class::MemberService::UIDNotFoundException
|
39
|
-
|
40
|
+
[]
|
40
41
|
end
|
41
42
|
|
42
43
|
def users_for_gid(gid)
|
43
44
|
return [] unless group_exists?(gid)
|
44
45
|
search = @member_service.find_group(gid).last
|
45
|
-
|
46
|
-
|
47
|
-
return []
|
48
|
-
|
46
|
+
method = select_member_method(search)
|
47
|
+
return [] if method.nil?
|
49
48
|
users_from_search_results(search, method)
|
50
49
|
end
|
51
50
|
|
51
|
+
# returns whether a user is a member of ALL or ANY particular groups
|
52
|
+
# note: this method is much faster than groups_for_uid
|
53
|
+
#
|
54
|
+
# gids should be an array of group common names
|
55
|
+
#
|
56
|
+
# returns true if owner is in ALL of the groups if all=true, otherwise
|
57
|
+
# returns true if owner is in ANY of the groups
|
58
|
+
def is_in_groups(uid, gids = [], all = true)
|
59
|
+
service_bind
|
60
|
+
groups = @member_service.find_user_groups(uid).sort
|
61
|
+
gids = gids.sort
|
62
|
+
if all
|
63
|
+
groups & gids == gids
|
64
|
+
else
|
65
|
+
(groups & gids).any?
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
52
69
|
def includes_cn?(cn)
|
53
70
|
filter = Net::LDAP::Filter.eq('cn', cn)
|
54
71
|
@ldap.search(:base => @ldap.base, :filter => filter).present?
|
@@ -57,11 +74,28 @@ class LdapFluff::Generic
|
|
57
74
|
def service_bind
|
58
75
|
unless @anon || bind?(@bind_user, @bind_pass, :search => false)
|
59
76
|
raise UnauthenticatedException,
|
60
|
-
|
77
|
+
"Could not bind to #{class_name} user #{@bind_user}"
|
61
78
|
end
|
62
79
|
end
|
63
80
|
|
64
81
|
private
|
82
|
+
|
83
|
+
def select_member_method(search_result)
|
84
|
+
if @use_netgroups
|
85
|
+
:nisnetgrouptriple
|
86
|
+
else
|
87
|
+
%i[member memberuid uniquemember].find { |m| search_result.respond_to? m }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def create_member_service(config)
|
92
|
+
if @use_netgroups
|
93
|
+
self.class::NetgroupMemberService.new(@ldap, config)
|
94
|
+
else
|
95
|
+
self.class::MemberService.new(@ldap, config)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
65
99
|
def class_name
|
66
100
|
self.class.name.split('::').last
|
67
101
|
end
|
@@ -71,6 +105,8 @@ class LdapFluff::Generic
|
|
71
105
|
if method == :memberuid
|
72
106
|
# memberuid contains an array ['user1','user2'], no need to parse it
|
73
107
|
members
|
108
|
+
elsif method == :nisnetgrouptriple
|
109
|
+
@member_service.get_netgroup_users(members)
|
74
110
|
else
|
75
111
|
@member_service.get_logins(members)
|
76
112
|
end
|
@@ -79,4 +115,3 @@ class LdapFluff::Generic
|
|
79
115
|
class UnauthenticatedException < LdapFluff::Error
|
80
116
|
end
|
81
117
|
end
|
82
|
-
|