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

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.



87
88
89
90
91
92
93
94
95
# File 'lib/ucb_ldap_entry.rb', line 87

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.



170
171
172
173
174
# File 'lib/ucb_ldap_entry.rb', line 170

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.



393
394
395
# File 'lib/ucb_ldap_entry.rb', line 393

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


279
280
281
# File 'lib/ucb_ldap_entry.rb', line 279

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.



243
244
245
246
247
248
# File 'lib/ucb_ldap_entry.rb', line 243

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.



262
263
264
# File 'lib/ucb_ldap_entry.rb', line 262

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

.entity_nameObject

Schema entity name. Set in each subclass.



409
410
411
# File 'lib/ucb_ldap_entry.rb', line 409

def entity_name
  @entity_name
end

.find_by_dn(dn) ⇒ Object

Returns entry whose distinguised name is dn.



252
253
254
255
256
257
258
# File 'lib/ucb_ldap_entry.rb', line 252

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)


290
291
292
293
294
295
296
297
298
299
300
# File 'lib/ucb_ldap_entry.rb', line 290

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.



400
401
402
# File 'lib/ucb_ldap_entry.rb', line 400

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

.object_classesObject

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



304
305
306
# File 'lib/ucb_ldap_entry.rb', line 304

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



315
316
317
# File 'lib/ucb_ldap_entry.rb', line 315

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>


331
332
333
334
335
336
337
# File 'lib/ucb_ldap_entry.rb', line 331

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



356
357
358
359
# File 'lib/ucb_ldap_entry.rb', line 356

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.



342
343
344
345
# File 'lib/ucb_ldap_entry.rb', line 342

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.



351
352
353
354
# File 'lib/ucb_ldap_entry.rb', line 351

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'}


377
378
379
380
381
382
383
384
385
386
387
# File 'lib/ucb_ldap_entry.rb', line 377

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.



417
418
419
420
421
422
423
424
425
426
427
428
429
# File 'lib/ucb_ldap_entry.rb', line 417

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.



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

def tree_base
  @tree_base
end

.tree_base=(tree_base) ⇒ Object



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

def tree_base=(tree_base)
  @tree_base = tree_base
end

.unique_object_classObject



308
309
310
# File 'lib/ucb_ldap_entry.rb', line 308

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

Instance Method Details

#assigned_attributesObject



201
202
203
# File 'lib/ucb_ldap_entry.rb', line 201

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.



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

def attributes
  @attributes
end

#canonical(string_or_symbol) ⇒ Object

:nodoc:



117
118
119
# File 'lib/ucb_ldap_entry.rb', line 117

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

#deleteObject

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



146
147
148
# File 'lib/ucb_ldap_entry.rb', line 146

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

#delete!Object

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



153
154
155
# File 'lib/ucb_ldap_entry.rb', line 153

def delete!
  delete || raise(DirectoryNotUpdatedException)
end

#dnObject

Returns the value of the Distinguished Name attribute.



113
114
115
# File 'lib/ucb_ldap_entry.rb', line 113

def dn
  attributes[canonical(:dn)]
end

#modifyObject



215
216
217
218
219
220
221
# File 'lib/ucb_ldap_entry.rb', line 215

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

#modify_operationsObject



205
206
207
208
209
210
211
212
213
# File 'lib/ucb_ldap_entry.rb', line 205

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



157
158
159
# File 'lib/ucb_ldap_entry.rb', line 157

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)


179
180
181
# File 'lib/ucb_ldap_entry.rb', line 179

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)


127
128
129
130
131
132
133
134
# File 'lib/ucb_ldap_entry.rb', line 127

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.



139
140
141
# File 'lib/ucb_ldap_entry.rb', line 139

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

#value_getter(method) ⇒ Object

Called by method_missing() to get an attribute value.



186
187
188
189
190
# File 'lib/ucb_ldap_entry.rb', line 186

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.



195
196
197
198
199
# File 'lib/ucb_ldap_entry.rb', line 195

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