ldaptic 0.2.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.
- data/LICENSE +20 -0
- data/README.rdoc +104 -0
- data/Rakefile +41 -0
- data/lib/ldaptic.rb +151 -0
- data/lib/ldaptic/active_model.rb +37 -0
- data/lib/ldaptic/adapters.rb +90 -0
- data/lib/ldaptic/adapters/abstract_adapter.rb +123 -0
- data/lib/ldaptic/adapters/active_directory_adapter.rb +78 -0
- data/lib/ldaptic/adapters/active_directory_ext.rb +12 -0
- data/lib/ldaptic/adapters/ldap_conn_adapter.rb +262 -0
- data/lib/ldaptic/adapters/net_ldap_adapter.rb +173 -0
- data/lib/ldaptic/adapters/net_ldap_ext.rb +24 -0
- data/lib/ldaptic/attribute_set.rb +283 -0
- data/lib/ldaptic/dn.rb +365 -0
- data/lib/ldaptic/entry.rb +646 -0
- data/lib/ldaptic/error_set.rb +34 -0
- data/lib/ldaptic/errors.rb +136 -0
- data/lib/ldaptic/escape.rb +110 -0
- data/lib/ldaptic/filter.rb +282 -0
- data/lib/ldaptic/methods.rb +387 -0
- data/lib/ldaptic/railtie.rb +9 -0
- data/lib/ldaptic/schema.rb +246 -0
- data/lib/ldaptic/syntaxes.rb +319 -0
- data/test/core.schema +582 -0
- data/test/ldaptic_active_model_test.rb +40 -0
- data/test/ldaptic_adapters_test.rb +35 -0
- data/test/ldaptic_attribute_set_test.rb +57 -0
- data/test/ldaptic_dn_test.rb +110 -0
- data/test/ldaptic_entry_test.rb +22 -0
- data/test/ldaptic_errors_test.rb +23 -0
- data/test/ldaptic_escape_test.rb +47 -0
- data/test/ldaptic_filter_test.rb +53 -0
- data/test/ldaptic_hierarchy_test.rb +90 -0
- data/test/ldaptic_schema_test.rb +44 -0
- data/test/ldaptic_syntaxes_test.rb +66 -0
- data/test/mock_adapter.rb +47 -0
- data/test/rbslapd1.rb +111 -0
- data/test/rbslapd4.rb +172 -0
- data/test/test_helper.rb +2 -0
- metadata +146 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
require 'ldaptic/escape'
|
|
2
|
+
require 'ldaptic/errors'
|
|
3
|
+
|
|
4
|
+
module Ldaptic
|
|
5
|
+
module Adapters
|
|
6
|
+
# Subclasse must implement search, add, modify, delete, and rename. These
|
|
7
|
+
# methods should return 0 on success and non-zero on failure. The failure
|
|
8
|
+
# code is intended to be the server error code. If this is unavailable,
|
|
9
|
+
# return -1.
|
|
10
|
+
class AbstractAdapter
|
|
11
|
+
|
|
12
|
+
# When implementing an adapter, +register_as+ must be called to associate
|
|
13
|
+
# the adapter with a name. The adapter name must mimic the filename.
|
|
14
|
+
# The following might be found in ldaptic/adapters/some_adapter.rb.
|
|
15
|
+
#
|
|
16
|
+
# class SomeAdapter < AbstractAdapter
|
|
17
|
+
# register_as(:some)
|
|
18
|
+
# end
|
|
19
|
+
def self.register_as(name)
|
|
20
|
+
require 'ldaptic/adapters'
|
|
21
|
+
Ldaptic::Adapters.register(name, self)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def initialize(options)
|
|
25
|
+
@options = options
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# The server's RootDSE. +attrs+ is an array specifying which attributes
|
|
29
|
+
# to return.
|
|
30
|
+
def root_dse(attrs = nil)
|
|
31
|
+
result = search(
|
|
32
|
+
:base => "",
|
|
33
|
+
:scope => Ldaptic::SCOPES[:base],
|
|
34
|
+
:filter => "(objectClass=*)",
|
|
35
|
+
:attributes => attrs && [attrs].flatten.map {|a| Ldaptic.encode(a)},
|
|
36
|
+
:disable_pagination => true
|
|
37
|
+
) { |x| break x }
|
|
38
|
+
return if result.kind_of?(Fixnum)
|
|
39
|
+
if attrs.kind_of?(Array) || attrs.nil?
|
|
40
|
+
result
|
|
41
|
+
else
|
|
42
|
+
result[attrs]
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def schema(attrs = nil)
|
|
47
|
+
@subschema_dn ||= root_dse(['subschemaSubentry'])['subschemaSubentry'].first
|
|
48
|
+
search(
|
|
49
|
+
:base => @subschema_dn,
|
|
50
|
+
:scope => Ldaptic::SCOPES[:base],
|
|
51
|
+
:filter => "(objectClass=subschema)",
|
|
52
|
+
:attributes => attrs
|
|
53
|
+
) { |x| return x }
|
|
54
|
+
nil
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Returns the first of the +namingContexts+ found in the RootDSE.
|
|
58
|
+
def server_default_base_dn
|
|
59
|
+
unless defined?(@naming_contexts)
|
|
60
|
+
@naming_contexts = root_dse(%w(namingContexts))
|
|
61
|
+
end
|
|
62
|
+
if @naming_contexts
|
|
63
|
+
@naming_contexts["namingContexts"].to_a.first
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
alias default_base_dn server_default_base_dn
|
|
68
|
+
|
|
69
|
+
# Returns a hash of attribute types, keyed by both OID and name.
|
|
70
|
+
def attribute_types
|
|
71
|
+
@attribute_types ||= construct_schema_hash('attributeTypes',
|
|
72
|
+
Ldaptic::Schema::AttributeType)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def attribute_type(key = nil)
|
|
76
|
+
if key
|
|
77
|
+
attribute_types[key] || attribute_types.values.detect do |at|
|
|
78
|
+
at.names.map {|n| n.downcase}.include?(key.downcase)
|
|
79
|
+
end
|
|
80
|
+
else
|
|
81
|
+
attribute_types.values.uniq
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Returns a hash of DIT content rules, keyed by both OID and name.
|
|
86
|
+
def dit_content_rules
|
|
87
|
+
@dit_content_rules ||= construct_schema_hash('dITContentRules',
|
|
88
|
+
Ldaptic::Schema::DITContentRule)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Returns a hash of object classes, keyed by both OID and name.
|
|
92
|
+
def object_classes
|
|
93
|
+
@object_classes ||= construct_schema_hash('objectClasses',
|
|
94
|
+
Ldaptic::Schema::ObjectClass)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Default compare operation, emulated with a search.
|
|
98
|
+
def compare(dn, attr, value)
|
|
99
|
+
search(:base => dn, :scope => Ldaptic::SCOPES[:base], :filter => "(#{attr}=#{Ldaptic.escape(value)})") { return true }
|
|
100
|
+
false
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def logger
|
|
104
|
+
@logger || Ldaptic.logger
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
private
|
|
108
|
+
|
|
109
|
+
def construct_schema_hash(element, klass)
|
|
110
|
+
@schema_hash ||= schema(['attributeTypes', 'dITContentRules', 'objectClasses'])
|
|
111
|
+
@schema_hash[element.to_s].to_a.inject({}) do |hash, val|
|
|
112
|
+
object = klass.new(val)
|
|
113
|
+
hash[object.oid] = object
|
|
114
|
+
Array(object.name).each do |name|
|
|
115
|
+
hash[name] = object
|
|
116
|
+
end
|
|
117
|
+
hash
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
require 'ldaptic/adapters/ldap_conn_adapter'
|
|
2
|
+
require 'ldaptic/adapters/active_directory_ext'
|
|
3
|
+
|
|
4
|
+
module Ldaptic
|
|
5
|
+
module Adapters
|
|
6
|
+
# ActiveDirectoryAdapter is a LDAPConnAdapter with some Active Directory
|
|
7
|
+
# specific behaviors. To help mitigate server timeout issues, this adapter
|
|
8
|
+
# binds on each request and unbinds afterwards. For search requests, the
|
|
9
|
+
# adapter connects to the global catalog on port 3268 instead of the usual
|
|
10
|
+
# port 389. The global catalog is read-only but is a bit more flexible
|
|
11
|
+
# when it comes to searching.
|
|
12
|
+
#
|
|
13
|
+
# Active Directory servers can also be connected to with the Net::LDAP
|
|
14
|
+
# adapter.
|
|
15
|
+
class ActiveDirectoryAdapter < LDAPConnAdapter
|
|
16
|
+
register_as(:active_directory)
|
|
17
|
+
|
|
18
|
+
def initialize(options)
|
|
19
|
+
super
|
|
20
|
+
if @connection
|
|
21
|
+
@options[:connection] = @connection = nil
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Returns either the +defaultNamingContext+ (Active Directory specific)
|
|
26
|
+
# or the first of the +namingContexts+ found in the RootDSE.
|
|
27
|
+
def server_default_base_dn
|
|
28
|
+
unless defined?(@naming_contexts)
|
|
29
|
+
@naming_contexts = root_dse(%w(defaultNamingContext namingContexts))
|
|
30
|
+
end
|
|
31
|
+
if @naming_contexts
|
|
32
|
+
@naming_contexts["defaultNamingContext"].to_a.first ||
|
|
33
|
+
@naming_contexts["namingContexts"].to_a.first
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def full_username(username)
|
|
40
|
+
if username.kind_of?(Hash)
|
|
41
|
+
super
|
|
42
|
+
elsif username && username !~ /[\\=@]/
|
|
43
|
+
if @options[:domain].include?(".")
|
|
44
|
+
username = [username, @options[:domain]].join("@")
|
|
45
|
+
elsif @options[:domain]
|
|
46
|
+
username = [@options[:domain], username].join("\\")
|
|
47
|
+
else
|
|
48
|
+
conn = new_connection(3268)
|
|
49
|
+
dn = conn.search2("", 0, "(objectClass=*", ['defaultNamingContext']).first['defaultNamingContext']
|
|
50
|
+
if dn
|
|
51
|
+
domain = Ldaptic::DN(dn).rdns.map {|rdn| rdn[:dc]}.compact
|
|
52
|
+
unless domain.empty?
|
|
53
|
+
username = [username, domain.join(".")].join("@")
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
username
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def with_port(port, &block)
|
|
62
|
+
conn = new_connection(port)
|
|
63
|
+
bind_connection(conn, @options[:username], @options[:password]) do
|
|
64
|
+
with_conn(conn, &block)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def with_reader(&block)
|
|
69
|
+
with_port(3268, &block)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def with_writer(&block)
|
|
73
|
+
with_port(389, &block)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Converts an integer representing the number of microseconds since January 1,
|
|
2
|
+
# 1600 to a DateTime.
|
|
3
|
+
def DateTime.microsoft(tinies)
|
|
4
|
+
new(1601,1,1).new_offset(Time.now.utc_offset/60/60/24.0) + tinies/1e7/60/60/24
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
# Converts an integer representing the number of microseconds since January 1,
|
|
8
|
+
# 1600 to a Time.
|
|
9
|
+
def Time.microsoft(tinies)
|
|
10
|
+
dt = DateTime.microsoft(tinies)
|
|
11
|
+
Time.local(dt.year,dt.mon,dt.day,dt.hour,dt.min,dt.sec,dt.sec_fraction*60*60*24*1e6)
|
|
12
|
+
end
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
require 'ldaptic/adapters/abstract_adapter'
|
|
2
|
+
|
|
3
|
+
module Ldaptic
|
|
4
|
+
module Adapters
|
|
5
|
+
class LDAPConnAdapter < AbstractAdapter
|
|
6
|
+
register_as(:ldap_conn)
|
|
7
|
+
|
|
8
|
+
def initialize(options)
|
|
9
|
+
require 'ldap'
|
|
10
|
+
if defined?(::LDAP::Conn) && options.kind_of?(::LDAP::Conn)
|
|
11
|
+
options = {:adapter => :ldap_conn, :connection => options}
|
|
12
|
+
else
|
|
13
|
+
options = options.dup
|
|
14
|
+
end
|
|
15
|
+
options[:version] ||= 3
|
|
16
|
+
@options = options
|
|
17
|
+
if @connection = @options.delete(:connection)
|
|
18
|
+
begin
|
|
19
|
+
host, port = @connection.get_option(::LDAP::LDAP_OPT_HOST_NAME).split(':')
|
|
20
|
+
@options[:host] ||= host
|
|
21
|
+
@options[:port] ||= port.to_i if port
|
|
22
|
+
rescue
|
|
23
|
+
end
|
|
24
|
+
else
|
|
25
|
+
if username = @options.delete(:username)
|
|
26
|
+
@options[:username] = full_username(username)
|
|
27
|
+
end
|
|
28
|
+
if @options[:username]
|
|
29
|
+
connection = new_connection
|
|
30
|
+
bind_connection(connection, @options[:username], @options[:password])
|
|
31
|
+
connection.unbind
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
@logger = @options.delete(:logger)
|
|
35
|
+
super(@options)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def add(dn, attributes)
|
|
39
|
+
with_writer do |conn|
|
|
40
|
+
conn.add(dn, attributes)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def modify(dn, attributes)
|
|
45
|
+
if attributes.kind_of?(Array)
|
|
46
|
+
attributes = attributes.map do |(op, key, vals)|
|
|
47
|
+
LDAP::Mod.new(mod(op) | LDAP::LDAP_MOD_BVALUES, key, vals)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
with_writer do |conn|
|
|
51
|
+
conn.modify(dn, attributes)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def delete(dn)
|
|
56
|
+
with_writer do |conn|
|
|
57
|
+
conn.delete(dn)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def rename(dn, new_rdn, delete_old, new_superior = nil)
|
|
62
|
+
with_writer do |conn|
|
|
63
|
+
if new_superior
|
|
64
|
+
# This is from a patch I hope to get accepted upstream.
|
|
65
|
+
if conn.respond_to?(:rename)
|
|
66
|
+
conn.rename(dn, new_rdn, new_superior, delete_old)
|
|
67
|
+
else
|
|
68
|
+
Ldaptic::Errors.raise(NotImplementedError.new("rename unsupported"))
|
|
69
|
+
end
|
|
70
|
+
else
|
|
71
|
+
conn.modrdn(dn, new_rdn, delete_old)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def compare(dn, attr, value)
|
|
77
|
+
with_reader do |conn|
|
|
78
|
+
conn.compare(dn, attr, value)
|
|
79
|
+
end
|
|
80
|
+
rescue Ldaptic::Errors::CompareFalse
|
|
81
|
+
false
|
|
82
|
+
rescue Ldaptic::Errors::CompareTrue
|
|
83
|
+
true
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def search(options = {}, &block)
|
|
87
|
+
parameters = search_parameters(options)
|
|
88
|
+
with_reader do |conn|
|
|
89
|
+
begin
|
|
90
|
+
if options[:limit]
|
|
91
|
+
# Some servers don't support this option. If that happens, the
|
|
92
|
+
# higher level interface will simulate it.
|
|
93
|
+
conn.set_option(LDAP::LDAP_OPT_SIZELIMIT, options[:limit]) rescue nil
|
|
94
|
+
end
|
|
95
|
+
cookie = ""
|
|
96
|
+
while cookie
|
|
97
|
+
ctrl = paged_results_control(cookie)
|
|
98
|
+
if !options[:disable_pagination] && paged_results?
|
|
99
|
+
conn.set_option(LDAP::LDAP_OPT_SERVER_CONTROLS, [ctrl])
|
|
100
|
+
end
|
|
101
|
+
params = parameters
|
|
102
|
+
result = conn.search2(*params, &block)
|
|
103
|
+
ctrl = conn.controls.detect {|c| c.oid == ctrl.oid}
|
|
104
|
+
cookie = ctrl && ctrl.decode.last
|
|
105
|
+
cookie = nil if cookie.to_s.empty?
|
|
106
|
+
end
|
|
107
|
+
ensure
|
|
108
|
+
conn.set_option(LDAP::LDAP_OPT_SERVER_CONTROLS, []) rescue nil
|
|
109
|
+
conn.set_option(LDAP::LDAP_OPT_SIZELIMIT, 0) rescue nil
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def authenticate(dn, password)
|
|
115
|
+
conn = new_connection
|
|
116
|
+
bind_connection(conn, full_username(dn) || "", password)
|
|
117
|
+
true
|
|
118
|
+
rescue ::LDAP::ResultError => exception
|
|
119
|
+
message = exception.message
|
|
120
|
+
err = error_for_message(message)
|
|
121
|
+
unless err == 49 # Invalid credentials
|
|
122
|
+
Ldaptic::Errors.raise_unless_zero(err, message)
|
|
123
|
+
end
|
|
124
|
+
false
|
|
125
|
+
ensure
|
|
126
|
+
conn.unbind rescue nil
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def default_base_dn
|
|
130
|
+
@options[:base] || server_default_base_dn
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
private
|
|
134
|
+
|
|
135
|
+
def paged_results?
|
|
136
|
+
if @paged_results.nil?
|
|
137
|
+
@paged_results = root_dse('supportedControl').to_a.include?(CONTROL_PAGEDRESULTS)
|
|
138
|
+
end
|
|
139
|
+
@paged_results
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# ::LDAP::LDAP_CONTROL_PAGEDRESULTS,
|
|
143
|
+
CONTROL_PAGEDRESULTS = "1.2.840.113556.1.4.319"
|
|
144
|
+
|
|
145
|
+
def paged_results_control(cookie = "", size = 126)
|
|
146
|
+
require 'ldap/control'
|
|
147
|
+
# values above 126 cause problems for slapd, as determined by net/ldap
|
|
148
|
+
::LDAP::Control.new(
|
|
149
|
+
CONTROL_PAGEDRESULTS,
|
|
150
|
+
::LDAP::Control.encode(size, cookie),
|
|
151
|
+
true
|
|
152
|
+
)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def search_parameters(options = {})
|
|
156
|
+
case options[:sort]
|
|
157
|
+
when Proc, Method then s_attr, s_proc = nil, options[:sort]
|
|
158
|
+
else s_attr, s_proc = options[:sort], nil
|
|
159
|
+
end
|
|
160
|
+
[
|
|
161
|
+
options[:base],
|
|
162
|
+
options[:scope],
|
|
163
|
+
options[:filter],
|
|
164
|
+
options[:attributes] && Array(options[:attributes]),
|
|
165
|
+
options[:attributes_only],
|
|
166
|
+
options[:timeout].to_i,
|
|
167
|
+
((options[:timeout].to_f % 1) * 1e6).round,
|
|
168
|
+
s_attr.to_s,
|
|
169
|
+
s_proc
|
|
170
|
+
]
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def new_connection(default_port = nil)
|
|
174
|
+
if @options[:tls].nil?
|
|
175
|
+
conn = ::LDAP::Conn.new(
|
|
176
|
+
@options[:host]||"localhost",
|
|
177
|
+
*[@options[:port] || default_port].compact
|
|
178
|
+
)
|
|
179
|
+
else
|
|
180
|
+
conn = ::LDAP::SSLConn.new(
|
|
181
|
+
@options[:host]||"localhost",
|
|
182
|
+
@options[:port] || default_port || ::LDAP::LDAP_PORT,
|
|
183
|
+
@options[:tls]
|
|
184
|
+
)
|
|
185
|
+
end
|
|
186
|
+
conn.set_option(::LDAP::LDAP_OPT_PROTOCOL_VERSION, @options[:version])
|
|
187
|
+
conn
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def bind_connection(conn, dn, password, &block)
|
|
191
|
+
if dn
|
|
192
|
+
password = password.call if password.respond_to?(:call)
|
|
193
|
+
conn.bind(dn, password, *[@options[:method]].compact, &block)
|
|
194
|
+
else
|
|
195
|
+
block_given? ? yield(conn) : conn
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def full_username(username)
|
|
200
|
+
if username.kind_of?(Hash)
|
|
201
|
+
base = Ldaptic::DN(default_base_dn || "")
|
|
202
|
+
base / username
|
|
203
|
+
else
|
|
204
|
+
username
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def with_reader(&block)
|
|
209
|
+
if @connection
|
|
210
|
+
with_conn(@connection, &block)
|
|
211
|
+
else
|
|
212
|
+
conn = new_connection
|
|
213
|
+
bind_connection(conn, @options[:username], @options[:password]) do
|
|
214
|
+
with_conn(conn, &block)
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
alias with_writer with_reader
|
|
220
|
+
|
|
221
|
+
def with_conn(conn, &block)
|
|
222
|
+
err, message, result = 0, nil, nil
|
|
223
|
+
begin
|
|
224
|
+
result = yield conn
|
|
225
|
+
rescue ::LDAP::ResultError => exception
|
|
226
|
+
message = exception.message
|
|
227
|
+
err = error_for_message(message)
|
|
228
|
+
end
|
|
229
|
+
conn_err = conn.err.to_i
|
|
230
|
+
if err.zero? && !conn_err.zero?
|
|
231
|
+
err = conn_err
|
|
232
|
+
message = conn.err2string(err) rescue nil
|
|
233
|
+
end
|
|
234
|
+
Ldaptic::Errors.raise_unless_zero(err, message)
|
|
235
|
+
result
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# LDAP::Conn only gives us a worthless string rather than a real error
|
|
239
|
+
# code on exceptions.
|
|
240
|
+
def error_for_message(msg)
|
|
241
|
+
unless @errors
|
|
242
|
+
with_reader do |conn|
|
|
243
|
+
@errors = (0..127).inject({}) do |h, err|
|
|
244
|
+
h[conn.err2string(err)] = err; h
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
@errors.delete("Unknown error")
|
|
248
|
+
end
|
|
249
|
+
@errors[msg]
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def mod(symbol)
|
|
253
|
+
{
|
|
254
|
+
:add => LDAP::LDAP_MOD_ADD,
|
|
255
|
+
:replace => LDAP::LDAP_MOD_REPLACE,
|
|
256
|
+
:delete => LDAP::LDAP_MOD_DELETE
|
|
257
|
+
}[symbol]
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
end
|