Class: UCB::LDAP::Entry

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

Overview

UCB::LDAP::Entry

Abstract class representing an entry in the UCB LDAP directory. You won’t ever deal with Entry instances, but instead instances of Entry sub-classes.

Accessing LDAP Attributes

You will not see the attributes documented in the instance method section of the documentation for Entry sub-classes, even though you can access them as if they were instance methods.

person = Person.find_by_uid("123")  #=> #<UCB::LDAP::Person ..>
people.givenname                    #=> ["John"]

Entry sub-classes may have convenience methods that allow for accessing attributes by friendly names:

person = Person.person_by_uid("123")  #=> #<UCB::LDAP::Person ..>
person.firstname                      #=> "John"

See the sub-class documentation for specifics.

Single- / Multi-Value Attributes

Attribute values are returned as arrays or scalars based on how they are defined in the LDAP schema.

Entry subclasses may have convenience methods that return scalars even though the schema defines the unerlying attribute as multi-valued becuase in practice the are single-valued.

Attribute Types

Attribute values are stored as arrays of strings in LDAP, but when accessed through Entry sub-class methods are returned cast to their Ruby type as defined in the schema. Types are one of:

  • string

  • integer

  • boolean

  • datetime

Missing Attribute Values

If an attribute value is not present, the value returned depends on type and multi/single value field:

  • empty multi-valued attributes return an empty array ([])

  • empty booleans return false

  • everything else returns nil if empty

Attempting to get or set an attribute value for an invalid attriubte name will raise a BadAttributeNameException.

Updating LDAP

If your bind has privleges for updating the directory you can update the directory using methods of Entry sub-classes. Make sure you call UCB::LDAP.authenticate before calling any update methods.

There are three pairs of update methods that behave like Rails ActiveRecord methods of the same name. These methods are fairly thin wrappers around standard LDAP update commands.

The “bang” methods (those ending in “!”) differ from their bangless counterparts in that the bang methods raise DirectoryNotUpdatedException on failure, while the bangless return false.

  • #create/#create! - class methods that do LDAP add

  • #update_attributes/#update_attributes! - instance methods that do LDAP modify

  • #delete/#delete! - instance methods that do LDAP delete

Constant Summary collapse

TESTING =
false

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(net_ldap_entry) ⇒ Entry

Returns new instance of UCB::LDAP::Entry. The argument net_ldap_entry is an instance of Net::LDAP::Entry.

You should not need to create any UCB::LDAP::Entry instances; they are created by calls to UCB::LDAP.search and friends.



88
89
90
91
92
93
94
95
96
# File 'lib/ucb_ldap/entry.rb', line 88

def initialize(net_ldap_entry) #:nodoc:
                               # Don't store Net::LDAP entry in object since it uses the block
                               # initialization method of Hash which can't be marshalled ... this
                               # means it can't be stored in a Rails session.
  @attributes = {}
  net_ldap_entry.each do |attr, value|
    @attributes[canonical(attr)] = value.map { |v| v.dup }
  end
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *args) ⇒ Object

Used to get/set attribute values.

If we can’t make an attribute name out of method, let regular method_missing() handle it.



171
172
173
174
175
# File 'lib/ucb_ldap/entry.rb', line 171

def method_missing(method, *args) #:nodoc:
  setter_method?(method) ? value_setter(method, *args) : value_getter(method)
rescue BadAttributeNameException
  return super
end

Class Method Details

.canonical(string_or_symbol) ⇒ Object

Returns the canonical representation of a symbol or string so we can look up attributes in a number of ways.



399
400
401
# File 'lib/ucb_ldap/entry.rb', line 399

def canonical(string_or_symbol)
  string_or_symbol.to_s.downcase.to_sym
end

.combine_filters(filters, operator = '&') ⇒ Object

Returns a new Net::LDAP::Filter that is the result of combining filters using operator (filters is an Array of Net::LDAP::Filter).

See Net::LDAP#& and Net::LDAP#| for details.

