Class: Ldaptic::Entry

Inherits:
Object
  • Object
show all
Extended by:
ActiveModel::Naming
Includes:
ActiveModel::Callbacks, ActiveModel::Dirty, ActiveModel::Serializers::JSON, ActiveModel::Serializers::Xml, ActiveModel::Validations, BeforeTypeCast
Defined in:
lib/ldaptic/entry.rb,
lib/ldaptic/railtie.rb,
lib/ldaptic/active_model.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.



174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/ldaptic/entry.rb', line 174

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.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.



397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
# File 'lib/ldaptic/entry.rb', line 397

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.



67
68
69
# File 'lib/ldaptic/entry.rb', line 67

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.



218
219
220
# File 'lib/ldaptic/entry.rb', line 218

def dn
  @dn
end

Class Method Details

.attributes(all = true) ⇒ Object



112
113
114
# File 'lib/ldaptic/entry.rb', line 112

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

.auxObject



104
105
106
107
108
109
110
# File 'lib/ldaptic/entry.rb', line 104

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
# File 'lib/ldaptic/entry.rb', line 48

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}() read_attribute('#{attr}').one end
    def #{method}=(value) write_attribute('#{attr}', value) end
    RUBY
  end
  class_eval(to_be_evaled, __FILE__, __LINE__)
end

.dit_content_ruleObject



116
117
118
# File 'lib/ldaptic/entry.rb', line 116

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"


134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/ldaptic/entry.rb', line 134

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:



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/ldaptic/entry.rb', line 147

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]


63
64
65
# File 'lib/ldaptic/entry.rb', line 63

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



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/ldaptic/entry.rb', line 69

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



89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/ldaptic/entry.rb', line 89

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



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

def object_class
  @object_class || names.first
end

.object_classesObject Also known as: objectClass



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

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.



431
432
433
# File 'lib/ldaptic/entry.rb', line 431

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.



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

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

#[]=(key, value) ⇒ Object



456
457
458
459
460
461
462
# File 'lib/ldaptic/entry.rb', line 456

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:



342
343
344
# File 'lib/ldaptic/entry.rb', line 342

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

#attribute_namesObject

attr_reader :attributes



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

def attribute_names
  attributes.keys
end

#attributesObject

Returns a hash of attributes.



283
284
285
286
287
288
# File 'lib/ldaptic/entry.rb', line 283

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

#auxObject



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

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

#changesObject



290
291
292
293
294
295
296
297
# File 'lib/ldaptic/entry.rb', line 290

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.



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

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.



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

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

#delete!(key, *values) ⇒ Object

:nodoc:



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

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

#destroyObject

Alias for #delete.



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

def destroy
  delete
end

#errorsObject



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

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

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

:nodoc:



437
438
439
# File 'lib/ldaptic/entry.rb', line 437

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

#inspectObject



244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/ldaptic/entry.rb', line 244

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)


480
481
482
# File 'lib/ldaptic/entry.rb', line 480

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

#ldap_ancestorsObject



365
366
367
# File 'lib/ldaptic/entry.rb', line 365

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

#loggerObject



209
210
211
# File 'lib/ldaptic/entry.rb', line 209

def logger
  self.class.logger
end

#may(all = true) ⇒ Object



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

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

#may_must(attribute) ⇒ Object



381
382
383
384
385
386
387
388
# File 'lib/ldaptic/entry.rb', line 381

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

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



189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/ldaptic/entry.rb', line 189

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.



337
338
339
340
# File 'lib/ldaptic/entry.rb', line 337

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

#must(all = true) ⇒ Object



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

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

#namespaceObject

A link back to the namespace.



205
206
207
# File 'lib/ldaptic/entry.rb', line 205

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

#parentObject

The parent object containing this one.



236
237
238
239
240
241
242
# File 'lib/ldaptic/entry.rb', line 236

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)


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

def persisted?
  !!@original_attributes
end

#rdnObject

The first (relative) component of the distinguished name.



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

def rdn
  dn && dn.rdn
end

#read_attribute_for_validation(attribute) ⇒ Object



24
25
26
# File 'lib/ldaptic/active_model.rb', line 24

def read_attribute_for_validation(attribute)
  read_attribute(attribute.to_sym, true)
end

#reloadObject

Refetches the attributes from the server.



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

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



554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
# File 'lib/ldaptic/entry.rb', line 554

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:



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

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

#respond_to?(method) ⇒ Boolean

:nodoc:

Returns:

  • (Boolean)


390
391
392
393
# File 'lib/ldaptic/entry.rb', line 390

def respond_to?(method, *) #:nodoc:
  both = may + must
  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.



501
502
503
504
505
506
507
508
509
510
511
# File 'lib/ldaptic/entry.rb', line 501

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.



514
515
516
# File 'lib/ldaptic/entry.rb', line 514

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.



423
424
425
426
427
428
# File 'lib/ldaptic/entry.rb', line 423

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.



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

def to_key
  [dn] if persisted?
end

#to_modelObject

Returns self. For ActiveModel compatibility.



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

def to_model
  self
end

#to_paramObject

Returns the DN. For ActiveModel compatibility.



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

def to_param
  dn if persisted?
end

#to_sObject



267
268
269
# File 'lib/ldaptic/entry.rb', line 267

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

#update_attributes(hash) ⇒ Object

Assign the given attribute hash, then #save.



519
520
521
522
# File 'lib/ldaptic/entry.rb', line 519

def update_attributes(hash)
  merge_attributes(hash)
  save
end

#update_attributes!(hash) ⇒ Object

Like #update_attributes but raise on failure.



525
526
527
528
# File 'lib/ldaptic/entry.rb', line 525

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

#valid?Boolean

Returns:

  • (Boolean)


473
474
475
476
477
# File 'lib/ldaptic/entry.rb', line 473

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