treequel 1.0.4 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 >" % [
|