Class: Ldapter::Entry

Inherits:
Object
  • Object
show all
Defined in:
lib/ldapter/entry.rb

Overview

When a new Ldapter namespace is created, a Ruby class hierarchy is contructed that mirrors the server's object classes. Ldapter::Entry serves as the base class for this hierarchy.

Constant Summary

DEFAULT_ATTRIBUTE_NAMES =
{
  'dn'     => 'distinguishedName',
  'cn'     => 'commonName',
  'l'      => 'localityName',
  'st'     => 'stateOrProvinceName',
  'o'      => 'organizationName',
  'ou'     => 'organizatonalUnitName',
  'c'      => 'countryName',
  'street' => 'streetAddress',
  'dc'     => 'domainComponent',
  'uid'    => 'userId',
  'co'     => 'friendlyCountryName',
  'sn'     => 'surname'
}

Class Attribute Summary (collapse)

Instance Attribute Summary (collapse)

Class Method Summary (collapse)

Instance Method Summary (collapse)

Constructor Details

- (Entry) initialize(data = {})

A new instance of Entry



180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/ldapter/entry.rb', line 180

def initialize(data = {})
  Ldapter::Errors.raise(TypeError.new("abstract class initialized")) if self.class.oid.nil? || self.class.abstract?
  @attributes = {}
  data = data.dup
  if dn = data.delete('dn')||data.delete(:dn)
    dn.first if dn.kind_of?(Array)
    self.dn = dn
  end
  merge_attributes(data)
  @attributes['objectClass'] ||= []
  @attributes['objectClass'].insert(0,*self.class.object_classes).uniq!
  common_initializations
  after_build
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

- (Object) method_missing(method, *args, &block)

Delegates to read_attribute or write_attribute.



403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
# File 'lib/ldapter/entry.rb', line 403

def method_missing(method,*args,&block)
  attribute = LDAP.encode(method)
  if attribute[-1] == ?=
    attribute.chop!
    if may_must(attribute)
      return write_attribute(attribute,*args,&block)
    end
  elsif attribute[-1] == ??
    attribute.chop!
    if may_must(attribute)
      if args.empty?
        return !read_attribute(attribute,true).empty?
      else
        return args.flatten.any? {|arg| compare(attribute, arg)}
      end
    end
  elsif may_must(attribute)
    return read_attribute(attribute,*args,&block)
  end
  super(method,*args,&block)
end

Class Attribute Details

+ (Object) desc (readonly)

Returns the value of attribute desc



21
22
23
# File 'lib/ldapter/entry.rb', line 21

def desc
  @desc
end

+ (Object) namespace (readonly)

Returns the value of attribute namespace



61
62
63
# File 'lib/ldapter/entry.rb', line 61

def namespace
  @namespace
end

+ (Object) oid (readonly)

Returns the value of attribute oid



21
22
23
# File 'lib/ldapter/entry.rb', line 21

def oid
  @oid
end

+ (Object) sup (readonly)

Returns the value of attribute sup



21
22
23
# File 'lib/ldapter/entry.rb', line 21

def sup
  @sup
end

Instance Attribute Details

- (Object) dn

Returns the value of attribute dn



223
224
225
# File 'lib/ldapter/entry.rb', line 223

def dn
  @dn
end

Class Method Details

+ (Object) attributes(all = true)



106
107
108
# File 'lib/ldapter/entry.rb', line 106

def attributes(all = true)
  may(all) + must(all)
end

+ (Object) aux



98
99
100
101
102
103
104
# File 'lib/ldapter/entry.rb', line 98

def aux
  if dit_content_rule
    Array(dit_content_rule.aux)
  else
    []
  end
end

+ (Object) clone_ldap_hash(attributes)

Constructs a deep copy of a set of LDAP attributes, normalizing them to arrays as appropriate. The returned hash has a default value of [].



11
12
13
14
15
16
17
18
# File 'lib/ldapter/entry.rb', line 11

