ruby-activeldap 0.8.1 → 0.8.2
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.
- data/CHANGES +5 -0
- data/Manifest.txt +91 -25
- data/README +22 -0
- data/Rakefile +41 -8
- data/TODO +1 -6
- data/examples/config.yaml.example +5 -0
- data/examples/example.der +0 -0
- data/examples/example.jpg +0 -0
- data/examples/groupadd +41 -0
- data/examples/groupdel +35 -0
- data/examples/groupls +49 -0
- data/examples/groupmod +42 -0
- data/examples/lpasswd +55 -0
- data/examples/objects/group.rb +13 -0
- data/examples/objects/ou.rb +4 -0
- data/examples/objects/user.rb +20 -0
- data/examples/ouadd +38 -0
- data/examples/useradd +45 -0
- data/examples/useradd-binary +50 -0
- data/examples/userdel +34 -0
- data/examples/userls +50 -0
- data/examples/usermod +42 -0
- data/examples/usermod-binary-add +47 -0
- data/examples/usermod-binary-add-time +51 -0
- data/examples/usermod-binary-del +48 -0
- data/examples/usermod-lang-add +43 -0
- data/lib/active_ldap.rb +213 -214
- data/lib/active_ldap/adapter/base.rb +461 -0
- data/lib/active_ldap/adapter/ldap.rb +232 -0
- data/lib/active_ldap/adapter/ldap_ext.rb +69 -0
- data/lib/active_ldap/adapter/net_ldap.rb +288 -0
- data/lib/active_ldap/adapter/net_ldap_ext.rb +29 -0
- data/lib/active_ldap/association/belongs_to.rb +3 -1
- data/lib/active_ldap/association/belongs_to_many.rb +5 -6
- data/lib/active_ldap/association/has_many.rb +9 -17
- data/lib/active_ldap/association/has_many_wrap.rb +4 -5
- data/lib/active_ldap/attributes.rb +4 -0
- data/lib/active_ldap/base.rb +201 -56
- data/lib/active_ldap/configuration.rb +11 -1
- data/lib/active_ldap/connection.rb +15 -9
- data/lib/active_ldap/distinguished_name.rb +246 -0
- data/lib/active_ldap/ldap_error.rb +74 -0
- data/lib/active_ldap/object_class.rb +9 -5
- data/lib/active_ldap/schema.rb +50 -9
- data/lib/active_ldap/validations.rb +11 -13
- data/rails/plugin/active_ldap/generators/scaffold_al/scaffold_al_generator.rb +7 -0
- data/rails/plugin/active_ldap/generators/scaffold_al/templates/ldap.yml +21 -0
- data/rails/plugin/active_ldap/init.rb +10 -4
- data/test/al-test-utils.rb +46 -3
- data/test/run-test.rb +16 -4
- data/test/test-unit-ext/always-show-result.rb +28 -0
- data/test/test-unit-ext/priority.rb +163 -0
- data/test/test_adapter.rb +81 -0
- data/test/test_attributes.rb +8 -1
- data/test/test_base.rb +132 -3
- data/test/test_base_per_instance.rb +14 -3
- data/test/test_connection.rb +19 -0
- data/test/test_dn.rb +161 -0
- data/test/test_find.rb +24 -0
- data/test/test_object_class.rb +15 -2
- data/test/test_schema.rb +108 -1
- metadata +111 -41
- data/lib/active_ldap/adaptor/base.rb +0 -29
- data/lib/active_ldap/adaptor/ldap.rb +0 -466
- data/lib/active_ldap/ldap.rb +0 -113
@@ -0,0 +1,232 @@
|
|
1
|
+
require 'active_ldap/adapter/base'
|
2
|
+
|
3
|
+
module ActiveLdap
|
4
|
+
module Adapter
|
5
|
+
class Base
|
6
|
+
class << self
|
7
|
+
def ldap_connection(options)
|
8
|
+
unless defined?(::LDAP)
|
9
|
+
require 'active_ldap/adapter/ldap_ext'
|
10
|
+
end
|
11
|
+
Ldap.new(options)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Ldap < Base
|
17
|
+
module Method
|
18
|
+
class SSL
|
19
|
+
def connect(host, port)
|
20
|
+
LDAP::SSLConn.new(host, port, false)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class TLS
|
25
|
+
def connect(host, port)
|
26
|
+
LDAP::SSLConn.new(host, port, true)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Plain
|
31
|
+
def connect(host, port)
|
32
|
+
LDAP::Conn.new(host, port)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def connect(options={})
|
38
|
+
super do |host, port, method|
|
39
|
+
method.connect(host, port)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def unbind(options={})
|
44
|
+
return unless bound?
|
45
|
+
operation(options) do
|
46
|
+
execute(:unbind)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def bind(options={})
|
51
|
+
super do
|
52
|
+
@connection.error_message
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def bind_as_anonymous(options={})
|
57
|
+
super do
|
58
|
+
execute(:bind)
|
59
|
+
true
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def bound?
|
64
|
+
connecting? and @connection.bound?
|
65
|
+
end
|
66
|
+
|
67
|
+
def search(options={}, &block)
|
68
|
+
super(options) do |base, scope, filter, attrs, limit, callback|
|
69
|
+
begin
|
70
|
+
i = 0
|
71
|
+
execute(:search, base, scope, filter, attrs) do |entry|
|
72
|
+
i += 1
|
73
|
+
attributes = {}
|
74
|
+
entry.attrs.each do |attr|
|
75
|
+
attributes[attr] = entry.vals(attr)
|
76
|
+
end
|
77
|
+
callback.call([entry.dn, attributes], block)
|
78
|
+
break if limit and limit >= i
|
79
|
+
end
|
80
|
+
rescue RuntimeError
|
81
|
+
if $!.message == "no result returned by search"
|
82
|
+
@logger.debug {"No matches for #{filter} and attrs " +
|
83
|
+
"#{attrs.inspect}"}
|
84
|
+
else
|
85
|
+
raise
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def to_ldif(dn, attributes)
|
92
|
+
ldif = LDAP::LDIF.to_ldif("dn", [dn.dup])
|
93
|
+
attributes.sort_by do |key, value|
|
94
|
+
key
|
95
|
+
end.each do |key, values|
|
96
|
+
ldif << LDAP::LDIF.to_ldif(key, values)
|
97
|
+
end
|
98
|
+
ldif
|
99
|
+
end
|
100
|
+
|
101
|
+
def load(ldifs, options={})
|
102
|
+
super do |ldif|
|
103
|
+
LDAP::LDIF.parse_entry(ldif).send(@connection)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def delete(targets, options={})
|
108
|
+
super do |target|
|
109
|
+
execute(:delete, target)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def add(dn, entries, options={})
|
114
|
+
super do |dn, entries|
|
115
|
+
execute(:add, dn, parse_entries(entries))
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def modify(dn, entries, options={})
|
120
|
+
super do |dn, entries|
|
121
|
+
execute(:modify, dn, parse_entries(entries))
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
def prepare_connection(options={})
|
127
|
+
operation(options) do
|
128
|
+
@connection.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def root_dse(attributes, options={})
|
133
|
+
sec = options[:sec] || 0
|
134
|
+
usec = options[:usec] || 0
|
135
|
+
@connection.root_dse(attributes, sec, usec)
|
136
|
+
end
|
137
|
+
|
138
|
+
def execute(method, *args, &block)
|
139
|
+
begin
|
140
|
+
@connection.send(method, *args, &block)
|
141
|
+
rescue LDAP::ResultError
|
142
|
+
@connection.assert_error_code
|
143
|
+
raise $!.message
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def with_timeout(try_reconnect=true, options={}, &block)
|
148
|
+
begin
|
149
|
+
super
|
150
|
+
rescue LDAP::ServerDown => e
|
151
|
+
@logger.error {"LDAP server is down: #{e.message}"}
|
152
|
+
retry if try_reconnect and reconnect(options)
|
153
|
+
raise ConnectionError.new(e.message)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def ensure_method(method)
|
158
|
+
Method.constants.each do |name|
|
159
|
+
if method.to_s.downcase == name.downcase
|
160
|
+
return Method.const_get(name).new
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
available_methods = Method.constants.collect do |name|
|
165
|
+
name.downcase.to_sym.inspect
|
166
|
+
end.join(", ")
|
167
|
+
raise ConfigurationError,
|
168
|
+
"#{method.inspect} is not one of the available connect " +
|
169
|
+
"methods #{available_methods}"
|
170
|
+
end
|
171
|
+
|
172
|
+
def ensure_scope(scope)
|
173
|
+
scope_map = {
|
174
|
+
:base => LDAP::LDAP_SCOPE_BASE,
|
175
|
+
:sub => LDAP::LDAP_SCOPE_SUBTREE,
|
176
|
+
:one => LDAP::LDAP_SCOPE_ONELEVEL,
|
177
|
+
}
|
178
|
+
value = scope_map[scope || :sub]
|
179
|
+
if value.nil?
|
180
|
+
available_scopes = scope_map.keys.inspect
|
181
|
+
raise ArgumentError, "#{scope.inspect} is not one of the available " +
|
182
|
+
"LDAP scope #{available_scopes}"
|
183
|
+
end
|
184
|
+
value
|
185
|
+
end
|
186
|
+
|
187
|
+
def sasl_bind(bind_dn, options={})
|
188
|
+
super do |bind_dn, mechanism, quiet|
|
189
|
+
begin
|
190
|
+
sasl_quiet = @connection.sasl_quiet
|
191
|
+
@connection.sasl_quiet = quiet unless quiet.nil?
|
192
|
+
args = [bind_dn, mechanism]
|
193
|
+
if need_credential_sasl_mechanism?(mechanism)
|
194
|
+
args << password(bind_dn, options)
|
195
|
+
end
|
196
|
+
execute(:sasl_bind, *args)
|
197
|
+
ensure
|
198
|
+
@connection.sasl_quiet = sasl_quiet
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def simple_bind(bind_dn, options={})
|
204
|
+
super do |bind_dn, passwd|
|
205
|
+
execute(:bind, bind_dn, passwd)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def parse_entries(entries)
|
210
|
+
result = []
|
211
|
+
entries.each do |type, key, attributes|
|
212
|
+
mod_type = ensure_mod_type(type)
|
213
|
+
binary = schema.binary?(key)
|
214
|
+
mod_type |= LDAP::LDAP_MOD_BVALUES if binary
|
215
|
+
attributes.each do |name, values|
|
216
|
+
result << LDAP.mod(mod_type, name, values)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
result
|
220
|
+
end
|
221
|
+
|
222
|
+
def ensure_mod_type(type)
|
223
|
+
case type
|
224
|
+
when :replace, :add
|
225
|
+
LDAP.const_get("LDAP_MOD_#{type.to_s.upcase}")
|
226
|
+
else
|
227
|
+
raise ArgumentError, "unknown type: #{type}"
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require_library_or_gem 'ldap'
|
2
|
+
require 'ldap/ldif'
|
3
|
+
require 'ldap/schema'
|
4
|
+
|
5
|
+
module LDAP
|
6
|
+
class Mod
|
7
|
+
unless instance_method(:to_s).arity.zero?
|
8
|
+
alias_method :original_to_s, :to_s
|
9
|
+
def to_s
|
10
|
+
inspect
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
alias_method :_initialize, :initialize
|
15
|
+
def initialize(op, type, vals)
|
16
|
+
if (LDAP::VERSION.split(/\./).collect {|x| x.to_i} <=> [0, 9, 7]) <= 0
|
17
|
+
@op, @type, @vals = op, type, vals # to protect from GC
|
18
|
+
end
|
19
|
+
_initialize(op, type, vals)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
IMPLEMENT_SPECIFIC_ERRORS = {}
|
24
|
+
{
|
25
|
+
0x51 => "SERVER_DOWN",
|
26
|
+
0x52 => "LOCAL_ERROR",
|
27
|
+
0x53 => "ENCODING_ERROR",
|
28
|
+
0x54 => "DECODING_ERROR",
|
29
|
+
0x55 => "TIMEOUT",
|
30
|
+
0x56 => "AUTH_UNKNOWN",
|
31
|
+
0x57 => "FILTER_ERROR",
|
32
|
+
0x58 => "USER_CANCELLED",
|
33
|
+
0x59 => "PARAM_ERROR",
|
34
|
+
0x5a => "NO_MEMORY",
|
35
|
+
|
36
|
+
0x5b => "CONNECT_ERROR",
|
37
|
+
0x5c => "NOT_SUPPORTED",
|
38
|
+
0x5d => "CONTROL_NOT_FOUND",
|
39
|
+
0x5e => "NO_RESULTS_RETURNED",
|
40
|
+
0x5f => "MORE_RESULTS_TO_RETURN",
|
41
|
+
0x60 => "CLIENT_LOOP",
|
42
|
+
0x61 => "REFERRAL_LIMIT_EXCEEDED",
|
43
|
+
}.each do |code, name|
|
44
|
+
IMPLEMENT_SPECIFIC_ERRORS[code] =
|
45
|
+
ActiveLdap::LdapError.define(code, name, self)
|
46
|
+
end
|
47
|
+
|
48
|
+
class Conn
|
49
|
+
def failed?
|
50
|
+
not err.zero?
|
51
|
+
end
|
52
|
+
|
53
|
+
def error_message
|
54
|
+
if failed?
|
55
|
+
LDAP.err2string(err)
|
56
|
+
else
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def assert_error_code
|
62
|
+
return unless failed?
|
63
|
+
klass = ActiveLdap::LdapError::ERRORS[err]
|
64
|
+
klass ||= IMPLEMENT_SPECIFIC_ERRORS[err]
|
65
|
+
klass ||= ActiveLdap::LdapError
|
66
|
+
raise klass, LDAP.err2string(err)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,288 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
|
3
|
+
require 'active_ldap/adapter/base'
|
4
|
+
|
5
|
+
module ActiveLdap
|
6
|
+
module Adapter
|
7
|
+
class Base
|
8
|
+
class << self
|
9
|
+
def net_ldap_connection(options)
|
10
|
+
unless defined?(::Net::LDAP)
|
11
|
+
require 'active_ldap/adapter/net_ldap_ext'
|
12
|
+
end
|
13
|
+
NetLdap.new(options)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class NetLdap < Base
|
19
|
+
METHOD = {
|
20
|
+
:ssl => :simple_tls,
|
21
|
+
:tls => :start_tls,
|
22
|
+
:plain => nil,
|
23
|
+
}
|
24
|
+
|
25
|
+
def connect(options={})
|
26
|
+
@bound = false
|
27
|
+
super do |host, port, method|
|
28
|
+
config = {
|
29
|
+
:host => host,
|
30
|
+
:port => port,
|
31
|
+
}
|
32
|
+
config[:encryption] = {:method => method} if method
|
33
|
+
Net::LDAP::Connection.new(config)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def unbind(options={})
|
38
|
+
@bound = false
|
39
|
+
end
|
40
|
+
|
41
|
+
def bind(options={})
|
42
|
+
@bound = false
|
43
|
+
begin
|
44
|
+
super
|
45
|
+
rescue Net::LDAP::LdapError
|
46
|
+
raise AuthenticationError, $!.message
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def bind_as_anonymous(options={})
|
51
|
+
super do
|
52
|
+
@bound = false
|
53
|
+
execute(:bind, :method => :anonymous)
|
54
|
+
@bound = true
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def bound?
|
59
|
+
connecting? and @bound
|
60
|
+
end
|
61
|
+
|
62
|
+
def search(options={}, &block)
|
63
|
+
super(options) do |base, scope, filter, attrs, limit, callback|
|
64
|
+
args = {
|
65
|
+
:base => base,
|
66
|
+
:scope => scope,
|
67
|
+
:filter => filter,
|
68
|
+
:attributes => attrs,
|
69
|
+
:size => limit,
|
70
|
+
}
|
71
|
+
execute(:search, args) do |entry|
|
72
|
+
attributes = {}
|
73
|
+
entry.original_attribute_names.each do |name|
|
74
|
+
attributes[name] = entry[name]
|
75
|
+
end
|
76
|
+
callback.call([entry.dn, attributes], block)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def to_ldif(dn, attributes)
|
82
|
+
entry = Net::LDAP::Entry.new(dn.dup)
|
83
|
+
attributes.each do |key, values|
|
84
|
+
entry[key] = values.flatten
|
85
|
+
end
|
86
|
+
entry.to_ldif
|
87
|
+
end
|
88
|
+
|
89
|
+
def load(ldifs, options={})
|
90
|
+
super do |ldif|
|
91
|
+
entry = Net::LDAP::Entry.from_single_ldif_string(ldif)
|
92
|
+
attributes = {}
|
93
|
+
entry.each do |name, values|
|
94
|
+
attributes[name] = values
|
95
|
+
end
|
96
|
+
attributes.delete(:dn)
|
97
|
+
execute(:add,
|
98
|
+
:dn => entry.dn,
|
99
|
+
:attributes => attributes)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def delete(targets, options={})
|
104
|
+
super do |target|
|
105
|
+
execute(:delete, :dn => target)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def add(dn, entries, options={})
|
110
|
+
super do |dn, entries|
|
111
|
+
attributes = {}
|
112
|
+
entries.each do |type, key, attrs|
|
113
|
+
attrs.each do |name, values|
|
114
|
+
attributes[name] = values
|
115
|
+
end
|
116
|
+
end
|
117
|
+
execute(:add, :dn => dn, :attributes => attributes)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def modify(dn, entries, options={})
|
122
|
+
super do |dn, entries|
|
123
|
+
execute(:modify,
|
124
|
+
:dn => dn,
|
125
|
+
:operations => parse_entries(entries))
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
def execute(method, *args, &block)
|
131
|
+
result = @connection.send(method, *args, &block)
|
132
|
+
message = nil
|
133
|
+
if result.is_a?(Hash)
|
134
|
+
message = result[:errorMessage]
|
135
|
+
result = result[:resultCode]
|
136
|
+
end
|
137
|
+
unless result.zero?
|
138
|
+
klass = LdapError::ERRORS[result]
|
139
|
+
klass ||= LdapError
|
140
|
+
raise klass,
|
141
|
+
[Net::LDAP.result2string(result), message].compact.join(": ")
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def root_dse(attrs, options={})
|
146
|
+
search(:base => "",
|
147
|
+
:scope => :base,
|
148
|
+
:attributes => attrs).collect do |dn, attributes|
|
149
|
+
attributes
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def ensure_method(method)
|
154
|
+
method ||= "plain"
|
155
|
+
normalized_method = method.to_s.downcase.to_sym
|
156
|
+
return METHOD[normalized_method] if METHOD.has_key?(normalized_method)
|
157
|
+
|
158
|
+
available_methods = METHOD.keys.collect {|m| m.inspect}.join(", ")
|
159
|
+
raise ConfigurationError,
|
160
|
+
"#{method.inspect} is not one of the available connect " +
|
161
|
+
"methods #{available_methods}"
|
162
|
+
end
|
163
|
+
|
164
|
+
def ensure_scope(scope)
|
165
|
+
scope_map = {
|
166
|
+
:base => Net::LDAP::SearchScope_BaseObject,
|
167
|
+
:sub => Net::LDAP::SearchScope_WholeSubtree,
|
168
|
+
:one => Net::LDAP::SearchScope_SingleLevel,
|
169
|
+
}
|
170
|
+
value = scope_map[scope || :sub]
|
171
|
+
if value.nil?
|
172
|
+
available_scopes = scope_map.keys.inspect
|
173
|
+
raise ArgumentError, "#{scope.inspect} is not one of the available " +
|
174
|
+
"LDAP scope #{available_scopes}"
|
175
|
+
end
|
176
|
+
value
|
177
|
+
end
|
178
|
+
|
179
|
+
def sasl_bind(bind_dn, options={})
|
180
|
+
super do |bind_dn, mechanism, quiet|
|
181
|
+
normalized_mechanism = mechanism.downcase.gsub(/-/, '_')
|
182
|
+
sasl_bind_setup = "sasl_bind_setup_#{normalized_mechanism}"
|
183
|
+
next unless respond_to?(sasl_bind_setup, true)
|
184
|
+
initial_credential, challenge_response =
|
185
|
+
send(sasl_bind_setup, bind_dn, options)
|
186
|
+
args = {
|
187
|
+
:method => :sasl,
|
188
|
+
:initial_credential => initial_credential,
|
189
|
+
:mechanism => mechanism,
|
190
|
+
:challenge_response => challenge_response,
|
191
|
+
}
|
192
|
+
@bound = false
|
193
|
+
execute(:bind, args)
|
194
|
+
@bound = true
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def sasl_bind_setup_digest_md5(bind_dn, options)
|
199
|
+
initial_credential = ""
|
200
|
+
nonce_count = 1
|
201
|
+
challenge_response = Proc.new do |cred|
|
202
|
+
params = parse_sasl_digest_md5_credential(cred)
|
203
|
+
qops = params["qop"].split(/,/)
|
204
|
+
return "unsupported qops: #{qops.inspect}" unless qops.include?("auth")
|
205
|
+
qop = "auth"
|
206
|
+
server = @connection.instance_variable_get("@conn").addr[2]
|
207
|
+
realm = params['realm']
|
208
|
+
uri = "ldap/#{server}"
|
209
|
+
nc = "%08x" % nonce_count
|
210
|
+
nonce = params["nonce"]
|
211
|
+
cnonce = generate_client_nonce
|
212
|
+
requests = {
|
213
|
+
:username => bind_dn.inspect,
|
214
|
+
:realm => realm.inspect,
|
215
|
+
:nonce => nonce.inspect,
|
216
|
+
:cnonce => cnonce.inspect,
|
217
|
+
:nc => nc,
|
218
|
+
:qop => qop,
|
219
|
+
:maxbuf => "65536",
|
220
|
+
"digest-uri" => uri.inspect,
|
221
|
+
}
|
222
|
+
a1 = "#{bind_dn}:#{realm}:#{password(cred, options)}"
|
223
|
+
a1 = "#{Digest::MD5.digest(a1)}:#{nonce}:#{cnonce}"
|
224
|
+
ha1 = Digest::MD5.hexdigest(a1)
|
225
|
+
a2 = "AUTHENTICATE:#{uri}"
|
226
|
+
ha2 = Digest::MD5.hexdigest(a2)
|
227
|
+
response = "#{ha1}:#{nonce}:#{nc}:#{cnonce}:#{qop}:#{ha2}"
|
228
|
+
requests["response"] = Digest::MD5.hexdigest(response)
|
229
|
+
nonce_count += 1
|
230
|
+
requests.collect do |key, value|
|
231
|
+
"#{key}=#{value}"
|
232
|
+
end.join(",")
|
233
|
+
end
|
234
|
+
[initial_credential, challenge_response]
|
235
|
+
end
|
236
|
+
|
237
|
+
def parse_sasl_digest_md5_credential(cred)
|
238
|
+
params = {}
|
239
|
+
cred.scan(/(\w+)=(\"?)(.+?)\2(?:,|$)/) do |name, sep, value|
|
240
|
+
params[name] = value
|
241
|
+
end
|
242
|
+
params
|
243
|
+
end
|
244
|
+
|
245
|
+
CHARS = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
|
246
|
+
def generate_client_nonce(size=32)
|
247
|
+
nonce = ""
|
248
|
+
size.times do |i|
|
249
|
+
nonce << CHARS[rand(CHARS.size)]
|
250
|
+
end
|
251
|
+
nonce
|
252
|
+
end
|
253
|
+
|
254
|
+
def simple_bind(bind_dn, options={})
|
255
|
+
super do |bind_dn, passwd|
|
256
|
+
args = {
|
257
|
+
:method => :simple,
|
258
|
+
:username => bind_dn,
|
259
|
+
:password => passwd,
|
260
|
+
}
|
261
|
+
@bound = false
|
262
|
+
execute(:bind, args)
|
263
|
+
@bound = true
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
def parse_entries(entries)
|
268
|
+
result = []
|
269
|
+
entries.each do |type, key, attributes|
|
270
|
+
mod_type = ensure_mod_type(type)
|
271
|
+
attributes.each do |name, values|
|
272
|
+
result << [mod_type, name, values]
|
273
|
+
end
|
274
|
+
end
|
275
|
+
result
|
276
|
+
end
|
277
|
+
|
278
|
+
def ensure_mod_type(type)
|
279
|
+
case type
|
280
|
+
when :replace, :add
|
281
|
+
type
|
282
|
+
else
|
283
|
+
raise ArgumentError, "unknown type: #{type}"
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|