Class: Ldaptic::Entry

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

Overview

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

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(data = {}) ⇒ Entry

Returns a new instance of Entry.



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

def initialize(data = {})
  Ldaptic::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 = 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

#method_missing(method, *args, &block) ⇒ Object

Delegates to read_attribute or write_attribute. Pops an element out of its set if the attribute is marked SINGLE-VALUE.



423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
# File 'lib/ldaptic/entry.rb', line 423

def method_missing(method, *args, &block)
  attribute = Ldaptic.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).empty?
      else
        return args.flatten.any? {|arg| compare(attribute, arg)}
      end
    end
  elsif attribute =~ /\A(.*)-before-type-cast\z/ && may_must($1)
    return read_attribute($1, *args, &block)
  elsif may_must(attribute)
    return read_attribute(attribute, *args, &block).one
  end
  super(method, *args, &block)
end

Class Attribute Details

.descObject (readonly)

Returns the value of attribute desc.



27
28
29
# File 'lib/ldaptic/entry.rb', line 27

def desc
  @desc
end

.namespaceObject (readonly)

Returns the value of attribute namespace.



74
75
76
# File 'lib/ldaptic/entry.rb', line 74

def namespace
  @namespace
end

.oidObject (readonly)

Returns the value of attribute oid.



27
28
29
# File 'lib/ldaptic/entry.rb', line 27

def oid
  @oid
end

.supObject (readonly)

Returns the value of attribute sup.



27
28
29
# File 'lib/ldaptic/entry.rb', line 27

def sup
  @sup
end

Instance Attribute Details

#dnObject

Returns the value of attribute dn.



225
226
227
# File 'lib/ldaptic/entry.rb', line 225

def dn
  @dn
end

Class Method Details

.attributes(all = true) ⇒ Object



119
120
121
# File 'lib/ldaptic/entry.rb', line 119

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

.auxObject



111
112
113
114
115
116
117
# File 'lib/ldaptic/entry.rb', line 111

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

.clone_ldap_hash(attributes) ⇒ Object

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



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

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

.create_accessorsObject

:nodoc:



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/ldaptic/entry.rb', line 48

def create_accessors #:nodoc:
  to_be_evaled = ""
  (may(false) + must(false)).each do |attr|
    if type = namespace.attribute_type(attr)
      names = type.names
    else
      names = [attr]
    end
    names.each do |name|
      method = name.to_s.tr_s('-_', '_-')
      to_be_evaled << <<-RUBY
      def #{method}() read_attribute('#{attr}').one end
      def #{method}=(value) write_attribute('#{attr}', value) end
      RUBY
    end
  end
  class_eval(to_be_evaled, __FILE__, __LINE__)
end

.dit_content_ruleObject



123
124
125
# File 'lib/ldaptic/entry.rb', line 123

def dit_content_rule
  namespace.dit_content_rule(oid)
end

.has_attribute?(attribute) ⇒ Boolean

Returns:

  • (Boolean)


43
44
45
46
# File 'lib/ldaptic/entry.rb', line 43

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

.human_attribute_name(attribute, options = {}) ⇒ Object

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

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


141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/ldaptic/entry.rb', line 141

def human_attribute_name(attribute, options={})
  attribute = Ldaptic.encode(attribute)
  if at = namespace.attribute_type(attribute)
    attribute = at.verbose_name
  end
  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!(/\b[a-z][A-Z]/) { $&.upcase }
  attribute.gsub!('_', '-')
  attribute
end

.instantiate(attributes) ⇒ Object