def self.clone_ldap_hash(attributes) #:nodoc:
  hash = Hash.new
  attributes.each do |k,v|
    k = k.kind_of?(Symbol) ?  k.to_s.tr('_','-') : k.dup
    hash[k] = Array(v).map {|x| x.dup rescue x}
  end
  hash
end

+ (Object) create_accessors

:nodoc:



42
43
44
45
46
47
48
49
50
51
52
# File 'lib/ldapter/entry.rb', line 42

def create_accessors #:nodoc:
  to_be_evaled = ""
  (may(false) + must(false)).each do |attr|
    method = attr.to_s.tr_s('-_','_-')
    to_be_evaled << <<-RUBY
    def #{method}(*args,&block) read_attribute('#{attr}',*args,&block) end
    def #{method}=(value) write_attribute('#{attr}',value) end
    RUBY
  end
  class_eval(to_be_evaled, __FILE__, __LINE__)
end

+ (Object) dit_content_rule



110
111
112
# File 'lib/ldapter/entry.rb', line 110

def dit_content_rule
  namespace.adapter.dit_content_rules[oid]
end

+ (Boolean) has_attribute?(attribute)

Returns:

  • (Boolean)


37
38
39
40
# File 'lib/ldapter/entry.rb', line 37

def has_attribute?(attribute)
  attribute = LDAP.encode(attribute)
  may.include?(attribute) || must.include?(attribute)
end

+ (Object) human_attribute_name(attribute, options = {})

Converts an attribute name to a human readable form. For compatibility with ActiveRecord.

L::User.human_attribute_name(:givenName) #=> "Given name"


143
144
145
146
147
148
149
150
151
# File 'lib/ldapter/entry.rb', line 143

def human_attribute_name(attribute, options={})
  attribute = LDAP.encode(attribute)
  attribute = DEFAULT_ATTRIBUTE_NAMES[attribute] || attribute
  attribute = attribute[0..0].upcase + attribute[1..-1]
  attribute.gsub!(/([A-Z])([A-Z][a-z])/) { "#$1 #{$2.downcase}" }
  attribute.gsub!(/([a-z\d])([A-Z])/) { "#$1 #{$2.downcase}" }
  attribute.gsub!('_','-')
  attribute
end

+ (Object) instantiate(attributes)

:nodoc:



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/ldapter/entry.rb', line 153

def instantiate(attributes) #:nodoc:
  ocs = attributes["objectClass"].to_a.map {|c| namespace.object_class(c)}
  subclass = (@subclasses.to_a & ocs).detect {|x| !x.auxiliary?}
  if subclass
    return subclass.instantiate(attributes)
  end
  unless structural? || ocs.empty?
    logger.warn("ldapter") { "#{self}: invalid object class for #{attributes.inspect}" }
  end
  obj = allocate
  obj.instance_variable_set(:@dn, ::LDAP::DN(Array(attributes.delete('dn')).first,obj))
  obj.instance_variable_set(:@original_attributes, attributes)
  obj.instance_variable_set(:@attributes, {})
  obj.instance_eval { common_initializations; after_load }
  obj
end

+ (Object) ldap_ancestors

An array of classes that make up the inheritance hierarchy.

L::OrganizationalPerson.ldap_ancestors #=> [L::OrganizationalPerson, L::Person, L::Top]


57
58
59
# File 'lib/ldapter/entry.rb', line 57

def ldap_ancestors
  ancestors.select {|o| o.respond_to?(:oid) && o.oid }
end

+ (Object) logger



26
27
28
# File 'lib/ldapter/entry.rb', line 26

def logger
  namespace.logger
end

+ (Object) may(all = true)



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/ldapter/entry.rb', line 63

def may(all = true)
  if all
    core = []
    nott = []
    ldap_ancestors.reverse.each do |klass|
      core |= Array(klass.may(false))
      nott |= Array(klass.must(false))
    end
    if dit = dit_content_rule
      core.push(*Array(dit.may))
      core -= Array(dit.must)
      core -= Array(dit.not)
    end
    core -= nott
    core
  else
    Array(@may)
  end
end

+ (Object) must(all = true)



83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/ldapter/entry.rb', line 83