f1 = Net::LDAP::Filter.eq("lastname", "hansen")
f2 = Net::LDAP::Filter.eq("firstname", "steven")

combine_filters([f1, f2])      # same as: f1 & f2
combine_filters([f1, f2], '|') # same as: f1 | f2


285
286
287
# File 'lib/ucb_ldap/entry.rb', line 285

def combine_filters(filters, operator = '&')
  filters.inject { |accum, filter| accum.send(operator, filter) }
end

.create(args) ⇒ Object

Creates and returns new entry. Returns false if unsuccessful. Sets :objectclass key of args[:attributes] to object_classes read from schema.

dn = "uid=999999,ou=people,dc=example,dc=com"
attr = {
  :uid => "999999",
  :mail => "[email protected]"
}

EntrySubClass.create(:dn => dn, :attributes => attr)  #=> #<UCB::LDAP::EntrySubClass ..>

Caller is responsible for setting :dn and :attributes correctly, as well as any other validation.



249
250
251
252
253
254
# File 'lib/ucb_ldap/entry.rb', line 249

def create(args)
  args[:attributes][:objectclass] = object_classes
  result = net_ldap.add(args)
  result or return false
  find_by_dn(args[:dn])
end

.create!(args) ⇒ Object

Same as #create(), but raises DirectoryNotUpdated on failure.



268
269
270
# File 'lib/ucb_ldap/entry.rb', line 268

def create!(args)
  create(args) || raise(DirectoryNotUpdatedException)
end

.entity_nameObject

Schema entity name. Set in each subclass.



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

def entity_name
  @entity_name
end

.filter_in(attribute_name, array_of_values) ⇒ Object



229
230
231
232
# File 'lib/ucb_ldap/entry.rb', line 229

def filter_in(attribute_name, array_of_values)
  filters = array_of_values.map { |value| Net::LDAP::Filter.eq(attribute_name, value) }
  UCB::LDAP::Entry.combine_filters(filters, '|')
end

.find_by_dn(dn) ⇒ Object

Returns entry whose distinguised name is dn.



258
259
260
261
262
263
264
# File 'lib/ucb_ldap/entry.rb', line 258

def find_by_dn(dn)
  search(
      :base => dn,
      :scope => Net::LDAP::SearchScope_BaseObject,
      :filter => "objectClass=*"
  ).first
end

.make_search_filter(filter) ⇒ Object

Returns Net::LDAP::Filter. Allows for filter to be a Hash of :key => value. Filters are combined with “&”.

UCB::LDAP::Entry.make_search_filter(:uid => '123')
UCB::LDAP::Entry.make_search_filter(:a1 => v1, :a2 => v2)


296
297
298
299
300
301
302
303
304
305
306
# File 'lib/ucb_ldap/entry.rb', line 296

def make_search_filter(filter)
  return filter if filter.instance_of? Net::LDAP::Filter
  return filter if filter.instance_of? String

  filters = []
  # sort so result is predictable for unit test
  filter.keys.sort_by { |symbol| "#{symbol}" }.each do |attr|
    filters << Net::LDAP::Filter.eq("#{attr}", "#{filter[attr]}")
  end
  combine_filters(filters, "&")
end

.net_ldapObject

Returns underlying Net::LDAP instance.



406
407
408
# File 'lib/ucb_ldap/entry.rb', line 406

def net_ldap #:nodoc:
  UCB::LDAP.net_ldap
end

.object_classesObject

Returns Array of object classes making up this type of LDAP entity.



310
311
312
# File 'lib/ucb_ldap/entry.rb', line 310

def object_classes
  @object_classes ||= UCB::LDAP::Schema.schema_hash[entity_name]["objectClasses"]
end

.required_attributesObject

returns an Array of symbols where each symbol is the name of a required attribute for the Entry



321
322
323
# File 'lib/ucb_ldap/entry.rb', line 321

def required_attributes
  required_schema_attributes.keys
end

