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 they 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(dn = nil) ⇒ 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.



85
86
87
88
89
# File 'lib/ucb_ldap_entry.rb', line 85

def initialize(dn = nil) #:nodoc:
  @new_record = true
  @attributes = {}
  @tainted_attributes = {}
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.



172
173
174
175
176
# File 'lib/ucb_ldap_entry.rb', line 172

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.



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

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


273
274
275
# File 'lib/ucb_ldap_entry.rb', line 273

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.



235
236
237
238
239
240
241
# File 'lib/ucb_ldap_entry.rb', line 235

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

  # why is the object being refetched from ldap here?
  find_by_dn(args[:dn])
end

.create!(args) ⇒ Object

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



257
258
259
# File 'lib/ucb_ldap_entry.rb', line 257

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

.entity_nameObject

Schema entity name. Set in each subclass.



363
364
365
# File 'lib/ucb_ldap_entry.rb', line 363

def entity_name
  @entity_name
end

.find_by_dn(dn) ⇒ Object

returns entry whose distinguised name is dn.



248
249
250
251
252
253
254
# File 'lib/ucb_ldap_entry.rb', line 248

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

.hydrate(net_ldap_entry) ⇒ Object

Hydrates (populates) the object with values from the ldap resultset.



96
97
98
99
100
101
102
103
104
105
106
# File 'lib/ucb_ldap_entry.rb', line 96

def self.hydrate(net_ldap_entry)
  new_ldap_entry = self.new
  new_ldap_entry.instance_variable_set(:@new_record, false)
  # 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.
  net_ldap_entry.each do |attr, value|
    new_ldap_entry.attributes[canonical(attr)] = value.map{|v| v.dup}
  end
  new_ldap_entry
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)


283
284
285
286
287
288
289
290
291
292
293
# File 'lib/ucb_ldap_entry.rb', line 283

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.



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

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

.object_classesObject

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



296
297
298
# File 'lib/ucb_ldap_entry.rb', line 296

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

.required_attributesObject



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

def required_attributes
  schema_attributes_hash.delete_if {|key, value| value["required"] == false }.keys
end

.schema_attribute(attribute_name) ⇒ Object



317
318
319
320
# File 'lib/ucb_ldap_entry.rb', line 317

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.



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

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.



312
313
314
315
# File 'lib/ucb_ldap_entry.rb', line 312

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


337
338
339
340
341
342
343
344
345
346
347
# File 'lib/ucb_ldap_entry.rb', line 337

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



369
370
371
372
373
374
375
376
377
378
379
380
381
# File 'lib/ucb_ldap_entry.rb', line 369

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.



387
388
389
# File 'lib/ucb_ldap_entry.rb', line 387

def tree_base
  @tree_base
end

.tree_base=(tree_base) ⇒ Object



391
392
393
# File 'lib/ucb_ldap_entry.rb', line 391

def tree_base=(tree_base)
  @tree_base = tree_base
end

.unique_object_classObject



300
301
302
# File 'lib/ucb_ldap_entry.rb', line 300

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

Instance Method Details

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



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

def attributes
  @attributes
end

#canonical(string_or_symbol) ⇒ Object

:nodoc:



128
129
130
# File 'lib/ucb_ldap_entry.rb', line 128

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

#deleteObject

delete entry. returns true on sucess, false on failure.



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

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

#delete!Object

same as #delete() except raises directorynotupdated on failure.



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

def delete!
  delete || raise(directorynotupdatedexception)
end

#dnObject

returns the value of the distinguished name attribute.



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

def dn
  attributes[canonical(:dn)]
end

#modifyObject



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

def modify()
  if ucb::ldap.net_ldap.modify(:dn => dn, :operations => modify_operations)
    @tainted_attributes = nil
    return true
  end
  false
end

#modify_operationsObject



197
198
199
200
201
202
203
204
205
# File 'lib/ucb_ldap_entry.rb', line 197

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

#net_ldapObject



161
162
163
# File 'lib/ucb_ldap_entry.rb', line 161

def net_ldap
  self.class.net_ldap
end

#new_record?Boolean

Returns:

  • (Boolean)


91
92
93
# File 'lib/ucb_ldap_entry.rb', line 91

def new_record?
  @new_record
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

#tainted_attributesObject



108
109
110
# File 'lib/ucb_ldap_entry.rb', line 108

def tainted_attributes
  @tainted_attributes
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)


137
138
139
140
141
142
143
144
# File 'lib/ucb_ldap_entry.rb', line 137

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.



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

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

#value_getter(method) ⇒ Object

called by method_missing() to get an attribute value.



184
185
186
187
188
# File 'lib/ucb_ldap_entry.rb', line 184

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.



191
192
193
194
195
# File 'lib/ucb_ldap_entry.rb', line 191

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