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.
Files changed (40) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +104 -0
  3. data/Rakefile +41 -0
  4. data/lib/ldaptic.rb +151 -0
  5. data/lib/ldaptic/active_model.rb +37 -0
  6. data/lib/ldaptic/adapters.rb +90 -0
  7. data/lib/ldaptic/adapters/abstract_adapter.rb +123 -0
  8. data/lib/ldaptic/adapters/active_directory_adapter.rb +78 -0
  9. data/lib/ldaptic/adapters/active_directory_ext.rb +12 -0
  10. data/lib/ldaptic/adapters/ldap_conn_adapter.rb +262 -0
  11. data/lib/ldaptic/adapters/net_ldap_adapter.rb +173 -0
  12. data/lib/ldaptic/adapters/net_ldap_ext.rb +24 -0
  13. data/lib/ldaptic/attribute_set.rb +283 -0
  14. data/lib/ldaptic/dn.rb +365 -0
  15. data/lib/ldaptic/entry.rb +646 -0
  16. data/lib/ldaptic/error_set.rb +34 -0
  17. data/lib/ldaptic/errors.rb +136 -0
  18. data/lib/ldaptic/escape.rb +110 -0
  19. data/lib/ldaptic/filter.rb +282 -0
  20. data/lib/ldaptic/methods.rb +387 -0
  21. data/lib/ldaptic/railtie.rb +9 -0
  22. data/lib/ldaptic/schema.rb +246 -0
  23. data/lib/ldaptic/syntaxes.rb +319 -0
  24. data/test/core.schema +582 -0
  25. data/test/ldaptic_active_model_test.rb +40 -0
  26. data/test/ldaptic_adapters_test.rb +35 -0
  27. data/test/ldaptic_attribute_set_test.rb +57 -0
  28. data/test/ldaptic_dn_test.rb +110 -0
  29. data/test/ldaptic_entry_test.rb +22 -0
  30. data/test/ldaptic_errors_test.rb +23 -0
  31. data/test/ldaptic_escape_test.rb +47 -0
  32. data/test/ldaptic_filter_test.rb +53 -0
  33. data/test/ldaptic_hierarchy_test.rb +90 -0
  34. data/test/ldaptic_schema_test.rb +44 -0
  35. data/test/ldaptic_syntaxes_test.rb +66 -0
  36. data/test/mock_adapter.rb +47 -0
  37. data/test/rbslapd1.rb +111 -0
  38. data/test/rbslapd4.rb +172 -0
  39. data/test/test_helper.rb +2 -0
  40. 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,9 @@
1
+ require 'ldaptic'
2
+ require 'ldaptic/before_type_cast'
3
+
4
+ class Ldaptic::Entry
5
+ include Ldaptic::BeforeTypeCast
6
+ if defined?(ActiveModel)
7
+ extend ActiveModel::Naming
8
+ end
9
+ 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'