.required_schema_attributesObject

returns Hash of SchemaAttribute objects that are required for the Entry. Each SchemaAttribute object is keyed to the attribute’s name.

Note: required_schema_attributes will not return aliases, it only returns the original attributes

Example:

Person.required_schema_attribues[:cn]
=> <UCB::LDAP::Schema::Attribute:0x11c6b68>


337
338
339
340
341
342
343
# File 'lib/ucb_ldap/entry.rb', line 337

def required_schema_attributes
  required_atts = schema_attributes_hash.reject { |key, value| !value.required? }
  required_atts.reject do |key, value|
    aliases = value.aliases.map { |a| canonical(a) }
    aliases.include?(key)
  end
end

.schema_attribute(attribute_name) ⇒ Object



362
363
364
365
# File 'lib/ucb_ldap/entry.rb', line 362

def schema_attribute(attribute_name)
  schema_attributes_hash[canonical(attribute_name)] ||
      raise(BadAttributeNameException, "'#{attribute_name}' is not a recognized attribute name")
end

.schema_attributes_arrayObject

Returns an Array of Schema::Attribute for the entity.



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

def schema_attributes_array
  @schema_attributes_array || set_schema_attributes
  @schema_attributes_array
end

.schema_attributes_hashObject

Returns as Hash whose keys are the canonical attribute names and whose values are the corresponding Schema::Attributes.



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

def schema_attributes_hash
  @schema_attributes_hash || set_schema_attributes
  @schema_attributes_hash
end

.search(args = {}) ⇒ Object

Returns Array of UCB::LDAP::Entry for entries matching args. When called from a subclass, returns Array of subclass instances.

See Net::LDAP::search for more information on args.

Most common arguments are :base and :filter. Search methods of subclasses have default :base that can be overriden.

See make_search_filter for :filter options.