:nodoc:



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

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 "#{name}: invalid object class for #{attributes.inspect}"
  end
  obj = allocate
  obj.instance_variable_set(:@dn, ::Ldaptic::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

.ldap_ancestorsObject

An array of classes that make up the inheritance hierarchy.

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


70
71
72
# File 'lib/ldaptic/entry.rb', line 70

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

.loggerObject



32
33
34
# File 'lib/ldaptic/entry.rb', line 32

def logger
  namespace.logger
end

.may(all = true) ⇒ Object



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/ldaptic/entry.rb', line 76

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

.model_nameObject

For Active Model compliance. Delegates to #namespace.



22
23
24
# File 'lib/ldaptic/entry.rb', line 22

def self.model_name
  namespace.model_name
end

.must(all = true) ⇒ Object



96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/ldaptic/entry.rb', line 96

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

.namesObject

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.



39
40
41
# File 'lib/ldaptic/entry.rb', line 39

def names
  Array(@name)
end

.object_classObject



127
128
129
# File 'lib/ldaptic/entry.rb', line 127

def object_class
  @object_class || names.first
end

.object_classesObject Also known as: objectClass



131
132
133
# File 'lib/ldaptic/entry.rb', line 131

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

Instance Method Details

#/(*args) ⇒ Object Also known as: find

Searches for a child, given an RDN.



457
458
459
# File 'lib/ldaptic/entry.rb', line 457

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

#[](key) ⇒ Object

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.



477
478
479
480
481
482
483
# File 'lib/ldaptic/entry.rb', line 477

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

#[]=(key, value) ⇒ Object



485
486
487
488
489
490
491
# File 'lib/ldaptic/entry.rb', line 485

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

#add!(key, *values) ⇒ Object

:nodoc:



353
354
355
# File 'lib/ldaptic/entry.rb', line 353

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

#attribute_namesObject



371
372
373
# File 'lib/ldaptic/entry.rb', line 371

def attribute_names
  attributes.keys
end

#attributesObject

Returns a hash of attributes.



294
295
296
297
298
299
# File 'lib/ldaptic/entry.rb', line 294

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

#auxObject



379
380
381
# File 'lib/ldaptic/entry.rb', line 379

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

#changesObject



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

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

#compare(key, value) ⇒ Object

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



367
368
369
# File 'lib/ldaptic/entry.rb', line 367

def compare(key, value)
  namespace.compare(dn, key, value)
end

#deleteObject

Deletes the object from the server. If #save is invoked afterwards, the entry will be recreated.



571
572
573
574
575
576
# File 'lib/ldaptic/entry.rb', line 571

def delete
  namespace.delete(dn)
  @attributes = (@original_attributes||{}).merge(@attributes)
  @original_attributes = nil
  self
end

#delete!(key, *values) ⇒ Object

:nodoc:



361
362
363
# File 'lib/ldaptic/entry.rb', line 361

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

#destroyObject

Alias for #delete.



579
580
581
# File 'lib/ldaptic/entry.rb', line 579

def destroy
  delete
end

#errorsObject



498
499
500
# File 'lib/ldaptic/entry.rb', line 498

def errors
  @errors ||= Ldaptic::ErrorSet.new(self)
end

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

:nodoc:



463
464
465
466
467
468
# File 'lib/ldaptic/entry.rb', line 463

def fetch(dn = self.dn, options = {}) #:nodoc:
  if dn.kind_of?(Hash)
    dn = self.dn/dn
  end
  namespace.fetch(dn, options)
end

#inspectObject



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

def inspect
  str = "#<#{self.class.inspect} #{dn}"
  (@original_attributes||{}).merge(@attributes).each do |k, values|
    next if values.empty?
    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[1..-2]
    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

#invalid?(*args) ⇒ Boolean

Inverse of #valid?

Returns:

  • (Boolean)


509
510
511
# File 'lib/ldaptic/entry.rb', line 509

def invalid?(*args)
  !valid?(*args)
end

#ldap_ancestorsObject



375
376
377
# File 'lib/ldaptic/entry.rb', line 375

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

#loggerObject



216
217
218
# File 'lib/ldaptic/entry.rb', line 216

def logger
  self.class.logger
end

#may(include_aliases = false) ⇒ Object



395
396
397
398
399
400
401
402
403
404
405
# File 'lib/ldaptic/entry.rb', line 395

def may(include_aliases = false)
  attrs = (self.class.may + aux.map {|a| a.may(false)}.flatten).uniq - must
  if include_aliases
    attrs.map do |attr|
      type = namespace.attribute_type(attr)
      type ? type.names : attr
    end.flatten
  else
    attrs
  end
end

#may_must(attribute) ⇒ Object



407
408
409
410
411
412
413
414
# File 'lib/ldaptic/entry.rb', line 407

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

#merge_attributes(data) ⇒ Object Also known as: attributes=



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

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

#modify_attributes(mods) ⇒ Object

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



348
349
350
351
# File 'lib/ldaptic/entry.rb', line 348

def modify_attributes(mods) #:nodoc:
  namespace.modify(dn, mods)
  self
end

#must(include_aliases = false) ⇒ Object



383
384
385
386
387
388
389
390
391
392
393
# File 'lib/ldaptic/entry.rb', line 383

def must(include_aliases = false)
  attrs = (self.class.must + aux.map {|a| a.must(false)}.flatten).uniq
  if include_aliases
    attrs.map do |attr|
      type = namespace.attribute_type(attr)
      type ? type.names : attr
    end.flatten
  else
    attrs
  end
end

#namespaceObject

A link back to the namespace.



212
213
214
# File 'lib/ldaptic/entry.rb', line 212

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

#parentObject

The parent object containing this one.



243
244
245
246
247
248
249
# File 'lib/ldaptic/entry.rb', line 243

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

#persisted?Boolean

Has the object been saved before?

Returns:

  • (Boolean)


494
495
496
# File 'lib/ldaptic/entry.rb', line 494

def persisted?
  !!@original_attributes
end

#rdnObject

The first (relative) component of the distinguished name.



228
229
230
# File 'lib/ldaptic/entry.rb', line 228

def rdn
  dn && dn.rdn
end

#reloadObject

Refetches the attributes from the server.



560
561
562
563
564
565
566
567
# File 'lib/ldaptic/entry.rb', line 560

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

#rename(new_rdn, delete_old = nil) ⇒ Object



583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
# File 'lib/ldaptic/entry.rb', line 583

def rename(new_rdn, delete_old = nil)
  old_rdn = rdn
  if new_rdn.kind_of?(Ldaptic::DN)
    new_root = new_rdn.parent
    new_rdn = new_rdn.rdn
  else
    new_rdn = Ldaptic::RDN(new_rdn)
    new_root = nil
  end
  if delete_old.nil?
    delete_old = (new_rdn == old_rdn)
  end
  namespace.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 = Ldaptic::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

#replace!(key, *values) ⇒ Object

:nodoc:



357
358
359
# File 'lib/ldaptic/entry.rb', line 357

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

#respond_to?(method) ⇒ Boolean

:nodoc:

Returns:

  • (Boolean)


416
417
418
419
# File 'lib/ldaptic/entry.rb', line 416

def respond_to?(method, *) #:nodoc:
  both = may(true) + must(true)
  super || (both + both.map {|x| "#{x}="} + both.map {|x| "#{x}-before-type-cast"}).include?(Ldaptic.encode(method.to_sym))
end

#saveObject

For new objects, does an LDAP add. For existing objects, does an LDAP modify. This only sends the modified attributes to the server. If a server constraint was violated, populates #errors and returns false.



530
531
532
533
534
535
536
537
538
539
540
# File 'lib/ldaptic/entry.rb', line 530

def save
  return false unless valid?
  if persisted?
    namespace.modify(dn, changes)
  else
    namespace.add(dn, changes)
  end
  @original_attributes = (@original_attributes||{}).merge(@attributes)
  @attributes = {}
  true
end

#save!Object

Like #save, but raise an exception if the entry could not be saved.



543
544
545
# File 'lib/ldaptic/entry.rb', line 543

def save!
  save ? self : raise(EntryNotSaved)
end

#search(options, &block) ⇒ Object

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



449
450
451
452
453
454
# File 'lib/ldaptic/entry.rb', line 449

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

#to_keyObject

Returns an array containing the DN. For ActiveModel compatibility.



233
234
235
# File 'lib/ldaptic/entry.rb', line 233

def to_key
  [dn] if persisted?
end

#to_modelObject

Returns self. For ActiveModel compatibility.



221
222
223
# File 'lib/ldaptic/entry.rb', line 221

def to_model
  self
end

#to_paramObject

Returns the DN. For ActiveModel compatibility.



238
239
240
# File 'lib/ldaptic/entry.rb', line 238

def to_param
  dn if persisted?
end

#to_sObject



274
275
276
# File 'lib/ldaptic/entry.rb', line 274

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

#update_attributes(hash) ⇒ Object

Assign the given attribute hash, then #save.



548
549
550
551
# File 'lib/ldaptic/entry.rb', line 548

def update_attributes(hash)
  merge_attributes(hash)
  save
end

#update_attributes!(hash) ⇒ Object

Like #update_attributes but raise on failure.



554
555
556
557
# File 'lib/ldaptic/entry.rb', line 554

def update_attributes!(hash)
  merge_attributes(hash)
  save!
end

#valid?Boolean

Returns:

  • (Boolean)


502
503
504
505
506
# File 'lib/ldaptic/entry.rb', line 502

def valid?
  errors.clear
  check_server_constraints
  errors.empty?
end