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 underlying 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 an attribute value for an invalid attribute name will raise a BadAttributeNameException.

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.



70
71
72
73
74
75
76
77
78
# File 'lib/ucb_ldap/entry.rb', line 70

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

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



117
118
119
120
121
122
123
# File 'lib/ucb_ldap/entry.rb', line 117

def method_missing(method, *args) #:nodoc:
  schema_attribute = self.class.schema_attribute(method)
  raw_value = attributes[canonical(schema_attribute.name)]
  schema_attribute.get_value(raw_value)
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.



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

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


190
191
192
# File 'lib/ucb_ldap/entry.rb', line 190

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.



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

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.



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

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

.entity_nameObject

Schema entity name. Set in each subclass.



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

def entity_name
  @entity_name
end

.filter_in(attribute_name, array_of_values) ⇒ Object



134
135
136
137
# File 'lib/ucb_ldap/entry.rb', line 134

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.



163
164
165
166
167
168
169
# File 'lib/ucb_ldap/entry.rb', line 163

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)


201
202
203
204
205
206
207
208
209
210
211
# File 'lib/ucb_ldap/entry.rb', line 201

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.



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

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

.object_classesObject

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



215
216
217
# File 'lib/ucb_ldap/entry.rb', line 215

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



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

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>


242
243
244
245
246
247
248
# File 'lib/ucb_ldap/entry.rb', line 242

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



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

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.



253
254
255
256
# File 'lib/ucb_ldap/entry.rb', line 253

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.



262
263
264
265
# File 'lib/ucb_ldap/entry.rb', line 262

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


288
289
290
291
292
293
294
295
296
297
298
# File 'lib/ucb_ldap/entry.rb', line 288

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.



329
330
331
332
333
334
335
336
337
338
339
340
341
# File 'lib/ucb_ldap/entry.rb', line 329

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.



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

def tree_base
  @tree_base
end

.tree_base=(tree_base) ⇒ Object



353
354
355
# File 'lib/ucb_ldap/entry.rb', line 353

def tree_base=(tree_base)
  @tree_base = tree_base
end

.unique_object_classObject



219
220
221
# File 'lib/ucb_ldap/entry.rb', line 219

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

Instance Method Details

#assigned_attributesObject



125
126
127
# File 'lib/ucb_ldap/entry.rb', line 125

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.



89
90
91
# File 'lib/ucb_ldap/entry.rb', line 89

def attributes
  @attributes
end

#canonical(string_or_symbol) ⇒ Object

:nodoc:



100
101
102
# File 'lib/ucb_ldap/entry.rb', line 100

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

#dnObject

Returns the value of the Distinguished Name attribute.



96
97
98
# File 'lib/ucb_ldap/entry.rb', line 96

def dn
  attributes[canonical(:dn)]
end

#net_ldapObject



104
105
106
# File 'lib/ucb_ldap/entry.rb', line 104

def net_ldap
  self.class.net_ldap
end