base = "ou=people,dc=berkeley,dc=edu"
entries = UCB::LDAP::Entry.search(:base => base, :filter => {:uid => '123'})
entries = UCB::LDAP::Entry.search(:base => base, :filter => {:sn => 'Doe', :givenname => 'John'}


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

def search(args={})
  args = args.dup
  args[:base] ||= tree_base
  args[:filter] = make_search_filter args[:filter] if args[:filter]

  results = []
  net_ldap.search(args) do |entry|
    results << new(entry)
  end
  results
end

.set_schema_attributesObject

Want an array of Schema::Attributes as well as a hash of all possible variations on a name pointing to correct array element.



424
425
426
427
428
429
430
431
432
433
434
435
436
# File 'lib/ucb_ldap/entry.rb', line 424

def set_schema_attributes
  @schema_attributes_array = []
  @schema_attributes_hash = {}
  UCB::LDAP::Schema.schema_hash[entity_name]["attributes"].each do |k, v|
    sa = UCB::LDAP::Schema::Attribute.new(v.merge("name" => k))
    @schema_attributes_array << sa
    [sa.name, sa.aliases].flatten.each do |name|
      @schema_attributes_hash[canonical(name)] = sa
    end
  end
rescue
  raise "Error loading schema attributes for entity_name '#{entity_name}'"
end

.tree_baseObject

Returns tree base for LDAP searches. Subclasses each have their own value.

Can be overridden in #search by passing in a :base parm.



444
445
446
# File 'lib/ucb_ldap/entry.rb', line 444

def tree_base
  @tree_base
end

.tree_base=(tree_base) ⇒ Object



448
449
450
# File 'lib/ucb_ldap/entry.rb', line 448

def tree_base=(tree_base)
  @tree_base = tree_base
end

.unique_object_classObject



314
315
316
# File 'lib/ucb_ldap/entry.rb', line 314

def unique_object_class
  @unique_object_class ||= UCB::LDAP::Schema.schema_hash[entity_name]["uniqueObjectClass"]
end

Instance Method Details

#assigned_attributesObject



202
203
204
# File 'lib/ucb_ldap/entry.rb', line 202

def assigned_attributes
  @assigned_attributes ||= {}
end

#attributesObject

Hash of attributes returned from underlying NET::LDAP::Entry instance. Hash keys are #canonical attribute names, hash values are attribute values as returned from LDAP, i.e. arrays.

You should most likely be referencing attributes as if they were instance methods rather than directly through this method. See top of this document.



107
108
109
# File 'lib/ucb_ldap/entry.rb', line 107

def attributes
  @attributes
end

#canonical(string_or_symbol) ⇒ Object

:nodoc:



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

def canonical(string_or_symbol) #:nodoc:
  self.class.canonical(string_or_symbol)
end

#deleteObject

Delete entry. Returns true on sucess, false on failure.



147
148
149
# File 'lib/ucb_ldap/entry.rb', line 147

def delete
  net_ldap.delete(:dn => dn)
end

#delete!Object

Same as #delete() except raises DirectoryNotUpdated on failure.



154
155
156
# File 'lib/ucb_ldap/entry.rb', line 154

def delete!
  delete || raise(DirectoryNotUpdatedException)
end

#dnObject

Returns the value of the Distinguished Name attribute.



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

def dn
  attributes[canonical(:dn)]
end

#modifyObject



216
217
218
219
220
221
222
# File 'lib/ucb_ldap/entry.rb', line 216

def modify()
  if UCB::LDAP.net_ldap.modify(:dn => dn, :operations => modify_operations)
    @assigned_attributes = nil
    return true
  end
  false
end

#modify_operationsObject



206
207
208
209
210
211
212
213
214
# File 'lib/ucb_ldap/entry.rb', line 206

def modify_operations
  ops = []
  assigned_attributes.keys.sort_by { |k| k.to_s }.each do |key|
    value = assigned_attributes[key]
    op = value.nil? ? :delete : :replace
    ops << [op, key, value]
  end
  ops
end

#net_ldapObject



158
159
160
# File 'lib/ucb_ldap/entry.rb', line 158

def net_ldap
  self.class.net_ldap
end

#setter_method?(method) ⇒ Boolean

Returns true if method is a “setter”, i.e., ends in “=”.

Returns:

  • (Boolean)


180
181
182
# File 'lib/ucb_ldap/entry.rb', line 180

def setter_method?(method)
  method.to_s[-1, 1] == "="
end

#update_attributes(attrs) ⇒ Object

Update an existing entry. Returns entry if successful else false.

attrs = {:attr1 => "new_v1", :attr2 => "new_v2"}
entry.update_attributes(attrs)


128
129
130
131
132
133
134
135
# File 'lib/ucb_ldap/entry.rb', line 128

def update_attributes(attrs)
  attrs.each { |k, v| self.send("#{k}=", v) }
  if modify
    @attributes = self.class.find_by_dn(dn).attributes.dup
    return true
  end
  false
end

#update_attributes!(attrs) ⇒ Object

Same as #update_attributes(), but raises DirectoryNotUpdated on failure.



140
141
142
# File 'lib/ucb_ldap/entry.rb', line 140

def update_attributes!(attrs)
  update_attributes(attrs) || raise(DirectoryNotUpdatedException)
end

#value_getter(method) ⇒ Object

Called by method_missing() to get an attribute value.



187
188
189
190
191
# File 'lib/ucb_ldap/entry.rb', line 187

def value_getter(method)
  schema_attribute = self.class.schema_attribute(method)
  raw_value = attributes[canonical(schema_attribute.name)]
  schema_attribute.get_value(raw_value)
end

#value_setter(method, *args) ⇒ Object

Called by method_missing() to set an attribute value.



196
197
198
199
200
# File 'lib/ucb_ldap/entry.rb', line 196

def value_setter(method, *args)
  schema_attribute = self.class.schema_attribute(method.to_s.chop)
  attr_key = canonical(schema_attribute.name)
  assigned_attributes[attr_key] = schema_attribute.ldap_value(args[0])
end