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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: ed707d5d36b8d6b918fb50d6e89a5e6485735755
4
- data.tar.gz: eb28be466da5f887d087a9f604f4cd2f70706ca0
2
+ SHA256:
3
+ metadata.gz: 8c3b572c980fa3c48ede92f936858eb045cf54f6b507e6c7767de0751f85d9cc
4
+ data.tar.gz: 9280821c5a7ecc20c2300421d9a5947820de407da3480143b08c6ec146675dcb
5
5
  SHA512:
6
- metadata.gz: f1b2ca1f26ff45725c8ceaf615f3b871e833b57d77d5f6299b91f6dfa34548a5537af714945963c2fc671afbc8bd93549e282e498c9db393421fdc79db0ffb9f
7
- data.tar.gz: 7396f3426a2edae96fb8a5617222211daa72a224b6aaa8cc28c1cd83671fdccf9126858ec1c389dbd7733a1a24fbab75bd420e8ed7640d48518a3694b1593cec
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
- server_type: # type of server. default == posix. :active_directory, :posix, :free_ipa
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 => :freeipa,
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
- === License
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 && user.first
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
- return (all ? intersection == gids : intersection.size > 0)
20
+ (all ? intersection == gids : intersection.size > 0)
22
21
  rescue MemberService::UIDNotFoundException
23
- return false
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
- entry = @member_service.find_by_dn(member).first
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(organizationalperson person userproxy) & objectclasses).present?
39
+ if (%w[organizationalperson person userproxy] & objectclasses).present?
37
40
  users << @member_service.get_login_from_entry(entry)
38
- elsif (%w(organizationalunit group) & objectclasses).present?
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
- # in active directory, this means a recursive lookup
11
+ # try to use msds-memberOfTransitive if it is supported, otherwise do a recursive loop
13
12
  def find_user_groups(uid)
14
- data = find_user(uid)
15
- _groups_from_ldap_data(data.first)
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
- if !payload.nil?
22
- first_level = payload[:memberof]
23
- total_groups, _ = _walk_group_ancestry(first_level, first_level)
24
- data = (get_groups(first_level + total_groups)).uniq
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
- if !search.nil? && !search.first.nil?
35
- groups = search.first[:memberof] - known_groups
36
- known_groups += groups
37
- next_level, new_known_groups = _walk_group_ancestry(groups, known_groups)
38
- set += next_level
39
- set += groups
40
- known_groups += next_level
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
@@ -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
- service_pass anon_queries attr_login search_filter
7
- instrumentation_service ]
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 [:posix, :active_directory, :free_ipa].include?(config['server_type'])
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
- "but was #{config['server_type']}"
70
+ "but was #{config['server_type']}"
70
71
  end
71
72
  end
72
73
 
@@ -2,4 +2,3 @@ class LdapFluff
2
2
  class Error < StandardError
3
3
  end
4
4
  end
5
-
@@ -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 && user.first ? user.first.dn : "uid=#{uid},cn=users,cn=accounts,#{@base}"
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
- begin
17
- super
18
- rescue MemberService::InsufficientQueryPrivilegesException
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).each do |member|
47
- type = member.downcase.split(',')[1]
48
- if type == 'cn=users'
49
- users << @member_service.get_logins([member])
50
- elsif type == 'cn=groups'
51
- users << users_for_gid(member.split(',')[0].split('=')[1])
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
@@ -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 = config.service_user
11
- @bind_pass = config.service_pass
12
- @anon = config.anon_queries
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
- @member_service = self.class::MemberService.new(@ldap, config)
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
- return []
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
- method = [:member, :memberuid, :uniquemember].find { |m| search.respond_to? m } or
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
- "Could not bind to #{class_name} user #{@bind_user}"
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
-