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.



84
85
86
87
88
89
90
91
92
# File 'lib/ucb_ldap_entry.rb', line 84

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.



154
155
156
157
158
# File 'lib/ucb_ldap_entry.rb', line 154

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.



331
332
333
# File 'lib/ucb_ldap_entry.rb', line 331

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


253
254
255
# File 'lib/ucb_ldap_entry.rb', line 253

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.



221
222
223
224
225
# File 'lib/ucb_ldap_entry.rb', line 221

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

.create!(args) ⇒ Object

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



237
238
239
# File 'lib/ucb_ldap_entry.rb', line 237

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

.entity_nameObject

Schema entity name. Set in each subclass.



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

def entity_name
  @entity_name
end

.find_by_dn(dn) ⇒ Object

Returns entry whose distinguised name is dn.



228
229
230
231
232
233
234
# File 'lib/ucb_ldap_entry.rb', line 228

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)


263
264
265
266
267
268
269
270
271
272
273
# File 'lib/ucb_ldap_entry.rb', line 263

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.



336
337
338
# File 'lib/ucb_ldap_entry.rb', line 336

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

.object_classesObject

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



276
277
278
# File 'lib/ucb_ldap_entry.rb', line 276

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

.schema_attribute(attribute_name) ⇒ Object



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

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.



285
286
287
288
# File 'lib/ucb_ldap_entry.rb', line 285

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.



292
293
294
295
# File 'lib/ucb_ldap_entry.rb', line 292

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


317
318
319
320
321
322
323
324
325
326
327
# File 'lib/ucb_ldap_entry.rb', line 317

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.



349
350
351
352
353
354
355
356
357
358
359
360
361
# File 'lib/ucb_ldap_entry.rb', line 349

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.



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

def tree_base
  @tree_base
end

.tree_base=(tree_base) ⇒ Object



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

def tree_base=(tree_base)
  @tree_base = tree_base
end

.unique_object_classObject



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

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

Instance Method Details

#assigned_attributesObject



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

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.



101
102
103
# File 'lib/ucb_ldap_entry.rb', line 101

def attributes
  @attributes
end

#canonical(string_or_symbol) ⇒ Object

:nodoc:



110
111
112
# File 'lib/ucb_ldap_entry.rb', line 110

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

#deleteObject

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



134
135
136
# File 'lib/ucb_ldap_entry.rb', line 134

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

#delete!Object

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



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

def delete!
  delete || raise(DirectoryNotUpdatedException)
end

#dnObject

Returns the value of the Distinguished Name attribute.



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

def dn
  attributes[canonical(:dn)]
end

#modifyObject



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

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

#modify_operationsObject



183
184
185
186
187
188
189
190
191
# File 'lib/ucb_ldap_entry.rb', line 183

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



143
144
145
# File 'lib/ucb_ldap_entry.rb', line 143

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)


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

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)


119
120
121
122
123
124
125
126
# File 'lib/ucb_ldap_entry.rb', line 119

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.



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

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

#value_getter(method) ⇒ Object

Called by method_missing() to get an attribute value.



166
167
168
169
170
# File 'lib/ucb_ldap_entry.rb', line 166

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.



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

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