def must(all = true)
  if all
    core = ldap_ancestors.inject([]) do |memo,klass|
      memo |= Array(klass.must(false))
      memo
    end
    if dit = dit_content_rule
      core.push(*Array(dit.must))
    end
    core
  else
    Array(@must)
  end
end

+ (Object) names

Returns an array of all names for the object class. Typically the number of names is one, but it is possible for an object class to have aliases.



33
34
35
# File 'lib/ldapter/entry.rb', line 33

def names
  Array(@name)
end

+ (Object) object_class



114
115
116
# File 'lib/ldapter/entry.rb', line 114

def object_class
  @object_class || names.first
end

+ (Object) object_classes Also known as: objectClass



118
119
120
# File 'lib/ldapter/entry.rb', line 118

def object_classes
  ldap_ancestors.map {|a| a.object_class}.compact.reverse.uniq
end

Instance Method Details

- (Object) /(*args) Also known as: find

Searches for a child, given an RDN.



435
436
437
# File 'lib/ldapter/entry.rb', line 435

def /(*args)
  search(:base => dn.send(:/,*args), :scope => :base, :limit => true)
end

- (Object) [](key)

If a Hash or a String containing "=" is given, the argument is treated as an RDN and a search for a child is performed. nil is returned if no match is found.

For a singular String or Symbol argument, that attribute is read with read_attribute. Unlike with method_missing, an array is always returned, making this variant useful for metaprogramming.



452
453
454
455
456
457
458
# File 'lib/ldapter/entry.rb', line 452

def [](key)
  if key.kind_of?(Hash) || key =~ /=/
    cached_child(key)
  else
    read_attribute(key, true)
  end
end

- (Object) []=(key, value)



460
461
462
463
464
465
466
# File 'lib/ldapter/entry.rb', line 460

def []=(key, value)
  if key.kind_of?(Hash) || key =~ /=/
    assign_child(key, value)
  else
    write_attribute(key, value)
  end
end

- (Object) add!(key, *values)

:nodoc:



350
351
352
# File 'lib/ldapter/entry.rb', line 350

def add!(key, *values) #:nodoc:
  modify_attribute(:add, key, values)
end

- (Object) attribute_names

attr_reader :attributes



369
370
371
# File 'lib/ldapter/entry.rb', line 369

def attribute_names
  attributes.keys
end

- (Object) attributes

Returns a hash of attributes.



295
296
297
298
299
300
# File 'lib/ldapter/entry.rb', line 295

def attributes
  (@original_attributes||{}).merge(@attributes).keys.inject({}) do |hash,key|
    hash[key] = read_attribute(key)
    hash
  end
end

- (Object) aux



377
378
379
# File 'lib/ldapter/entry.rb', line 377

def aux
  self['objectClass'].map {|c| namespace.object_class(c)} - self.class.ldap_ancestors
end

- (Object) changes



302
303
304
305
306
307
308
309
# File 'lib/ldapter/entry.rb', line 302

def changes
  @attributes.reject do |k,v|
    @original_attributes && @original_attributes[k] == v
  end.keys.inject({}) do |hash,key|
    hash[key] = read_attribute(key)
    hash
  end
end

- (Object) compare(key, value)

Compare an attribute to see if it has a given value. This happens at the server.



364
365
366
# File 'lib/ldapter/entry.rb', line 364

def compare(key, value)
  namespace.adapter.compare(dn, LDAP.encode(key), LDAP.encode(value))
end

- (Object) delete Also known as: destroy

Deletes the object from the server and freezes it locally.



506
507
508
509
510
511
512
# File 'lib/ldapter/entry.rb', line 506

def delete
  namespace.adapter.delete(dn)
  if @parent
    @parent.instance_variable_get(:@children).delete(rdn)
  end
  freeze
end

- (Object) delete!(key, *values)

:nodoc:



358
359
360
# File 'lib/ldapter/entry.rb', line 358

def delete!(key, *values) #:nodoc:
  modify_attribute(:delete, key, values)
end

- (Object) fetch(dn = self.dn, options = {})

:nodoc:



441
442
443
# File 'lib/ldapter/entry.rb', line 441

def fetch(dn = self.dn, options = {}) #:nodoc:
  search({:base => dn}.merge(options))
end

- (Object) inspect



249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
# File 'lib/ldapter/entry.rb', line 249

def inspect
  str = "#<#{self.class.inspect} #{dn}"
  (@original_attributes||{}).merge(@attributes).each do |k,values|
    s = (values.size == 1 ? "" : "s")
    at = namespace.attribute_type(k)
    syntax = namespace.attribute_syntax(k)
    if at && syntax && !syntax.x_not_human_readable? && syntax.desc != "Octet String"
      str << " " << k << ": " << values.inspect
    else
      str << " " << k << ": "
      if !at
        str << "(unknown attribute)"
      elsif !syntax
        str << "(unknown type)"
      else
        str << "(" << values.size.to_s << " binary value" << s << ")"
      end
    end
  end
  str << ">"
end

- (Object) ldap_ancestors



373
374
375
# File 'lib/ldapter/entry.rb', line 373

def ldap_ancestors
  self.class.ldap_ancestors | objectClass.map {|c|namespace.object_class(c)}
end

- (Object) logger



214
215
216
# File 'lib/ldapter/entry.rb', line 214

def logger
  self.class.logger
end

- (Object) may(all = true)



385
386
387
# File 'lib/ldapter/entry.rb', line 385

def may(all = true)
  return self.class.may(all)  + aux.map {|a|a.may(false)}.flatten
end

- (Object) may_must(attribute)



389
390
391
392
393
394
395
396
# File 'lib/ldapter/entry.rb', line 389

def may_must(attribute)
  attribute = LDAP.encode(attribute)
  if must.include?(attribute)
    :must
  elsif may.include?(attribute)
    :may
  end
end

- (Object) merge_attributes(data)



195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/ldapter/entry.rb', line 195

def merge_attributes(data)
  # If it's a HashWithIndifferentAccess (eg, params in Rails), convert it
  # to a Hash with symbolic keys.  This causes the underscore/hyphen
  # translation to take place in write_attribute.  Form helpers in Rails
  # use a method name to read data,
  if defined?(::HashWithIndifferentAccess) && data.is_a?(HashWithIndifferentAccess)
    data = data.symbolize_keys
  end
  data.each do |key,value|
    write_attribute(key,value)
  end
end

- (Object) modify_attributes(mods)

Commit an array of modifications directly to LDAP, without updating the local object.



345
346
347
348
# File 'lib/ldapter/entry.rb', line 345

def modify_attributes(mods) #:nodoc:
  namespace.adapter.modify(dn, mods.map {|(action,key,values)| [action,LDAP.encode(key),Array(values)]})
  self
end

- (Object) must(all = true)



381
382
383
# File 'lib/ldapter/entry.rb', line 381

def must(all = true)
  return self.class.must(all) + aux.map {|a|a.must(false)}.flatten
end

- (Object) namespace

A link back to the namespace.



210
211
212
# File 'lib/ldapter/entry.rb', line 210

def namespace
  @namespace || self.class.namespace
end

- (Boolean) new_entry?

Has the object not been saved before?

Returns:

  • (Boolean)


469
470
471
# File 'lib/ldapter/entry.rb', line 469

def new_entry?
  !@original_attributes
end

- (Object) parent

The parent object containing this one.



241
242
243
244
245
246
247
# File 'lib/ldapter/entry.rb', line 241

def parent
  unless @parent
    @parent = search(:base => dn.parent, :scope => :base, :limit => true)
    @parent.instance_variable_get(:@children)[rdn] = self
  end
  @parent
end

- (Boolean) persisted?

Has the object been saved before?

Returns:

  • (Boolean)


474
475
476
# File 'lib/ldapter/entry.rb', line 474

def persisted?
  !new_entry?
end

- (Object) rdn

The first (relative) component of the distinguished name.



226
227
228
# File 'lib/ldapter/entry.rb', line 226

def rdn
  dn && dn.rdn
end

- (Object) reload

