treequel 1.0.4 → 1.1.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/ChangeLog +130 -1
- data/Rakefile +8 -3
- data/Rakefile.local +2 -0
- data/bin/treeirb +6 -2
- data/bin/treequel +5 -4
- data/lib/treequel/branch.rb +133 -72
- data/lib/treequel/branchcollection.rb +16 -5
- data/lib/treequel/branchset.rb +37 -6
- data/lib/treequel/constants.rb +12 -0
- data/lib/treequel/directory.rb +96 -41
- data/lib/treequel/filter.rb +42 -1
- data/lib/treequel/model/objectclass.rb +111 -0
- data/lib/treequel/model.rb +363 -0
- data/lib/treequel/monkeypatches.rb +65 -0
- data/lib/treequel/schema/attributetype.rb +108 -18
- data/lib/treequel/schema/ldapsyntax.rb +15 -0
- data/lib/treequel/schema/matchingrule.rb +24 -0
- data/lib/treequel/schema/matchingruleuse.rb +24 -0
- data/lib/treequel/schema/objectclass.rb +70 -5
- data/lib/treequel/schema/table.rb +5 -15
- data/lib/treequel/schema.rb +64 -1
- data/lib/treequel.rb +5 -5
- data/rake/documentation.rb +27 -0
- data/rake/hg.rb +14 -2
- data/rake/manual.rb +1 -1
- data/spec/lib/constants.rb +9 -7
- data/spec/lib/control_behavior.rb +1 -0
- data/spec/lib/matchers.rb +1 -0
- data/spec/treequel/branch_spec.rb +229 -20
- data/spec/treequel/branchcollection_spec.rb +73 -39
- data/spec/treequel/branchset_spec.rb +59 -8
- data/spec/treequel/control_spec.rb +1 -0
- data/spec/treequel/controls/contentsync_spec.rb +1 -0
- data/spec/treequel/controls/pagedresults_spec.rb +1 -0
- data/spec/treequel/controls/sortedresults_spec.rb +1 -0
- data/spec/treequel/directory_spec.rb +46 -10
- data/spec/treequel/filter_spec.rb +28 -5
- data/spec/treequel/mixins_spec.rb +7 -14
- data/spec/treequel/model/objectclass_spec.rb +330 -0
- data/spec/treequel/model_spec.rb +433 -0
- data/spec/treequel/monkeypatches_spec.rb +118 -0
- data/spec/treequel/schema/attributetype_spec.rb +116 -0
- data/spec/treequel/schema/ldapsyntax_spec.rb +8 -0
- data/spec/treequel/schema/matchingrule_spec.rb +6 -1
- data/spec/treequel/schema/matchingruleuse_spec.rb +5 -0
- data/spec/treequel/schema/objectclass_spec.rb +41 -3
- data/spec/treequel/schema/table_spec.rb +31 -18
- data/spec/treequel/schema_spec.rb +13 -16
- data/spec/treequel_spec.rb +22 -0
- data.tar.gz.sig +1 -0
- metadata +40 -7
- metadata.gz.sig +0 -0
- data/spec/treequel/utils_spec.rb +0 -49
@@ -0,0 +1,363 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
require 'set'
|
5
|
+
|
6
|
+
require 'treequel'
|
7
|
+
require 'treequel/branch'
|
8
|
+
require 'treequel/branchset'
|
9
|
+
|
10
|
+
|
11
|
+
# An object interface to LDAP entries.
|
12
|
+
class Treequel::Model < Treequel::Branch
|
13
|
+
include Treequel::Loggable,
|
14
|
+
Treequel::Constants,
|
15
|
+
Treequel::Normalization,
|
16
|
+
Treequel::Constants::Patterns
|
17
|
+
|
18
|
+
require 'treequel/model/objectclass'
|
19
|
+
|
20
|
+
|
21
|
+
# A prototype Hash that autovivifies its members as Sets, for use in
|
22
|
+
# the objectclass_registry and the base_registry
|
23
|
+
SET_HASH = Hash.new {|h,k| h[k] = Set.new }
|
24
|
+
|
25
|
+
|
26
|
+
#################################################################
|
27
|
+
### C L A S S M E T H O D S
|
28
|
+
#################################################################
|
29
|
+
|
30
|
+
@objectclass_registry = SET_HASH.dup
|
31
|
+
@base_registry = SET_HASH.dup
|
32
|
+
|
33
|
+
class << self
|
34
|
+
attr_reader :objectclass_registry
|
35
|
+
attr_reader :base_registry
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
### Inheritance callback -- add a class-specific objectclass registry to inheriting classes.
|
40
|
+
### @param [Class] subclass the inheriting class
|
41
|
+
def self::inherited( subclass )
|
42
|
+
super
|
43
|
+
subclass.instance_variable_set( :@objectclass_registry, SET_HASH.dup )
|
44
|
+
subclass.instance_variable_set( :@base_registry, SET_HASH.dup )
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
### Register the given +mixin+ for the specified +objectclasses+. Instances that
|
49
|
+
### have all the specified +objectclasses+ will be extended with the +mixin+.
|
50
|
+
###
|
51
|
+
### @param [Module] mixin the mixin to be applied; it should be extended with
|
52
|
+
### Treequel::Model::ObjectClass.
|
53
|
+
def self::register_mixin( mixin )
|
54
|
+
objectclasses = mixin.model_objectclasses
|
55
|
+
bases = mixin.model_bases
|
56
|
+
bases << '' if bases.empty?
|
57
|
+
|
58
|
+
Treequel.logger.debug "registering %p [objectClasses: %p, base DNs: %p]" %
|
59
|
+
[ mixin, objectclasses, bases ]
|
60
|
+
|
61
|
+
# Register it with each of its objectClasses
|
62
|
+
objectclasses.each do |oc|
|
63
|
+
@objectclass_registry[ oc.to_sym ].add( mixin )
|
64
|
+
end
|
65
|
+
|
66
|
+
# ...and each of its bases
|
67
|
+
bases.each do |dn|
|
68
|
+
@base_registry[ dn.downcase ].add( mixin )
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
### Unregister the given +mixin+ for the specified +objectclasses+.
|
74
|
+
### @param [Module] mixin the mixin that should no longer be applied
|
75
|
+
def self::unregister_mixin( mixin )
|
76
|
+
objectclasses = mixin.model_objectclasses
|
77
|
+
bases = mixin.model_bases
|
78
|
+
bases << '' if bases.empty?
|
79
|
+
|
80
|
+
Treequel.logger.debug "un-registering %p [objectclasses: %p, base DNs: %p]" %
|
81
|
+
[ mixin, objectclasses, bases ]
|
82
|
+
|
83
|
+
# Unregister it from each of its bases
|
84
|
+
bases.each do |dn|
|
85
|
+
@base_registry[ dn.downcase ].delete( mixin )
|
86
|
+
end
|
87
|
+
|
88
|
+
# ...and each of its objectClasses
|
89
|
+
objectclasses.each do |oc|
|
90
|
+
@objectclass_registry[ oc.to_sym ].delete( mixin )
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
### Return the mixins that should be applied to an entry with the given +objectclasses+.
|
96
|
+
### @param [Array<Symbol>] objectclasses the objectclasses from the entry
|
97
|
+
### @return [Set<Module>] the Set of mixin modules which apply
|
98
|
+
def self::mixins_for_objectclasses( *objectclasses )
|
99
|
+
ocsymbols = objectclasses.flatten.collect {|oc| oc.untaint.to_sym }
|
100
|
+
|
101
|
+
# Get the union of all of the mixin sets for the objectclasses in question
|
102
|
+
mixins = self.objectclass_registry.
|
103
|
+
values_at( *ocsymbols ).
|
104
|
+
inject {|set1,set2| set1 | set2 }
|
105
|
+
|
106
|
+
# Return the mixins whose objectClass requirements are met by the
|
107
|
+
# specified objectclasses
|
108
|
+
return mixins.delete_if do |mixin|
|
109
|
+
!mixin.model_objectclasses.all? {|oc| ocsymbols.include?(oc) }
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
### Return the mixins that should be applied to an entry with the given +dn+.
|
115
|
+
### @param [String] dn the DN of the entry
|
116
|
+
### @return [Set<Module>] the Set of mixin modules which apply
|
117
|
+
def self::mixins_for_dn( dn )
|
118
|
+
dn_tuples = dn.downcase.split( /\s*,\s*/ )
|
119
|
+
dn_keys = dn_tuples.reverse.inject(['']) do |keys, dnpair|
|
120
|
+
dnpair += ',' + keys.last unless keys.last.empty?
|
121
|
+
keys << dnpair
|
122
|
+
end
|
123
|
+
|
124
|
+
# Get the union of all of the mixin sets for the DN and all of its parents
|
125
|
+
union = self.base_registry.
|
126
|
+
values_at( *dn_keys ).
|
127
|
+
inject {|set1,set2| set1 | set2 }
|
128
|
+
|
129
|
+
return union
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
|
134
|
+
#################################################################
|
135
|
+
### I N S T A N C E M E T H O D S
|
136
|
+
#################################################################
|
137
|
+
|
138
|
+
### Override the default to extend new instances with applicable mixins if their
|
139
|
+
### entry is set.
|
140
|
+
def initialize( *args )
|
141
|
+
super
|
142
|
+
self.apply_applicable_mixins( @dn, @entry ) if @entry
|
143
|
+
end
|
144
|
+
|
145
|
+
|
146
|
+
######
|
147
|
+
public
|
148
|
+
######
|
149
|
+
|
150
|
+
### Override Branch#search to inject the 'objectClass' attribute to the
|
151
|
+
### selected attribute list if there is one.
|
152
|
+
def search( scope=:subtree, filter='(objectClass=*)', parameters={}, &block )
|
153
|
+
parameters[:selectattrs] |= ['objectClass'] unless
|
154
|
+
!parameters.key?( :selectattrs ) || parameters[ :selectattrs ].empty?
|
155
|
+
|
156
|
+
super
|
157
|
+
end
|
158
|
+
|
159
|
+
|
160
|
+
### Returns +true+ if the receiver responds to the given method.
|
161
|
+
### @param [Symbol,String] sym the name of the method to test for
|
162
|
+
### @return [Boolean]
|
163
|
+
def respond_to?( sym, include_priv=false )
|
164
|
+
return super if caller(1).first =~ %r{/spec/} &&
|
165
|
+
caller(1).first !~ /respond_to/ # RSpec workaround
|
166
|
+
return true if super
|
167
|
+
plainsym, _ = attribute_from_method( sym )
|
168
|
+
return self.find_attribute_type( plainsym ) ? true : false
|
169
|
+
end
|
170
|
+
|
171
|
+
|
172
|
+
### Return the Treequel::Model::ObjectClass mixins that have been applied to the receiver.
|
173
|
+
### @return [Array<Module>]
|
174
|
+
def extensions
|
175
|
+
eigenclass = ( class << self; self; end )
|
176
|
+
return eigenclass.included_modules.find_all do |mod|
|
177
|
+
(class << mod; self; end).include?(Treequel::Model::ObjectClass)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
|
182
|
+
### Return a human-readable representation of the receiving object, suitable for debugging.
|
183
|
+
def inspect
|
184
|
+
return "#<%s:0x%x (%s)>" % [
|
185
|
+
self.class.name,
|
186
|
+
self.object_id * 2,
|
187
|
+
self.extensions.map( &:name ).join( ', ' )
|
188
|
+
]
|
189
|
+
end
|
190
|
+
|
191
|
+
|
192
|
+
#########
|
193
|
+
protected
|
194
|
+
#########
|
195
|
+
|
196
|
+
### Search for the Treequel::Schema::AttributeType associated with +sym+.
|
197
|
+
###
|
198
|
+
### @param [Symbol,String] name the name of the attribute to find
|
199
|
+
### @return [Treequel::Schema::AttributeType,nil] the associated attributeType, or nil
|
200
|
+
### if there isn't one
|
201
|
+
def find_attribute_type( name )
|
202
|
+
attrtype = nil
|
203
|
+
|
204
|
+
# If the attribute doesn't match as-is, try the camelCased version of it
|
205
|
+
camelcased_sym = name.to_s.gsub( /_(\w)/ ) { $1.upcase }.to_sym
|
206
|
+
attrtype = self.valid_attribute_type( name ) ||
|
207
|
+
self.valid_attribute_type( camelcased_sym )
|
208
|
+
|
209
|
+
return attrtype
|
210
|
+
end
|
211
|
+
|
212
|
+
|
213
|
+
### Proxy method -- Handle calls to missing methods by searching for an attribute.
|
214
|
+
def method_missing( sym, *args )
|
215
|
+
|
216
|
+
# First, if the entry hasn't yet been loaded, try loading it to make sure the
|
217
|
+
# object is already extended with any applicable objectClass mixins. If that ends
|
218
|
+
# up defining the method in question, call it.
|
219
|
+
if !@entry && self.entry
|
220
|
+
meth = begin
|
221
|
+
self.method( sym )
|
222
|
+
rescue NoMethodError, NameError
|
223
|
+
nil
|
224
|
+
end
|
225
|
+
return meth.call( *args ) if meth
|
226
|
+
end
|
227
|
+
|
228
|
+
# Next, super to rdn-traversal if it looks like a reader but has arguments
|
229
|
+
plainsym, methodtype = attribute_from_method( sym )
|
230
|
+
return super if methodtype == :reader && !args.empty?
|
231
|
+
|
232
|
+
# Now make a method body for a new method based on what attributeType it is if
|
233
|
+
# it's a valid attribute
|
234
|
+
attrtype = self.find_attribute_type( plainsym ) or return super
|
235
|
+
methodbody = case methodtype
|
236
|
+
when :writer
|
237
|
+
self.make_writer( attrtype )
|
238
|
+
when :predicate
|
239
|
+
self.make_predicate( attrtype )
|
240
|
+
else
|
241
|
+
self.make_reader( attrtype )
|
242
|
+
end
|
243
|
+
|
244
|
+
# Define the new method and call it by fetching the corresponding Method object
|
245
|
+
# so we don't loop back through #method_missing if something goes wrong
|
246
|
+
self.class.send( :define_method, sym, &methodbody )
|
247
|
+
return self.method( sym ).call( *args )
|
248
|
+
end
|
249
|
+
|
250
|
+
|
251
|
+
### Make a reader method body for the given +attrtype+.
|
252
|
+
###
|
253
|
+
### @param [Treequel::Mode::AttributeType] attrtype the attributeType to create the reader
|
254
|
+
### for.
|
255
|
+
### @return [Proc] the body of the reader method.
|
256
|
+
def make_reader( attrtype )
|
257
|
+
self.log.debug "Generating an attribute reader for %p" % [ attrtype ]
|
258
|
+
attrname = attrtype.name
|
259
|
+
return lambda {|*args|
|
260
|
+
if args.empty?
|
261
|
+
self[ attrname ]
|
262
|
+
else
|
263
|
+
self.traverse_branch( attrname, *args )
|
264
|
+
end
|
265
|
+
}
|
266
|
+
end
|
267
|
+
|
268
|
+
|
269
|
+
### Make a writer method body for the given +attrtype+.
|
270
|
+
###
|
271
|
+
### @param [Treequel::Mode::AttributeType] attrtype the attributeType to create the accessor
|
272
|
+
### for.
|
273
|
+
### @return [Proc] the body of the writer method.
|
274
|
+
def make_writer( attrtype )
|
275
|
+
self.log.debug "Generating an attribute writer for %p" % [ attrtype ]
|
276
|
+
attrname = attrtype.name
|
277
|
+
if attrtype.single?
|
278
|
+
self.log.debug " attribute is SINGLE, so generating a scalar writer..."
|
279
|
+
return lambda {|newvalue| self[attrname] = newvalue }
|
280
|
+
else
|
281
|
+
self.log.debug " attribute isn't SINGLE, so generating an array writer..."
|
282
|
+
return lambda {|*newvalues| self[attrname] = newvalues }
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
|
287
|
+
### Make a predicate method body for the given +attrtype+.
|
288
|
+
###
|
289
|
+
### @param [Treequel::Mode::AttributeType] attrtype the attributeType to create the method
|
290
|
+
### for.
|
291
|
+
### @return [Proc] the body of the predicate method.
|
292
|
+
def make_predicate( attrtype )
|
293
|
+
self.log.debug "Generating an attribute predicate for %p" % [ attrtype ]
|
294
|
+
attrname = attrtype.name
|
295
|
+
if attrtype.single?
|
296
|
+
self.log.debug " attribute is SINGLE, so generating a scalar predicate..."
|
297
|
+
return lambda { self[attrname] ? true : false }
|
298
|
+
else
|
299
|
+
self.log.debug " attribute isn't SINGLE, so generating an array predicate..."
|
300
|
+
return lambda { self[attrname].any? {|val| val} }
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
|
305
|
+
### Overridden to apply applicable mixins to lazily-loaded objects once their entry
|
306
|
+
### has been looked up.
|
307
|
+
### @return [LDAP::Entry] the fetched entry object
|
308
|
+
def lookup_entry
|
309
|
+
if entry = super
|
310
|
+
self.log.debug " applying mixins to %p" % [ entry ]
|
311
|
+
self.apply_applicable_mixins( self.dn, entry )
|
312
|
+
end
|
313
|
+
return entry
|
314
|
+
end
|
315
|
+
|
316
|
+
|
317
|
+
### Apply mixins that are applicable considering the receiver's DN and the
|
318
|
+
### objectClasses from its entry.
|
319
|
+
def apply_applicable_mixins( dn, entry )
|
320
|
+
ocs = entry.object_classes.collect do |explicit_oc|
|
321
|
+
explicit_oc.ancestors.collect {|oc| oc.name }
|
322
|
+
end.flatten.uniq
|
323
|
+
|
324
|
+
oc_mixins = self.class.mixins_for_objectclasses( *ocs )
|
325
|
+
dn_mixins = self.class.mixins_for_dn( dn )
|
326
|
+
|
327
|
+
# The applicable mixins are those in the intersection of the ones
|
328
|
+
# inferred by its objectclasses and those that apply to its DN
|
329
|
+
mixins = ( oc_mixins & dn_mixins )
|
330
|
+
|
331
|
+
self.log.debug " %d mixins apply to %s: %p" % [ mixins.length, dn, mixins.to_a ]
|
332
|
+
mixins.each {|mod| self.extend(mod) }
|
333
|
+
end
|
334
|
+
|
335
|
+
|
336
|
+
#######
|
337
|
+
private
|
338
|
+
#######
|
339
|
+
|
340
|
+
### Given the symbol from an attribute accessor or predicate, return the
|
341
|
+
### name of the corresponding LDAP attribute/
|
342
|
+
### @param [Symbol] methodname the method being called
|
343
|
+
### @return [Symbol] the attribute name that corresponds to the method
|
344
|
+
def attribute_from_method( methodname )
|
345
|
+
|
346
|
+
case methodname.to_s
|
347
|
+
when /^(?:has_)?([a-z]\w+)\?$/i
|
348
|
+
return $1.to_sym, :predicate
|
349
|
+
when /^([a-z]\w+)(=)?$/i
|
350
|
+
return $1.to_sym, ($2 ? :writer : :reader )
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
|
355
|
+
### Turn a String DN into a reversed set of DN attribute/value pairs
|
356
|
+
def make_dn_tuples( dn )
|
357
|
+
return dn.split( /\s*,\s*/ ).reverse
|
358
|
+
end
|
359
|
+
|
360
|
+
end # class Treequel::Model
|
361
|
+
|
362
|
+
|
363
|
+
|
@@ -26,4 +26,69 @@ class LDAP::Control
|
|
26
26
|
end
|
27
27
|
|
28
28
|
|
29
|
+
### Extensions to the Time class to add LDAP (RFC4517) Generalized Time syntax
|
30
|
+
module Treequel::TimeExtensions
|
31
|
+
|
32
|
+
### Return +self+ as a String formatted as specified in RFC4517
|
33
|
+
### (LDAP Generalized Time).
|
34
|
+
def ldap_generalized( fraction_digits=0 )
|
35
|
+
fractional_seconds =
|
36
|
+
if fraction_digits == 0
|
37
|
+
''
|
38
|
+
elsif fraction_digits <= 6
|
39
|
+
'.' + sprintf('%06d', usec)[0, fraction_digits]
|
40
|
+
else
|
41
|
+
'.' + sprintf('%06d', usec) + '0' * (fraction_digits - 6)
|
42
|
+
end
|
43
|
+
tz =
|
44
|
+
if self.utc?
|
45
|
+
'Z'
|
46
|
+
else
|
47
|
+
off = utc_offset
|
48
|
+
sign = off < 0 ? '-' : '+'
|
49
|
+
"%s%02d%02d" % [ sign, *(off.abs / 60).divmod(60) ]
|
50
|
+
end
|
51
|
+
|
52
|
+
return "%02d%02d%02d%02d%02d%02d%s%s" % [
|
53
|
+
year,
|
54
|
+
mon,
|
55
|
+
day,
|
56
|
+
hour,
|
57
|
+
min,
|
58
|
+
sec,
|
59
|
+
fractional_seconds,
|
60
|
+
tz
|
61
|
+
]
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
### Returns +self+ as a String formatted as specified in RFC4517
|
66
|
+
### (UTC Time)
|
67
|
+
def ldap_utc
|
68
|
+
tz =
|
69
|
+
if utc?
|
70
|
+
'Z'
|
71
|
+
else
|
72
|
+
off = utc_offset
|
73
|
+
sign = off < 0 ? '-' : '+'
|
74
|
+
"%s%02d%02d" % [ sign, *(off.abs / 60).divmod(60) ]
|
75
|
+
end
|
76
|
+
|
77
|
+
return "%02d%02d%02d%02d%02d%02d%s" % [
|
78
|
+
year.divmod(100).last,
|
79
|
+
mon,
|
80
|
+
day,
|
81
|
+
hour,
|
82
|
+
min,
|
83
|
+
sec,
|
84
|
+
tz
|
85
|
+
]
|
86
|
+
end
|
87
|
+
|
88
|
+
end # module Treequel::TimeExtensions
|
89
|
+
|
90
|
+
class Time
|
91
|
+
include Treequel::TimeExtensions
|
92
|
+
end
|
93
|
+
|
29
94
|
|
@@ -28,6 +28,30 @@ class Treequel::Schema::AttributeType
|
|
28
28
|
|
29
29
|
extend Treequel::AttributeDeclarations
|
30
30
|
|
31
|
+
# Regex for splitting a syntax OID from its length specifier
|
32
|
+
OID_SPLIT_PATTERN = /
|
33
|
+
^
|
34
|
+
#{SQUOTE}?
|
35
|
+
(#{OID}) # OID = $1
|
36
|
+
#{SQUOTE}?
|
37
|
+
(?:
|
38
|
+
#{LCURLY}
|
39
|
+
(#{LEN}) # Length = $2
|
40
|
+
#{RCURLY}
|
41
|
+
)?$
|
42
|
+
/x
|
43
|
+
|
44
|
+
# The default USAGE type: "Usage of userApplications, the default,
|
45
|
+
# indicates that attributes of this type represent user information.
|
46
|
+
# That is, they are user attributes." (RFC4512)
|
47
|
+
DEFAULT_USAGE_TYPE = 'userApplications'.freeze
|
48
|
+
|
49
|
+
# A usage of directoryOperation, distributedOperation, or dSAOperation
|
50
|
+
# indicates that attributes of this type represent operational and/or
|
51
|
+
# administrative information. That is, they are operational
|
52
|
+
# attributes. (RFC4512)
|
53
|
+
OPERATIONAL_ATTRIBUTE_USAGES = %w[directoryOperation distributedOperation dSAOperation].freeze
|
54
|
+
|
31
55
|
|
32
56
|
#############################################################
|
33
57
|
### C L A S S M E T H O D S
|
@@ -63,7 +87,7 @@ class Treequel::Schema::AttributeType
|
|
63
87
|
:single => single,
|
64
88
|
:collective => collective,
|
65
89
|
:user_modifiable => nousermod ? false : true,
|
66
|
-
:usagetype => usagetype,
|
90
|
+
:usagetype => usagetype || DEFAULT_USAGE_TYPE,
|
67
91
|
:extensions => extensions
|
68
92
|
)
|
69
93
|
end
|
@@ -92,7 +116,7 @@ class Treequel::Schema::AttributeType
|
|
92
116
|
@usagetype = attributes[:usagetype]
|
93
117
|
@extensions = attributes[:extensions]
|
94
118
|
|
95
|
-
@syntax_oid, @syntax_len =
|
119
|
+
@syntax_oid, @syntax_len = split_syntax_oid( attributes[:syntax_oid] ) if
|
96
120
|
attributes[:syntax_oid]
|
97
121
|
|
98
122
|
super()
|
@@ -147,6 +171,8 @@ class Treequel::Schema::AttributeType
|
|
147
171
|
|
148
172
|
# The application of this attributeType
|
149
173
|
attr_accessor :usagetype
|
174
|
+
alias_method :usage, :usagetype
|
175
|
+
alias_method :usage=, :usagetype=
|
150
176
|
|
151
177
|
# The attributeType's extensions (as a String)
|
152
178
|
attr_accessor :extensions
|
@@ -181,16 +207,43 @@ class Treequel::Schema::AttributeType
|
|
181
207
|
end
|
182
208
|
|
183
209
|
|
210
|
+
### Returns the attributeType as a String, which is the RFC4512-style schema
|
211
|
+
### description.
|
212
|
+
def to_s
|
213
|
+
parts = [ self.oid ]
|
214
|
+
|
215
|
+
parts << "NAME %s" % Treequel::Schema.qdescrs( self.names ) unless self.names.empty?
|
216
|
+
|
217
|
+
parts << "DESC '%s'" % [ self.desc ] if self.desc
|
218
|
+
parts << "OBSOLETE" if self.obsolete?
|
219
|
+
parts << "SUP %s" % [ self.sup_oid ] if self.sup_oid
|
220
|
+
parts << "EQUALITY %s" % [ self.eqmatch_oid ] if self.eqmatch_oid
|
221
|
+
parts << "ORDERING %s" % [ self.ordmatch_oid ] if self.ordmatch_oid
|
222
|
+
parts << "SUBSTR %s" % [ self.submatch_oid ] if self.submatch_oid
|
223
|
+
if self.syntax_oid
|
224
|
+
parts << "SYNTAX %s" % [ self.syntax_oid ]
|
225
|
+
parts.last << "{%d}" % [ self.syntax_len ] if self.syntax_len
|
226
|
+
end
|
227
|
+
parts << "SINGLE-VALUE" if self.is_single?
|
228
|
+
parts << "COLLECTIVE" if self.is_collective?
|
229
|
+
parts << "NO-USER-MODIFICATION" unless self.is_user_modifiable?
|
230
|
+
parts << "USAGE %s" % [ self.usagetype ] if self.usagetype
|
231
|
+
parts << self.extensions.strip unless self.extensions.empty?
|
232
|
+
|
233
|
+
return "( %s )" % [ parts.join(' ') ]
|
234
|
+
end
|
235
|
+
|
236
|
+
|
184
237
|
### Return a human-readable representation of the object suitable for debugging
|
185
238
|
def inspect
|
186
|
-
return "#<%s:0x%0x %s(%s) %p %sSYNTAX: %
|
239
|
+
return "#<%s:0x%0x %s(%s) %p %sSYNTAX: %s (length: %s)>" % [
|
187
240
|
self.class.name,
|
188
241
|
self.object_id / 2,
|
189
242
|
self.name,
|
190
243
|
self.oid,
|
191
244
|
self.desc,
|
192
245
|
self.is_single? ? '(SINGLE) ' : '',
|
193
|
-
self.
|
246
|
+
self.syntax,
|
194
247
|
self.syntax_len ? self.syntax_len : 'unlimited',
|
195
248
|
]
|
196
249
|
end
|
@@ -247,21 +300,58 @@ class Treequel::Schema::AttributeType
|
|
247
300
|
end
|
248
301
|
|
249
302
|
|
250
|
-
|
251
|
-
|
252
|
-
|
303
|
+
# Usage of userApplications, the default, indicates that attributes of
|
304
|
+
# this type represent user information. That is, they are user
|
305
|
+
# attributes.
|
253
306
|
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
307
|
+
### Test whether or not the attrinbute is a user applications attribute.
|
308
|
+
### @return [Boolean] true if the attribute's USAGE is 'userApplications' (or nil)
|
309
|
+
def is_user?
|
310
|
+
return !self.is_operational?
|
311
|
+
end
|
312
|
+
alias_method :user?, :is_user?
|
313
|
+
|
314
|
+
|
315
|
+
### Test whether or not the attribute is an operational attribute.
|
316
|
+
### @return [Boolean] true if the attribute's usage is one of the OPERATIONAL_ATTRIBUTE_USAGES
|
317
|
+
def is_operational?
|
318
|
+
usage_type = self.usage || DEFAULT_USAGE_TYPE
|
319
|
+
return OPERATIONAL_ATTRIBUTE_USAGES.map( &:downcase ).include?( usage_type.downcase )
|
320
|
+
end
|
321
|
+
alias_method :operational?, :is_operational?
|
322
|
+
|
323
|
+
|
324
|
+
### Test whether or not the attribute is a directory operational attribute.
|
325
|
+
### @return [Boolean] true if the attribute's usage is 'directoryOperation'
|
326
|
+
def is_directory_operational?
|
327
|
+
usage_type = self.usage || DEFAULT_USAGE_TYPE
|
328
|
+
return usage_type == 'directoryOperation'
|
329
|
+
end
|
330
|
+
alias_method :directory_operational?, :is_directory_operational?
|
331
|
+
|
332
|
+
|
333
|
+
### Test whether or not the attribute is a distributed operational attribute.
|
334
|
+
### @return [Boolean] true if the attribute's usage is 'distributedOperation'
|
335
|
+
def is_distributed_operational?
|
336
|
+
usage_type = self.usage || DEFAULT_USAGE_TYPE
|
337
|
+
return usage_type == 'distributedOperation'
|
338
|
+
end
|
339
|
+
alias_method :distributed_operational?, :is_distributed_operational?
|
340
|
+
|
341
|
+
|
342
|
+
### Test whether or not the attribute is a DSA-specific operational attribute.
|
343
|
+
### @return [Boolean] true if the attribute's usage is 'dSAOperation'
|
344
|
+
def is_dsa_operational?
|
345
|
+
usage_type = self.usage || DEFAULT_USAGE_TYPE
|
346
|
+
return usage_type == 'dSAOperation'
|
347
|
+
end
|
348
|
+
alias_method :dsa_operational?, :is_dsa_operational?
|
349
|
+
alias_method :dsa_specific?, :is_dsa_operational?
|
350
|
+
|
351
|
+
|
352
|
+
#######
|
353
|
+
private
|
354
|
+
#######
|
265
355
|
|
266
356
|
### Split a numeric OID with an optional length qualifier into a numeric OID and length. If
|
267
357
|
### no length qualifier is present, it will be nil.
|
@@ -69,6 +69,21 @@ class Treequel::Schema::LDAPSyntax
|
|
69
69
|
# The syntax's extensions (as a String)
|
70
70
|
attr_accessor :extensions
|
71
71
|
|
72
|
+
# SyntaxDescription = LPAREN WSP
|
73
|
+
# numericoid ; object identifier
|
74
|
+
# [ SP "DESC" SP qdstring ] ; description
|
75
|
+
# extensions WSP RPAREN ; extensions
|
76
|
+
|
77
|
+
### Returns the SyntaxDescription as a String, which is the RFC4512-style schema
|
78
|
+
### description.
|
79
|
+
def to_s
|
80
|
+
parts = [ self.oid ]
|
81
|
+
parts << "DESC '%s'" % [ self.desc ] if self.desc
|
82
|
+
parts << self.extensions.strip unless self.extensions.empty?
|
83
|
+
|
84
|
+
return "( %s )" % [ parts.join(' ') ]
|
85
|
+
end
|
86
|
+
|
72
87
|
|
73
88
|
### Return a human-readable representation of the object suitable for debugging
|
74
89
|
def inspect
|
@@ -89,6 +89,30 @@ class Treequel::Schema::MatchingRule
|
|
89
89
|
end
|
90
90
|
|
91
91
|
|
92
|
+
# MatchingRuleDescription = LPAREN WSP
|
93
|
+
# numericoid ; object identifier
|
94
|
+
# [ SP "NAME" SP qdescrs ] ; short names (descriptors)
|
95
|
+
# [ SP "DESC" SP qdstring ] ; description
|
96
|
+
# [ SP "OBSOLETE" ] ; not active
|
97
|
+
# SP "SYNTAX" SP numericoid ; assertion syntax
|
98
|
+
# extensions WSP RPAREN ; extensions
|
99
|
+
|
100
|
+
### Returns the matchingRule as a String, which is the RFC4512-style schema
|
101
|
+
### description.
|
102
|
+
def to_s
|
103
|
+
parts = [ self.oid ]
|
104
|
+
|
105
|
+
parts << "NAME %s" % Treequel::Schema.qdescrs( self.names ) unless self.names.empty?
|
106
|
+
|
107
|
+
parts << "DESC '%s'" % [ self.desc ] if self.desc
|
108
|
+
parts << "OBSOLETE" if self.obsolete?
|
109
|
+
parts << "SYNTAX %s" % [ self.syntax_oid ]
|
110
|
+
parts << self.extensions.strip unless self.extensions.empty?
|
111
|
+
|
112
|
+
return "( %s )" % [ parts.join(' ') ]
|
113
|
+
end
|
114
|
+
|
115
|
+
|
92
116
|
### Return a human-readable representation of the object suitable for debugging
|
93
117
|
def inspect
|
94
118
|
return "#<%s:0x%0x %s(%s) %s %sSYNTAX: %p>" % [
|
@@ -90,6 +90,30 @@ class Treequel::Schema::MatchingRuleUse
|
|
90
90
|
end
|
91
91
|
|
92
92
|
|
93
|
+
# MatchingRuleUseDescription = LPAREN WSP
|
94
|
+
# numericoid ; object identifier
|
95
|
+
# [ SP "NAME" SP qdescrs ] ; short names (descriptors)
|
96
|
+
# [ SP "DESC" SP qdstring ] ; description
|
97
|
+
# [ SP "OBSOLETE" ] ; not active
|
98
|
+
# SP "APPLIES" SP oids ; attribute types
|
99
|
+
# extensions WSP RPAREN ; extensions
|
100
|
+
|
101
|
+
### Returns the matchingRuleUse as a String, which is the RFC4512-style schema
|
102
|
+
### description.
|
103
|
+
def to_s
|
104
|
+
parts = [ self.oid ]
|
105
|
+
|
106
|
+
parts << "NAME %s" % Treequel::Schema.qdescrs( self.names ) unless self.names.empty?
|
107
|
+
|
108
|
+
parts << "DESC '%s'" % [ self.desc ] if self.desc
|
109
|
+
parts << "OBSOLETE" if self.obsolete?
|
110
|
+
parts << "APPLIES %s" % [ Treequel::Schema.oids(self.attr_oids) ]
|
111
|
+
parts << self.extensions.strip unless self.extensions.empty?
|
112
|
+
|
113
|
+
return "( %s )" % [ parts.join(' ') ]
|
114
|
+
end
|
115
|
+
|
116
|
+
|
93
117
|
### Return a human-readable representation of the object suitable for debugging
|
94
118
|
def inspect
|
95
119
|
return "#<%s:0x%0x %s(%s) %p -> %p >" % [
|