ldap_fluff 0.4.4 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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
-