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,387 @@
|
|
|
1
|
+
module Ldaptic
|
|
2
|
+
|
|
3
|
+
# These methods are accessible directly from the Ldaptic object.
|
|
4
|
+
module Methods
|
|
5
|
+
|
|
6
|
+
# For duck typing.
|
|
7
|
+
def to_ldaptic
|
|
8
|
+
self
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def build_hierarchy
|
|
12
|
+
klasses = adapter.object_classes.values
|
|
13
|
+
klasses.uniq!
|
|
14
|
+
hash = klasses.inject(Hash.new { |h, k| h[k] = [] }) do |hash, k|
|
|
15
|
+
hash[k.sup] << k; hash
|
|
16
|
+
end
|
|
17
|
+
@object_classes = {}
|
|
18
|
+
add_constants(hash, Ldaptic::Entry)
|
|
19
|
+
nil
|
|
20
|
+
end
|
|
21
|
+
private :build_hierarchy
|
|
22
|
+
|
|
23
|
+
def add_constants(klasses, superclass)
|
|
24
|
+
(superclass.names.empty? ? [nil] : superclass.names).each do |myname|
|
|
25
|
+
klasses[myname].each do |sub|
|
|
26
|
+
klass = ::Class.new(superclass)
|
|
27
|
+
%w(oid name desc sup must may).each do |prop|
|
|
28
|
+
klass.instance_variable_set("@#{prop}", sub.send(prop))
|
|
29
|
+
end
|
|
30
|
+
%w(obsolete abstract structural auxiliary).each do |prop|
|
|
31
|
+
klass.instance_variable_set("@#{prop}", sub.send("#{prop}?"))
|
|
32
|
+
end
|
|
33
|
+
klass.instance_variable_set(:@namespace, self)
|
|
34
|
+
@object_classes[sub.oid.tr('-', '_').downcase] = klass
|
|
35
|
+
Array(sub.name).each do |name|
|
|
36
|
+
name = name.tr('-', '_')
|
|
37
|
+
name[0,1] = name[0,1].upcase
|
|
38
|
+
@object_classes[name.downcase] = klass
|
|
39
|
+
const_set(name, klass)
|
|
40
|
+
end
|
|
41
|
+
klass.class_eval { create_accessors }
|
|
42
|
+
add_constants(klasses, klass)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
private :add_constants
|
|
47
|
+
|
|
48
|
+
attr_reader :adapter
|
|
49
|
+
|
|
50
|
+
# Set a new base DN. Generally, the base DN should be set when the
|
|
51
|
+
# namespace is created and left unchanged.
|
|
52
|
+
def base=(dn)
|
|
53
|
+
@base = Ldaptic::DN(dn, self)
|
|
54
|
+
end
|
|
55
|
+
# Access the base DN.
|
|
56
|
+
def base
|
|
57
|
+
@base ||= Ldaptic::DN(adapter.default_base_dn, self)
|
|
58
|
+
end
|
|
59
|
+
alias dn base
|
|
60
|
+
|
|
61
|
+
def logger
|
|
62
|
+
@logger ||= adapter.logger
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Find an RDN relative to the base. This method is experimental.
|
|
66
|
+
#
|
|
67
|
+
# class L < Ldaptic::Class(:base => "DC=ruby-lang,DC=org", ...)
|
|
68
|
+
# end
|
|
69
|
+
#
|
|
70
|
+
# (L/{:cn => "Matz"}).dn #=> "CN=Matz,DC=ruby-lang,DC=org"
|
|
71
|
+
def /(*args)
|
|
72
|
+
find(base.send(:/, *args))
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Like #/, only the search results are cached. This method is
|
|
76
|
+
# experimental.
|
|
77
|
+
#
|
|
78
|
+
# L[:cn=>"Why"].bacon = "chunky"
|
|
79
|
+
# L[:cn=>"Why"].bacon #=> "chunky"
|
|
80
|
+
# L[:cn=>"Why"].save
|
|
81
|
+
def [](*args)
|
|
82
|
+
if args.empty?
|
|
83
|
+
@self ||= find(base)
|
|
84
|
+
else
|
|
85
|
+
self[][*args]
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Like Ldaptic::Entry#[]= for the root node. Only works for assigning
|
|
90
|
+
# children. This method is experimental.
|
|
91
|
+
#
|
|
92
|
+
# MyCompany[:cn=>"New Employee"] = MyCompany::User.new
|
|
93
|
+
def []=(*args) #:nodoc:
|
|
94
|
+
self[].send(:[]=, *args)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Clears the cache of children. This cache is automatically populated
|
|
98
|
+
# when a child is accessed through #[].
|
|
99
|
+
def reload
|
|
100
|
+
if @self
|
|
101
|
+
@self.reload rescue nil
|
|
102
|
+
@self = nil
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def search_options(options = {})
|
|
107
|
+
options = options.dup
|
|
108
|
+
|
|
109
|
+
if options[:base].kind_of?(Hash)
|
|
110
|
+
options[:base] = dn/options[:base]
|
|
111
|
+
end
|
|
112
|
+
options[:base] = (options[:base] || dn).to_s
|
|
113
|
+
|
|
114
|
+
original_scope = options[:scope]
|
|
115
|
+
options[:scope] ||= :subtree
|
|
116
|
+
if !options[:scope].kind_of?(Integer) && options[:scope].respond_to?(:to_sym)
|
|
117
|
+
options[:scope] = Ldaptic::SCOPES[options[:scope].to_sym]
|
|
118
|
+
end
|
|
119
|
+
Ldaptic::Errors.raise(ArgumentError.new("invalid scope #{original_scope.inspect}")) unless Ldaptic::SCOPES.values.include?(options[:scope])
|
|
120
|
+
|
|
121
|
+
options[:filter] ||= "(objectClass=*)"
|
|
122
|
+
if [Hash, Proc, Method, Symbol, Array].include?(options[:filter].class)
|
|
123
|
+
options[:filter] = Ldaptic::Filter(options[:filter])
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
if options[:attributes].respond_to?(:to_ary)
|
|
127
|
+
options[:attributes] = options[:attributes].map {|x| Ldaptic.encode(x)}
|
|
128
|
+
elsif options[:attributes]
|
|
129
|
+
options[:attributes] = [Ldaptic.encode(options[:attributes])]
|
|
130
|
+
end
|
|
131
|
+
if options[:attributes]
|
|
132
|
+
options[:attributes] |= ["objectClass"]
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
options.delete(:attributes_only) if options[:instantiate]
|
|
136
|
+
options[:instantiate] = true unless options.has_key?(:instantiate)
|
|
137
|
+
|
|
138
|
+
options
|
|
139
|
+
end
|
|
140
|
+
private :search_options
|
|
141
|
+
|
|
142
|
+
def find_one(dn, options)
|
|
143
|
+
objects = search(options.merge(:base => dn, :scope => :base, :limit => false))
|
|
144
|
+
unless objects.size == 1
|
|
145
|
+
# For a missing DN, the error will be raised automatically. If the
|
|
146
|
+
# DN does exist but is not returned (e.g., it doesn't match the given
|
|
147
|
+
# filter), we'll simulate it instead.
|
|
148
|
+
Ldaptic::Errors.raise(Ldaptic::Errors::NoSuchObject.new("record not found for #{dn}"))
|
|
149
|
+
end
|
|
150
|
+
objects.first
|
|
151
|
+
end
|
|
152
|
+
private :find_one
|
|
153
|
+
|
|
154
|
+
# A potential replacement or addition to find. Does not handle array
|
|
155
|
+
# arguments or do any of the active record monkey business.
|
|
156
|
+
def fetch(dn = self.dn, options = {}) #:nodoc:
|
|
157
|
+
find_one(dn, options)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Find an absolute DN, raising an error when no results are found.
|
|
161
|
+
# L.find("CN=Matz,DC=ruby-lang,DC=org")
|
|
162
|
+
# A hash is treated as an RDN relative to the default base.
|
|
163
|
+
# L.find(:cn=>"Matz")
|
|
164
|
+
# Equivalent to
|
|
165
|
+
# L.search(:base => dn, :scope => :base, :limit => true) or raise ...
|
|
166
|
+
def find(dn = self.dn, options = {})
|
|
167
|
+
# Some misguided attempts to emulate active record.
|
|
168
|
+
case dn
|
|
169
|
+
when :all then search({:limit => false}.merge(options))
|
|
170
|
+
when :first then first(options)
|
|
171
|
+
when Array then dn.map {|d| fetch(d, options)}
|
|
172
|
+
else fetch(dn, options)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Like #search, but only returns one entry.
|
|
177
|
+
def first(options = {})
|
|
178
|
+
search(options.merge(:limit => true))
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# This is the core method for LDAP searching.
|
|
182
|
+
# * <tt>:base</tt>: The base DN of the search. The default is derived
|
|
183
|
+
# from either the <tt>:base</tt> option of the adapter configuration or
|
|
184
|
+
# by querying the server.
|
|
185
|
+
# * <tt>:scope</tt>: The scope of the search. Valid values are
|
|
186
|
+
# <tt>:base</tt> (find the base only), <tt>:onelevel</tt> (children of
|
|
187
|
+
# the base), and <tt>:subtree</tt> (the base, children, and all
|
|
188
|
+
# descendants). The default is <tt>:subtree</tt>.
|
|
189
|
+
# * <tt>:filter</tt>: A standard LDAP filter. This can be a string, an
|
|
190
|
+
# Ldaptic::Filter object, or parameters for Ldaptic::Filter().
|
|
191
|
+
# * <tt>:limit</tt>: Maximum number of results to return. If the value
|
|
192
|
+
# is a literal +true+, the first item is returned directly (or +nil+ if
|
|
193
|
+
# nothing was found). For a literal +false+, an array always returned
|
|
194
|
+
# (the default).
|
|
195
|
+
# * <tt>:attributes</tt>: Specifies an Array of attributes to return.
|
|
196
|
+
# When unspecified, all attributes are returned. If this is not an
|
|
197
|
+
# Array but rather a String or a Symbol, an array of attributes is
|
|
198
|
+
# returned rather than an array of objects.
|
|
199
|
+
# * <tt>:instantiate</tt>: If this is false, a raw hash is returned
|
|
200
|
+
# rather than an Ldaptic object. Combined with a String or Symbol
|
|
201
|
+
# argument to <tt>:attributes</tt>, a +false+ value here causes the
|
|
202
|
+
# attribute not to be typecast.
|
|
203
|
+
#
|
|
204
|
+
# Option examples:
|
|
205
|
+
# # Returns all people.
|
|
206
|
+
# MyCompany.search(:filter => {:objectClass => "person"})
|
|
207
|
+
# # Returns an array of strings because givenName is marked as a singular value on this server.
|
|
208
|
+
# MyCompany.search(:attribute => :givenName)
|
|
209
|
+
# # Returns an array of arrays of strings.
|
|
210
|
+
# MyCompany.search(:attribute => :givenName, :instantiate => false)
|
|
211
|
+
# # Returns the first object found.
|
|
212
|
+
# MyCompany.search(:limit => true)
|
|
213
|
+
def search(options = {}, &block)
|
|
214
|
+
logger.debug("#{inspect}.search(#{options.inspect[1..-2]})")
|
|
215
|
+
ary = []
|
|
216
|
+
one_attribute = options[:attributes]
|
|
217
|
+
if one_attribute.respond_to?(:to_ary)
|
|
218
|
+
one_attribute = nil
|
|
219
|
+
end
|
|
220
|
+
options = search_options(options)
|
|
221
|
+
if options[:limit] == true
|
|
222
|
+
options[:limit] = 1
|
|
223
|
+
first = true
|
|
224
|
+
end
|
|
225
|
+
adapter.search(options) do |entry|
|
|
226
|
+
if options[:instantiate]
|
|
227
|
+
klass = const_get("Top")
|
|
228
|
+
entry = klass.instantiate(entry)
|
|
229
|
+
end
|
|
230
|
+
if one_attribute
|
|
231
|
+
entry = entry[Ldaptic.encode(one_attribute)]
|
|
232
|
+
entry = entry.one if entry.respond_to?(:one)
|
|
233
|
+
end
|
|
234
|
+
ary << entry
|
|
235
|
+
block.call(entry) if block_given?
|
|
236
|
+
return entry if first == true
|
|
237
|
+
return ary if options[:limit] == ary.size
|
|
238
|
+
end
|
|
239
|
+
first ? ary.first : ary
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def normalize_attributes(attributes)
|
|
243
|
+
attributes.inject({}) do |h, (k, v)|
|
|
244
|
+
h.update(Ldaptic.encode(k) => v.respond_to?(:before_type_cast) ? v.before_type_cast : Array(v))
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
private :normalize_attributes
|
|
248
|
+
|
|
249
|
+
# Performs an LDAP add.
|
|
250
|
+
def add(dn, attributes)
|
|
251
|
+
log_dispatch(:add, dn, attributes)
|
|
252
|
+
attributes = normalize_attributes(attributes)
|
|
253
|
+
adapter.add(dn, attributes)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Performs an LDAP modify.
|
|
257
|
+
def modify(dn, attributes)
|
|
258
|
+
log_dispatch(:modify, dn, attributes)
|
|
259
|
+
if attributes.kind_of?(Hash)
|
|
260
|
+
attributes = normalize_attributes(attributes)
|
|
261
|
+
else
|
|
262
|
+
attributes = attributes.map {|(action, key, values)| [action, Ldaptic.encode(key), Array(values)]}
|
|
263
|
+
end
|
|
264
|
+
adapter.modify(dn, attributes) unless attributes.empty?
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Performs an LDAP delete.
|
|
268
|
+
def delete(dn)
|
|
269
|
+
log_dispatch(:delete, dn)
|
|
270
|
+
adapter.delete(dn)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# Performs an LDAP modrdn.
|
|
274
|
+
def rename(dn, new_rdn, delete_old, *args)
|
|
275
|
+
log_dispatch(:delete, dn, new_rdn, delete_old, *args)
|
|
276
|
+
adapter.rename(dn, new_rdn.to_str, delete_old, *args)
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# Performs an LDAP compare.
|
|
280
|
+
def compare(dn, key, value)
|
|
281
|
+
log_dispatch(:compare, dn, key, value)
|
|
282
|
+
adapter.compare(dn, Ldaptic.encode(key), Ldaptic.encode(value))
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def dit_content_rule(oid)
|
|
286
|
+
adapter.dit_content_rules[oid]
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# Retrieves attributes from the Root DSE. If +attrs+ is an array, a hash
|
|
290
|
+
# is returned keyed on the attribute.
|
|
291
|
+
#
|
|
292
|
+
# L.root_dse(:subschemaSubentry) #=> ["cn=Subschema"]
|
|
293
|
+
def root_dse(attrs = nil) #:nodoc:
|
|
294
|
+
search(
|
|
295
|
+
:base => "",
|
|
296
|
+
:scope => :base,
|
|
297
|
+
:attributes => attrs,
|
|
298
|
+
:limit => true,
|
|
299
|
+
:instantiate => false
|
|
300
|
+
)
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def schema(attrs = nil) #:nodoc:
|
|
304
|
+
search(
|
|
305
|
+
:base => Array(root_dse(:subschemaSubentry)).first,
|
|
306
|
+
:scope => :base,
|
|
307
|
+
:attributes => attrs,
|
|
308
|
+
:limit => true
|
|
309
|
+
)
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# Returns the object class for a given name or OID.
|
|
313
|
+
#
|
|
314
|
+
# L.object_class("top") #=> L::Top
|
|
315
|
+
def object_class(klass)
|
|
316
|
+
@object_classes[klass.to_s.tr('-', '_').downcase]
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
# Returns an Ldaptic::Schema::AttibuteType object encapsulating server
|
|
320
|
+
# provided information about an attribute type.
|
|
321
|
+
#
|
|
322
|
+
# L.attribute_type(:cn).desc #=> "RFC2256: common name..."
|
|
323
|
+
def attribute_type(attribute)
|
|
324
|
+
adapter.attribute_type(Ldaptic.encode(attribute))
|
|
325
|
+
end
|
|
326
|
+
# Returns an Ldaptic::Schema::LdapSyntax object encapsulating server
|
|
327
|
+
# provided information about the syntax of an attribute.
|
|
328
|
+
#
|
|
329
|
+
# L.attribute_syntax(:cn).desc #=> "Directory String"
|
|
330
|
+
def attribute_syntax(attribute)
|
|
331
|
+
type = attribute_type(attribute)
|
|
332
|
+
syntax = nil
|
|
333
|
+
until type.nil? || syntax = type.syntax
|
|
334
|
+
type = attribute_type(type.sup)
|
|
335
|
+
end
|
|
336
|
+
syntax
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
# Verifies the given credentials are authorized to connect to the server
|
|
340
|
+
# by temporarily binding with them. Returns a boolean.
|
|
341
|
+
def authenticate(dn, password)
|
|
342
|
+
adapter.authenticate(dn, password)
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
# Delegated to from Ldaptic::Entry for Active Model compliance.
|
|
346
|
+
def model_name
|
|
347
|
+
if defined?(ActiveSupport::ModelName)
|
|
348
|
+
ActiveSupport::ModelName.new(name)
|
|
349
|
+
else
|
|
350
|
+
ActiveModel::Name.new(self)
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
# Convenience method for use with Rails. Allows the singleton to be used
|
|
355
|
+
# as a before filter, an after filter, or an around filter.
|
|
356
|
+
#
|
|
357
|
+
# class ApplicationController < ActionController::Base
|
|
358
|
+
# prepend_around_filter MyCompany
|
|
359
|
+
# end
|
|
360
|
+
#
|
|
361
|
+
# When invoked, the filter clears cached children. This operation is
|
|
362
|
+
# cheap and quite necessary if you care to avoid stale data.
|
|
363
|
+
def filter(controller = nil)
|
|
364
|
+
if controller
|
|
365
|
+
reload
|
|
366
|
+
if block_given?
|
|
367
|
+
begin
|
|
368
|
+
yield
|
|
369
|
+
ensure
|
|
370
|
+
reload
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
else
|
|
374
|
+
yield if block_given?
|
|
375
|
+
end
|
|
376
|
+
self
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
def log_dispatch(method, *args)
|
|
380
|
+
if logger.debug?
|
|
381
|
+
logger.debug("#{inspect}.#{method}(#{args.inspect[1..-2]})")
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
end
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
module Ldaptic
|
|
2
|
+
# RFC4512 - LDAP: Directory Information Models
|
|
3
|
+
module Schema
|
|
4
|
+
|
|
5
|
+
class ParseError < RuntimeError
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
class AbstractDefinition
|
|
9
|
+
|
|
10
|
+
class << self
|
|
11
|
+
def attr_ldap_boolean(*attrs)
|
|
12
|
+
attrs.each do |attr|
|
|
13
|
+
class_eval(<<-EOS)
|
|
14
|
+
def #{attr}?
|
|
15
|
+
!!attributes[:#{attr}]
|
|
16
|
+
end
|
|
17
|
+
EOS
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def attr_ldap_reader(*attrs)
|
|
22
|
+
attrs.each do |attr|
|
|
23
|
+
class_eval(<<-EOS)
|
|
24
|
+
def #{attr}
|
|
25
|
+
attributes[:#{attr}]
|
|
26
|
+
end
|
|
27
|
+
EOS
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
alias attr_ldap_qdstring attr_ldap_reader
|
|
32
|
+
alias attr_ldap_qdescr attr_ldap_reader
|
|
33
|
+
alias attr_ldap_qdescrs attr_ldap_reader
|
|
34
|
+
alias attr_ldap_oid attr_ldap_reader
|
|
35
|
+
alias attr_ldap_oids attr_ldap_reader
|
|
36
|
+
alias attr_ldap_noidlen attr_ldap_reader
|
|
37
|
+
alias attr_ldap_numericoid attr_ldap_reader
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
attr_accessor :oid
|
|
41
|
+
attr_reader :attributes
|
|
42
|
+
|
|
43
|
+
def inspect
|
|
44
|
+
"#<#{self.class.inspect} #{@oid} #{attributes.inspect}>"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def to_s
|
|
48
|
+
@string
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def initialize(string)
|
|
52
|
+
@string = string.dup
|
|
53
|
+
string = @string.dup
|
|
54
|
+
@oid = extract_oid(string)
|
|
55
|
+
array = build_array(string.dup)
|
|
56
|
+
hash = array_to_hash(array)
|
|
57
|
+
@attributes = hash
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def extract_oid(string)
|
|
63
|
+
string.gsub!(/^\s*\(\s*(\w[\w:.-]*\w)\s*(.*?)\s*\)\s*$/, '\\2')
|
|
64
|
+
$1
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def build_array(string)
|
|
68
|
+
array = []
|
|
69
|
+
until string.empty?
|
|
70
|
+
if string =~ /\A(\(\s*)?'/
|
|
71
|
+
array << eatstr(string)
|
|
72
|
+
elsif string =~ /\A[A-Z-]+[A-Z]\b/
|
|
73
|
+
array << eat(string, /\A[A-Z0-9-]+/).downcase.gsub('-', '_').to_sym
|
|
74
|
+
elsif string =~ /\A(\(\s*)?[\w-]/
|
|
75
|
+
array << eatary(string)
|
|
76
|
+
else
|
|
77
|
+
raise ParseError
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
array
|
|
81
|
+
rescue ParseError
|
|
82
|
+
raise ParseError, "failed to parse schema entry #{@string.inspect}", caller[1..-1]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def array_to_hash(array)
|
|
86
|
+
last = nil
|
|
87
|
+
hash = {}
|
|
88
|
+
array.each do |elem|
|
|
89
|
+
if elem.kind_of?(Symbol)
|
|
90
|
+
hash[last] = true if last
|
|
91
|
+
last = elem
|
|
92
|
+
else
|
|
93
|
+
if last
|
|
94
|
+
hash[last] = elem
|
|
95
|
+
last = nil
|
|
96
|
+
else
|
|
97
|
+
raise ParseError, "failed to parse schema entry #{@string.inspect}", caller[1..-1]
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
hash[last] = true if last
|
|
102
|
+
hash
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def eat(string, regex)
|
|
106
|
+
string.gsub!(regex, '')
|
|
107
|
+
string.strip!
|
|
108
|
+
$1 || $&
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def eatstr(string)
|
|
112
|
+
if eaten = eat(string, /^\(\s*'([^)]+)'\s*\)/i)
|
|
113
|
+
eaten.split("' '").collect{|attr| attr.strip }
|
|
114
|
+
else
|
|
115
|
+
eat(string, /^'([^']*)'\s*/)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def eatary(string)
|
|
120
|
+
if eaten = eat(string, /^\(([\w\d_.{}\s\$-]+)\)/i)
|
|
121
|
+
eaten.split("$").collect{|attr| attr.strip}
|
|
122
|
+
elsif eaten = eat(string, /^([\w\d_.{}-]+)/i)
|
|
123
|
+
eaten
|
|
124
|
+
else
|
|
125
|
+
raise ParseError
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def method_missing(key, *args, &block)
|
|
130
|
+
if key.to_s =~ /^x_.*[^!=]$/
|
|
131
|
+
if args.size == 0
|
|
132
|
+
if key.to_s[-1] == ??
|
|
133
|
+
!!attributes[key.to_s[0..-2].to_sym]
|
|
134
|
+
else
|
|
135
|
+
attributes[key]
|
|
136
|
+
end
|
|
137
|
+
else
|
|
138
|
+
raise ArgumentError, "wrong number of arguments (#{args.size} for 0)", caller
|
|
139
|
+
end
|
|
140
|
+
else
|
|
141
|
+
super(key, *args, &block)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Serves as an abstract base class for the many definitions that feature
|
|
148
|
+
# +name+, +desc+, and +obsolete+ attributes.
|
|
149
|
+
class NameDescObsoleteDefiniton < AbstractDefinition
|
|
150
|
+
attr_ldap_qdescrs :name
|
|
151
|
+
attr_ldap_qdstring :desc
|
|
152
|
+
attr_ldap_boolean :obsolete
|
|
153
|
+
|
|
154
|
+
# The definition's name(s), always returned as an array for programmatic
|
|
155
|
+
# ease.
|
|
156
|
+
def names
|
|
157
|
+
Array(name)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# The longest (and hopefully most descriptive) name. Used by
|
|
161
|
+
# +human_attribute_name+.
|
|
162
|
+
def verbose_name
|
|
163
|
+
names.sort_by { |n| n.size }.last
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
class ObjectClass < NameDescObsoleteDefiniton
|
|
169
|
+
attr_ldap_oids :sup
|
|
170
|
+
attr_ldap_boolean :structural, :auxiliary, :abstract
|
|
171
|
+
attr_ldap_oids :must, :may
|
|
172
|
+
# "ABSTRACT", "STRUCTURAL", or "AUXILIARY"
|
|
173
|
+
def kind
|
|
174
|
+
if abstract?
|
|
175
|
+
"ABSTRACT"
|
|
176
|
+
elsif structural?
|
|
177
|
+
"STRUCTURAL"
|
|
178
|
+
elsif auxiliary?
|
|
179
|
+
"AUXILIARY"
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
class AttributeType < NameDescObsoleteDefiniton
|
|
185
|
+
attr_ldap_oid :sup, :equality, :ordering, :substr
|
|
186
|
+
attr_ldap_noidlen :syntax
|
|
187
|
+
attr_ldap_boolean :single_value, :collective, :no_user_modification
|
|
188
|
+
attr_ldap_qdescr :usage # attr_ldap_usage
|
|
189
|
+
|
|
190
|
+
def syntax_attribute
|
|
191
|
+
@attributes[:syntax]
|
|
192
|
+
end
|
|
193
|
+
def syntax_oid
|
|
194
|
+
syntax_attribute && syntax_attribute[/[0-9.]+/]
|
|
195
|
+
end
|
|
196
|
+
def syntax_len
|
|
197
|
+
syntax_attribute && syntax_attribute[/\{(.*)\}/, 1].to_i
|
|
198
|
+
end
|
|
199
|
+
def syntax_object(*args)
|
|
200
|
+
Ldaptic::SYNTAXES[syntax_oid]
|
|
201
|
+
end
|
|
202
|
+
alias syntax syntax_object
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
class MatchingRule < NameDescObsoleteDefiniton
|
|
206
|
+
attr_ldap_numericoid :syntax # mandatory
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
class MatchingRuleUse < NameDescObsoleteDefiniton
|
|
210
|
+
attr_ldap_oids :applies # mandatory
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Note that LDAP syntaxes do not have names or the obsolete flag, only
|
|
214
|
+
# desc[riptions].
|
|
215
|
+
class LdapSyntax < AbstractDefinition
|
|
216
|
+
attr_ldap_qdstring :desc
|
|
217
|
+
|
|
218
|
+
# Returns the appropriate parser from the Ldaptic::Syntaxes module.
|
|
219
|
+
def object
|
|
220
|
+
Ldaptic::Syntaxes.for(desc.delete(" "))
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
class DITContentRule < NameDescObsoleteDefiniton
|
|
225
|
+
attr_ldap_oids :aux, :must, :may, :not
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
class DITStructureRule < AbstractDefinition
|
|
229
|
+
# Has a ruleid, not an oid!
|
|
230
|
+
attr_ldap_qdescrs :name
|
|
231
|
+
attr_ldap_qdstring :desc
|
|
232
|
+
attr_ldap_boolean :obsolete
|
|
233
|
+
attr_ldap_oid :form # mandatory
|
|
234
|
+
attr_ldap_oids :sup # attr_ldap_ruleids
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
class NameForm < NameDescObsoleteDefiniton
|
|
238
|
+
attr_ldap_oid :oc # mandatory
|
|
239
|
+
attr_ldap_oids :must # mandatory
|
|
240
|
+
attr_ldap_oids :may
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
require 'ldaptic/syntaxes'
|