Refetches the attributes from the server.



496
497
498
499
500
501
502
503
# File 'lib/ldapter/entry.rb', line 496

def reload
  new = search(:scope => :base, :limit => true)
  @original_attributes = new.instance_variable_get(:@original_attributes)
  @attributes          = new.instance_variable_get(:@attributes)
  @dn                  = LDAP::DN(new.dn, self)
  @children            = {}
  self
end

- (Object) rename(new_rdn, delete_old = nil)



516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
# File 'lib/ldapter/entry.rb', line 516

def rename(new_rdn, delete_old = nil)
  old_rdn = rdn
  if new_rdn.kind_of?(LDAP::DN)
    new_root = new_rdn.parent
    new_rdn = new_rdn.rdn
  else
    new_rdn = LDAP::RDN(new_rdn)
    new_root = nil
  end
  if delete_old.nil?
    delete_old = (new_rdn == old_rdn)
  end
  namespace.adapter.rename(dn,new_rdn.to_str,delete_old, *[new_root].compact)
  if delete_old
    old_rdn.each do |k,v|
      [@attributes, @original_attributes].each do |hash|
        hash.delete_if {|k2,v2| k.to_s.downcase == k2.to_s.downcase && v.to_s.downcase == v2.to_s.downcase }
        end
    end
  end
  old_dn = LDAP::DN(@dn,self)
  @dn = nil
  if new_root
    self.dn = new_root / new_rdn
  else
    self.dn = old_dn.parent / new_rdn
  end
  write_attributes_from_rdn(rdn, @original_attributes)
  if @parent
    children = @parent.instance_variable_get(:@children)
    if child = children.delete(old_rdn)
      children[new_rdn] = child if child == self
    end
  end
  self
end

- (Object) replace!(key, *values)

:nodoc:



354
355
356
# File 'lib/ldapter/entry.rb', line 354

def replace!(key, *values) #:nodoc:
  modify_attribute(:replace, key, values)
end

- (Boolean) respond_to?(method, *args)

:nodoc:

Returns:

  • (Boolean)


398
399
400
# File 'lib/ldapter/entry.rb', line 398

def respond_to?(method, *args) #:nodoc:
  super || (may + must + (may+must).map {|x| "#{x}="}).include?(method.to_s.tr('-_','_-'))
end

- (Object) save

For new objects, does an LDAP add. For existing objects, does an LDAP modify. This only sends the modified attributes to the server.



480
481
482
483
484
485
486
487
488
489
490
491
492
493
# File 'lib/ldapter/entry.rb', line 480

def save
  return false if respond_to?(:valid?) && !valid?
  if @original_attributes
    updates = @attributes.reject do |k,v|
      @original_attributes[k] == v
    end
    namespace.adapter.modify(dn, updates) unless updates.empty?
  else
    namespace.adapter.add(dn, @attributes)
  end
  @original_attributes = (@original_attributes||{}).merge(@attributes)
  @attributes = {}
  self
end

- (Object) search(options, &block)

Searches for children. This is identical to Ldapter::Base#search, only the default base is the current object's DN.



427
428
429
430
431
432
# File 'lib/ldapter/entry.rb', line 427

def search(options,&block)
  if options[:base].kind_of?(Hash)
    options = options.merge(:base => dn/options[:base])
  end
  namespace.search({:base => dn}.merge(options),&block)
end

- (Object) to_key

Returns an array containing the DN. For ActiveModel compatibility.



231
232
233
# File 'lib/ldapter/entry.rb', line 231

def to_key
  [dn] if persisted?
end

- (Object) to_model

Returns self. For ActiveModel compatibility.



219
220
221
# File 'lib/ldapter/entry.rb', line 219

def to_model
  self
end

- (Object) to_param

Returns the DN. For ActiveModel compatibility.



236
237
238
# File 'lib/ldapter/entry.rb', line 236

def to_param
  dn if persisted?
end

- (Object) to_s



271
272
273
# File 'lib/ldapter/entry.rb', line 271

def to_s
  "#<#{self.class} #{dn}>"
end