Class: PassiveLDAP::Base

Inherits:
Object
  • Object
show all
Includes:
ActiveRecord::Aggregations, ActiveRecord::Associations, ActiveRecord::AttributeMethods, ActiveRecord::Callbacks, ActiveRecord::Locking::Optimistic, ActiveRecord::Observing, ActiveRecord::Reflection, ActiveRecord::Serialization, ActiveRecord::Timestamp, ActiveRecord::Validations
Defined in:
lib/passiveldap.rb

Overview

Base class. See the documentation of #passive_ldap and #passive_ldap_attr

Direct Known Subclasses

User

Constant Summary collapse

VERSION =
"0.1"
@@default_timezone =
:local

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(userid = nil) {|_self| ... } ⇒ Base

AR create a record object and populate it’s data from the LDAP directory. If the record is not found it will create an empty user with that id

Beware! If userid is nil it will try to guess a new id number using the Proc in #passive_ldap. By default this guess is not guaranteed to be unique in a multi-threaded application. See #passive_ldap

the parameter may be a Hash with attributes that are the initial values.

Yields:

  • (_self)

Yield Parameters:

Raises:



669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
# File 'lib/passiveldap.rb', line 669

def initialize(userid = nil)
  values = nil
  if userid.kind_of?(Hash)
    values = userid.clone
    values[:id] ||= nil
    userid = values[:id]
  end
  raise ARFeatureMissing, "Id must be a Hash or a number" unless userid.kind_of?(Integer) or (userid.kind_of?(String) and userid.to_i.to_s == userid) or userid.nil?
  userid = self.class.settings[:new_id].call(self) if userid.nil?
  @array_separator = self.class.settings[:default_array_separator]
  @protection_level = 0
  @protection_username = nil
  @protection_password = nil
  @generated_methods = Set.new
  @dn = nil
  self.class.attrs.each { |name,value|
    alt_name = value[:name]
    eval "@#{alt_name.to_s} = nil"
    if not self.class.method_defined?(alt_name) then      
      self.class.module_eval <<-EOF
        def #{alt_name.id2name}
          read_mapped_attribute(:#{alt_name.to_s})
        end
        def #{alt_name.id2name}=(a)
          write_mapped_attribute(:#{alt_name.to_s},a)
        end
        def #{alt_name.id2name}?
          if @attributes.has_key?(:#{name.to_s}) then
            unless @attributes[:#{name.to_s}].nil? or @attributes[:#{name.to_s}] == "" or @attributes[:#{name.to_s}] == [] then
              true
            else
              false
            end
          else
            false
          end
        end
      EOF
      @generated_methods << "#{alt_name.id2name}".to_sym
      @generated_methods << "#{alt_name.id2name}=".to_sym
      @generated_methods << "#{alt_name.id2name}?".to_sym
    end
  }
  reload(:id => userid)  
  @errors = ActiveRecord::Errors.new(self)
  unless values.nil?
    values[:id] = userid
    values.each { |key,value|
      write_mapped_attribute(key,value) unless key == :id
    }
    self.id = userid
  end
  yield self if block_given?
end

Class Method Details

.attr_mapfromObject

gets the attribute_passive_ldap_name=>attribute_ldap_server_name hash



174
175
176
# File 'lib/passiveldap.rb', line 174

def attr_mapfrom
  read_inheritable_attribute(:mapfrom)
end

.attr_maptoObject

gets the attribute_ldap_server_name=>attribute_passive_ldap_name hash



169
170
171
# File 'lib/passiveldap.rb', line 169

def attr_mapto
  read_inheritable_attribute(:mapto)
end

.attrsObject

gets the attributes hash set with #passive_ldap_attr (excluding hidden values)



159
160
161
# File 'lib/passiveldap.rb', line 159

def attrs
  read_inheritable_attribute(:attrs)
end

.attrs_allObject

gets the attributes hash set with #passive_ldap_attr (including hidden values)



164
165
166
# File 'lib/passiveldap.rb', line 164

def attrs_all
  read_inheritable_attribute(:attr_orig)
end

.bind(password = nil, username = nil) ⇒ Object

Binds to the directory with the username and password given. Password may be a Proc object, see the documentation of Net::LDAP#bind

Will return true if the bind is sucesful, and will raise a ConnectionError with the message returned from the server if the bind fails

If password and username is nil, bind will try to bind with the default connection parameters

Beware! Password is the first parameter!

Raises:



187
188
189
190
191
192
193
# File 'lib/passiveldap.rb', line 187

def bind(password = nil, username = nil)
  ldap = initialize_ldap_con
  ldap.authenticate(username,password) if password
  ldap.bind
  raise ConnectionError, ldap.get_operation_result.message unless ldap.get_operation_result.code == 0
  true
end

.column_namesObject

AR returns an array of the attribute names as strings (if mapped then it will return the mapped name)



221
222
223
224
225
226
227
228
229
# File 'lib/passiveldap.rb', line 221

def column_names
  unless @column_names
    @column_names = ["id"]
    attrs.each { |key,value|
      @column_names << value[:name].to_s if key != settings[:id_attribute]
    }
  end
  @column_names
end

.columnsObject

AR returns an array of the columns as ActiveRecord::ConnectionAdapters::Column

The id is ‘int(8)’ the multi-valued attributes are ‘text’, all others are ‘varchar’



234
235
236
237
238
239
240
241
242
243
244
245
246
247
# File 'lib/passiveldap.rb', line 234

def columns
  unless @columns
    @columns = self.column_names.collect { |e|
      if e == "id" then
        i = ActiveRecord::ConnectionAdapters::Column.new("id",'0','int(8)',false)
        i.primary = true
      else
        i = ActiveRecord::ConnectionAdapters::Column.new(e,'',attrs[attr_mapfrom[e.to_sym]][:multi_valued]?'text':'varchar',true)
      end
      i
    } 
  end
  @columns
end

.columns_hashObject

AR returns a hash of column objects. See columns



250
251
252
253
254
255
256
257
258
259
# File 'lib/passiveldap.rb', line 250

def columns_hash
  unless @columns_hash
    a = self.columns
    @columns_hash = {}
    a.each { |e|
      @columns_hash[e.name] = e
    }
  end
  @columns_hash
end

.content_columnsObject

AR return the array of column objects without the id column



262
263
264
265
266
# File 'lib/passiveldap.rb', line 262

def content_columns
  a = columns
  a.delete_if { |e| e.name == "id" }
  a
end

.count(*args) ⇒ Object

AR always returns the number of records. Should be changed to something more intelligent

Doesn’t raise ARFeatureMissing yet



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

def count(*args)
  find(:all).length
end

.create(attributes = nil) ⇒ Object

AR Creates an object (or multiple objects) and saves it to the database, if validations pass. The resulting object is returned whether the object was saved successfully to the database or not.

The attributes parameter can be either be a Hash or an Array of Hashes. These Hashes describe the attributes on the objects that are to be created.



273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'lib/passiveldap.rb', line 273

def create(attributes = nil)
  if attributes.nil? then
    a = new
    a.save
    a
  else
    attributes = [attributes] unless attributes.kind_of?(Array)
    c = []
    attributes.each { |b|
      b[:id] ||= nil
      a = new(b[:id])
      b.each { |key,value|
        if key!=:id then
          a[key] = value
        end
      }
      a.save
      c << a
    }
    if attributes.length==1 then 
      c[0]
    else
      c
    end
  end
end

.define_attr_method(name, value = nil, &block) ⇒ Object

AR Defines an “attribute” method (like #inheritance_column or #table_name). A new (class) method will be created with the given name. If a value is specified, the new method will return that value (as a string). Otherwise, the given block will be used to compute the value of the method.

The original method will be aliased, with the new name being prefixed with “original_”. This allows the new method to access the original value.

Example:

class A < ActiveRecord::Base
  define_attr_method :primary_key, "sysid"
  define_attr_method( :inheritance_column ) do
    original_inheritance_column + "_id"
  end
end


1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
# File 'lib/passiveldap.rb', line 1155

def define_attr_method(name, value=nil, &block)
  sing = class << self; self; end
  sing.send :alias_method, "original_#{name}", name
  if block_given?
    sing.send :define_method, name, &block
  else
    # use eval instead of a block to work around a memory leak in dev
    # mode in fcgi
    sing.class_eval "def #{name}; #{value.to_s.inspect}; end"
  end
end

.delete(id) ⇒ Object

AR deletes the record. Object will be instantiated



301
302
303
304
# File 'lib/passiveldap.rb', line 301

def delete(id)
  a = new(id)
  a.destroy
end

.delete_all(conditions = nil) ⇒ Object

AR not implemented. Raises ARMethodMissing

Raises:



307
308
309
# File 'lib/passiveldap.rb', line 307

def delete_all(conditions = nil)
  raise ARMethodMissing, "ARMethodMissing: delete_all"
end

.destroy(id) ⇒ Object

AR same as delete



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

def destroy(id)
  delete(id)
end

.destroy_all(conditions = nil) ⇒ Object

AR not implemented. Raises ARMethodMissing

Raises:



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

def destroy_all(conditions = nil)
  raise ARMethodMissing, "ARMethodMissing: destroy_all"
end

.exists?(id_or_filter) ⇒ Boolean

AR checks whether the given id, or an object that satisfies the given Net::LDAP::Filter exist in the directory

will throw ARFeatureMissing if id_or_filter is not an integer or a Filter

Returns:

  • (Boolean)

Raises:



324
325
326
327
328
329
330
331
332
333
334
335
336
# File 'lib/passiveldap.rb', line 324

def exists?(id_or_filter)
  raise ARFeatureMissing, "id_or_filter must be an id or a filter" unless id_or_filter.kind_of?(Integer) or (id_or_filter.kind_of?(String) and id_or_filter.to_i.to_s == id_or_filter) or id_or_filter.kind_of?(Net::LDAP::Filter)
  begin
    if id_or_filter.kind_of?(Net::LDAP::Filter) then
      find(:first,id_or_filter)
    else
      find(id_or_filter)
    end
  rescue RecordNotFound
    return false
  end
  true
end

.find(user, filter = nil) ⇒ Object

AR find a user defined by it’s ID and return the object. If it is not found in the database it will raise RecordNotFound

If you pass the :all symbol as parameter, it will return an array with all objects in the directory. If no object is found it will return an empty array

If you pass the :first symbol as parameter, it will return the first object in the directory

the optional filter parameter is used to join a new filter to the default one. The filter parameter is only used in :all and :first searches

will throw ARFeatureMissing if passed a Hash or an Array instead of a Net::LDAP::Filter, or if the first parameter is not an id, or one the following symbols: :all, :first

Currently it will allow Hash filters, if all of the Hash parameters are nil. This is because doing so belongs_to relations will work.

Raises:



354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
# File 'lib/passiveldap.rb', line 354

def find(user, filter = nil)
  raise ARFeatureMissing, "User must be a number, :all or :first. Supplied was #{filter.inspect}" unless user.kind_of?(Integer) or user == :all or user == :first or (user.kind_of?(String) and user.to_i.to_s == user)
  if filter.kind_of?(Hash) then
    testf = true
    filter.each { |key,value|
      testf = false unless value.nil?
    }
    filter = nil if testf
  end
  raise ARFeatureMissing, "Filter must be a Net::LDAP::Filter or nil. Supplied was #{filter.inspect}" unless filter.nil? or filter.kind_of?(Net::LDAP::Filter)
  #filter = nil unless filter.kind_of?(Net::LDAP::Filter)
  if user == :all or user == :first then
    a = []     
    ldap = self.initialize_ldap_con    
    if filter then
      filter = filter & self.settings[:multiple_record_filter].call(self)
    else
      filter = self.settings[:multiple_record_filter].call(self)
    end
    alreadygot = false
    ldap.search( :return_result => false, :scope => self.settings[:record_scope], :base => self.settings[:record_base], :filter => filter ) do |entry|
      eval "a << self.new(entry.#{self.settings[:id_attribute].id2name}[0].to_i)" unless user == :first and alreadygot
      alreadygot = true
    end
    raise ConnectionError, ldap.get_operation_result.message unless ldap.get_operation_result.code == 0
    if user == :all then
      a
    elsif a == [] then
      raise PassiveLDAP::RecordNotFound
    else
      a[0]
    end
  else
    a = self.new(user)
    if a.exists_in_directory then
      a
    else
      raise PassiveLDAP::RecordNotFound
    end
  end
end

.generated_methodsObject

AR Returns an array of the generated methods



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

def generated_methods
  @generated_methods ||= Set.new
end

.generated_methods?Boolean

AR Returns true - attribute methods are generated in initalize

Returns:

  • (Boolean)


207
208
209
# File 'lib/passiveldap.rb', line 207

def generated_methods?
  true
end

.human_attribute_name(attribute_key_name) ⇒ Object

AR returns a humanized attribute name



397
398
399
# File 'lib/passiveldap.rb', line 397

def human_attribute_name(attribute_key_name)
  attribute_key_name.humanize
end

.initialize_ldap_conObject

creates a new Net::LDAP object



1102
1103
1104
# File 'lib/passiveldap.rb', line 1102

def initialize_ldap_con
  Net::LDAP.new( self.settings[:connection] ) 
end

.inspectObject

AR returns a string like “User id:integer name:string mail:text” multi-valued attributes will be text



402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
# File 'lib/passiveldap.rb', line 402

def inspect()
  a = column_names
  b = self.name
  a.each { |e|
    if e == "id" then
      b = b + " id:integer"
    else
      if attrs[attr_mapfrom[e.to_sym]][:multi_valued] then
        b = b + " #{e}:text"
      else
        b = b + " #{e}:string"
      end
    end
  }
  b
end

.passive_ldap(connection_attributes) ⇒ Object

sets the connection and record attributes that are used. The parameter is a hash with the following options. If there are parameters missing, then the default values will be used instead of them.

  • :connection: The :connection is a hash that will be passed without modification to Net::LDAP. The default value is to connect to localhost on port 389 as anonymous.

  • :id_attribute: The :id_attribute is a symbol, that tells PassiveLDAP which attribute is used as the id of a record. This attribute must be an integer attribute and it must be unique. (Although there are no constraint checkings yet)

  • :multiple_record_filter: The :multiple_record_filter is a Proc object with one argument, that should return a Net::LDAP::Filter object that will return all the appropriate records in the directory. The default value is a filter that filters out the object based whether their attribute that is sat in :id_attribute is set. The first argument of the block will be set to the caller PassiveLDAP object.

  • :single_record_filter: The :single_record_filter is a Proc object with two arguments: the caller PassiveLDAP object and an id number. The corresponding block should return a filter that will filter out the record which has the appropriate id. The default value of this argument is to check whether the attribute set with :id_attribute is equal to the specified id number.

  • :record_base: The :record_base is a String that is set to the base of the records. The default value is “ou=users,dc=com”

  • :record_scope: The :record_scope is a Net::LDAP::Scope object that sets the scope of the records according to the :record_base. The default value is Net::LDAP::SearchScope_SingleLevel

  • :new_id: The :new_id is a Proc object that will return an integer which should be an id that is not present in the directory. The default value is 10000 + count*5 + rand(5) which is not really safe

  • :default_array_separator: sets the string that will separate the multi-valued attributes if they are converted to string. Set to nil if you don’t want this conversion. This separator may be set with array_separator in an instance too. If this attribute is not nil every attribute setter/getter excluding get_attribute and set_attribute will use a converted string to set/get these attributes. If the separator is n then trailing r characters will be chomped from the splitted strings.

  • :default_protection_level: sets the default level. All attributes added after this is set wil have this default level number, unless they explicit specify something else. Default is 0

example (as well as the default values):

passive_ldap :connection => {:host => "127.0.0.1", :port => "389", :auth => { :method => :anonymous } },
             :id_attribute => :id,
             :multiple_record_filter => Proc.new { |s| Net::LDAP::Filter.eq(s.settings[:id_attribute].id2name,"*") },
             :single_record_filter => Proc.new { |s,id| Net::LDAP::Filter.eq(s.settings[:id_attribute].id2name,id) },
             :record_base => "ou=users,dc=com",
             :record_scope => Net::LDAP::SearchScope_SingleLevel,
             :new_id => Proc.new { |s| 10000 + s.class.count*5 + rand(5) },
             :default_array_separator => nil,
             :default_protection_level => 0


1015
1016
1017
# File 'lib/passiveldap.rb', line 1015

def passive_ldap(connection_attributes)
  write_inheritable_hash(:connection, connection_attributes)
end

.passive_ldap_attr(attribs) ⇒ Object

Sets the attributes you would like to use. Only the attributes set here, the attribute of the id and the dn attribute will be queried from the directory. The id_attribute and dn attributes are used automatically so they must not be set here (unless you define the dn attribute hidden with a default_value). The id attribute is always mapped to the name :id regardless of it’s original name.

All attributes will get a getter and a setter method with their respective name (unless a mapping is defined in attribute_map), as well as a query method, that queries whether the attribute is set or not. They also get an instance variable with their mapped name (although it is only used to write to. Some AR specific methods may read the attributes data from instance variables. PassiveLDAP stores the attributes in the @attributes Hash)

By default there are no attributes defined. Multiple calls of this method will result in the union of the attributes

The attributes are set as a Hash, where the key is the name of the attribute and the value is a Hash with the following options:

  • :type: defines a Hash with a :from, a :to and a :klass attribute, from wchich the :klass attribute must be “String”. Internally all data’s are stored as Strings (or array-of-strings if multi-valued). :from describes a Proc that will convert the internally represented String to the class defined in :klass (which is currently a String), and :to will define the inverse of this conversion. The whole :type attribute may be nil, which means there are no conversions, and the attribute is a String (or an Array of Strings). The default value is that the :from and :to attributes are Proc objects that will return their parameter back. The :klass is always String, and can not be changed. This type conversion will be done with all attribute changing methods, except #get_attribute, #set_attribute. Besides the value of the :default_value parameter won’t be converted either. Array_separator conversions are done before using this conversion. Some types are defined as constants in PassiveLDAP::Types

  • :multi_valued: tells whether the attribute can be multi_valued or not. multi_valued attributes will be arrays of string

  • :level: sets the protection level that is needed to update this attribute. Check set_protection_level for details. Default is 0

  • :name: sets the name/mapping of the attribute. By default it is the same as the attribute’s name. When accessing the attribute (using methods, [], get_variable, etc.) you have to reference it by it’s new name. Internally the attributes will be stored with their original attribute name.

  • :default_value: the default value of the attribute, if the value of the attribute is empty when saving. Must be a String/Array or a Proc object, that will return a String or an Array. The parameter of the proc object will be the PassiveLDAP object itself. If nil there is no default value. Default is nil

  • :hidden: if true, the object will be loaded from the directory, but it’s not accessable using methods, [], and such, and will be hidden from the columns too. The @attributes instance variable will still hold it’s value, and it will be saved back to the directory when changed. Useful for attributes like objectclass. Default is false.

  • :always_update: if true, and there is a default value given, before save the attribute will always get it’s default value regardles of it’s original value. Useful for timestamp or aggregate type attributes. Default is false.

  • :read_only: sets the attribute to be read only. If a default value is given saving will update this attribute too if it is empty. This is useful if the attribute needs a default value at creation but should be read-only otherwise. Default is false.

TODO: more types

TODO: name conflict checking for the mapped names

Attributes must be lowercase symbols, because Net::LDAP treats them that way!

example:

passive_ldap_attr :name => {}, :sn => {}, :cn => {}
passive_ldap_attr :name => {:level => 1}, :sn => {:level => 1}, :cn => {:level => 1}
passive_ldap_attr :mail => {:multi_valued => true, :level => 1}, :mobile => {:multi_valued => true, :level => 1}
passive_ldap_attr :roomnumber => {:level => 2}


1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
# File 'lib/passiveldap.rb', line 1067

def passive_ldap_attr(attribs)
  mapto = {}
  mapfrom = {}
  nohidden = {}
  attribs.each { |key, value|
    value[:multi_valued] ||= false
    value[:level] ||= self.settings[:default_protection_level]
    value[:type] ||= nil
    if (value[:type]) then
      value[:type][:from] ||= Proc.new { |s| s }
      value[:type][:to] ||= Proc.new { |s| s }
      value[:type][:klass] = String
    end
    value[:name] ||= key
    value[:default_value] ||= nil
    value[:hidden] ||= false
    value[:always_update] ||= false
    value[:read_only] ||= false
    value[:read_only] = value[:read_only] or value[:hidden]
    raise DistinguishedNameException, "DN attribute can't have the always_update flag set" if key == :dn and value[:always_update]
    raise DistinguishedNameException, "DN attribute must be hidden" if key == :dn and !value[:hidden]
    raise DistinguishedNameException, "DN attribute must have a default_value" if key == :dn and value[:default_value].nil?
    unless value[:hidden]
      mapto[key] = value[:name]
      mapfrom[value[:name]] = key
      nohidden[key] = value
    end
  }
  write_inheritable_hash(:attr_orig, attribs)
  write_inheritable_hash(:attrs, nohidden)
  write_inheritable_hash(:mapto, mapto)
  write_inheritable_hash(:mapfrom, mapfrom)
end

.primary_keyObject

AR returns :id



420
421
422
# File 'lib/passiveldap.rb', line 420

def primary_key
  :id
end

.serialize(attr_name, class_name = Object) ⇒ Object

AR not implemented. Will raise ARMethodMissing

Raises:



425
426
427
# File 'lib/passiveldap.rb', line 425

def serialize(attr_name, class_name = Object)
  raise ARMethodMissing, "ARMethodMissing: serialize"
end

.serialized_attributesObject

AR not implemented. Will raise ARMethodMissing

Raises:



430
431
432
# File 'lib/passiveldap.rb', line 430

def serialized_attributes
  raise ARMethodMissing, "ARMethodMissing: serialized_attributes"
end

.settingsObject

gets the hash set with #passive_ldap



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

def settings
  read_inheritable_attribute(:connection)
end

.table_nameObject

AR will return the name of the class



435
436
437
# File 'lib/passiveldap.rb', line 435

def table_name
  self.name
end

.update(id, attributes) ⇒ Object

AR Updates an object or objects (if passed an Array) with the attributes given. Uses save!



440
441
442
443
444
445
446
447
448
449
450
451
452
453
# File 'lib/passiveldap.rb', line 440

def update(id, attributes)
  id = [id] unless id.kind_of?(Array)
  attributes = [attributes] unless attributes.kind_of?(Array)
  if id.length != attributes.length then
    raise PassiveLDAPError, "Argument numbers don't mach"
  end
  c = []
  id.each_index { |v|
    a = new(id[v])
    a.update_attributes(attributes[v])
    c << a
  }
  id.length==1 ? c[0] : c
end

.update_all(updates, conditions = nil, options = {}) ⇒ Object

AR not implemented. Will raise ARMethodMissing

Raises:



456
457
458
# File 'lib/passiveldap.rb', line 456

def update_all(updates, conditions = nil, options = {})
  raise ARMethodMissing, "ARMethodMissing: update_all"
end

.update_counters(id, counters) ⇒ Object

AR not implemented. Will raise ARMethodMissing

Raises:



461
462
463
# File 'lib/passiveldap.rb', line 461

def update_counters(id,counters)
  raise ARMethodMissing, "ARMethodMissing: update_counters"
end

.validates_format_of_each(*attr_names) ⇒ Object

validates the format of each value in a multi-valued attribute. See ActiveRecord::Validations#validates_format_of. Only use this with multi-valued attributes!

Raises:

  • (ArgumentError)


1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
# File 'lib/passiveldap.rb', line 1108

def validates_format_of_each(*attr_names)
  configuration = { :message => ActiveRecord::Errors.default_error_messages[:invalid], :on => :save, :with => nil }
  configuration.update(attr_names.extract_options!)

  raise(ArgumentError, "A regular expression must be supplied as the :with option of the configuration hash") unless configuration[:with].is_a?(Regexp)
  validates_each(attr_names, configuration) do |record, attr_name, value|
    if value.nil? then
      record.errors.add(attr_name, configuration[:message]) 
    else
      if settings[:default_array_separator].nil? then
        value.each { |val|
          record.errors.add(attr_name, configuration[:message]) unless val.to_s =~ configuration[:with]
        }
      else
        value.split(settings[:default_array_separator]).each { |val|
          val.chomp!("\r") if settings[:default_array_separator] == "\n"
          record.errors.add(attr_name, configuration[:message]) unless val.to_s =~ configuration[:with]
        }
      end
    end
  end
end

Instance Method Details

#[](attribute) ⇒ Object

AR gets the value of the attribute. If the attribute has an alternate name then you have to use it here



725
726
727
# File 'lib/passiveldap.rb', line 725

def [](attribute)
  read_mapped_attribute(attribute)
end

#[]=(attribute, value) ⇒ Object

AR sets the value of the attribute. If the attribute has an alternate name then you have to use it here



730
731
732
# File 'lib/passiveldap.rb', line 730

def []=(attribute,value)
  write_mapped_attribute(attribute,value)
end

#array_separator(new_sep = nil) ⇒ Object

sets the array_separator



653
654
655
# File 'lib/passiveldap.rb', line 653

def array_separator(new_sep = nil)
  @array_separator = new_sep
end

#attribute_namesObject

AR Returns an array of symbols of the attributes that can be changed; sorted alphabetically



735
736
737
738
# File 'lib/passiveldap.rb', line 735

def attribute_names()
  a = self.class.column_names
  a.collect { |e| e.to_sym }.sort
end

#attribute_present?(attribute) ⇒ Boolean

AR Returns true if the specified attribute has been set by the user or by a database load and is neither nil nor empty?

It will always be true for the :id and :dn attribute (even if the :dn is not set)

Returns:

  • (Boolean)


744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
# File 'lib/passiveldap.rb', line 744

def attribute_present?(attribute)
  attribute = attribute.to_sym unless attribute.kind_of?(Symbol)
  return true if attribute == :id or attribute == :dn
  return false unless attribute_names.include?(attribute)
  a = self.class.attr_mapfrom[attribute]
  if @attributes[a].nil? then
    false
  elsif @attributes[a].kind_of?(Array) then
    if @attributes[a] == [] then
      false
    else
      true
    end
  elsif @attributes[a].kind_of?(String) then
    if @attributes[a] == "" then
      false
    else
      true
    end
  else
    true
  end
end

#attributes(options = nil) ⇒ Object

AR Returns a hash of all the attributes with their names as keys and clones of their objects as values.

Options will be ignored (is it used in AR anyway?)



771
772
773
774
775
776
777
778
779
780
781
# File 'lib/passiveldap.rb', line 771

def attributes(options = nil)
  a = { :id => id }
  @attributes.each { |key,value|
    v = value
    v = value.clone if value.duplicable?
    if self.class.attrs.has_key?(key) then
      a[self.class.attrs[key][:name]] = v
    end
  }
  a
end

#attributes=(new_attributes, guard_protected_attribute = true) ⇒ Object

AR sets multiple attributes at once. if guard_protected_attributes if true only level settings[:default_protection_level] attributes will be changed. guard_protected_attributes may be set to an Integer, indicating which is the maximum level of the attributes that need to be changed, or to false indicating that all attributes need to be changed



786
787
788
789
790
791
792
793
794
795
796
797
798
# File 'lib/passiveldap.rb', line 786

def attributes=(new_attributes, guard_protected_attribute = true)
  guard_protected_attribute = self.class.settings[:default_protection_level] if guard_protected_attribute == true
  new_attributes.each { |key,value|
    k = key
    k = key.to_sym unless key.kind_of?(Symbol)
    if self.class.attr_mapfrom.has_key?(k) then
      level = self.class.attrs[self.class.attr_mapfrom[k]][:level]
      if !guard_protected_attribute or (guard_protected_attribute.kind_of?(Integer) and guard_protected_attribute >= level) then
        self[k] = value 
      end
    end
  }
end

#bind(password = nil, username = nil) ⇒ Object

Bind to the directory to check whether the credentials are right or not. If there are no parameters specified bind will do the following:

  • If the actual protection_level is 0 it will bind with the default connection

  • If the level is 1 it will bind with the dn of the record and the password, that is set with #set_protection_level

  • If the level is above 2 it will bind with the dn and password set with #set_protection_level

Parameters may be used to set the dn and the password used to bind to the directory. Beware! The first parameter is the password! You may omit the username, in which case the dn of the record will be used to bind to the directory

bind will return true if the connection is succesful and will raise a ConnectionError with a message from the server if the authentication fails



485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
# File 'lib/passiveldap.rb', line 485

def bind(password = nil, username = nil)
  if password then
    ldap = self.class.initialize_ldap_con
    if username then
      ldap.authenticate(username,password)
    else
      ldap.authenticate(dn,password)
    end
    ldap.bind
    raise ConnectionError, ldap.get_operation_result.message unless ldap.get_operation_result.code == 0
  else
    ldap = initialize_ldap_con
    ldap.bind
    raise ConnectionError, ldap.get_operation_result.message unless ldap.get_operation_result.code == 0
  end
  true
end

#cloneObject

AR not implemented. Raises ARMethodMissing

Raises:



801
802
803
# File 'lib/passiveldap.rb', line 801

def clone
  raise ARMethodMissing, "ARMethodMissing: clone"
end

#column_for_attribute(name) ⇒ Object

AR returns the column object of the named attribute



806
807
808
# File 'lib/passiveldap.rb', line 806

def column_for_attribute(name)
  self.class.columns_hash[name.to_s]
end

#destroyObject

AR deletes the record in the directory and freezes the object

Raises:



811
812
813
814
815
816
# File 'lib/passiveldap.rb', line 811

def destroy
  ldap = initialize_ldap_con
  ldap.delete(:dn => dn)
  raise ConnectionError, ldap.get_operation_result.message unless ldap.get_operation_result.code == 0
  freeze
end

#dnObject

gets the distinguished name of the record. Returns nil if the record is nonexistent in the directory



565
566
567
# File 'lib/passiveldap.rb', line 565

def dn
  @attributes[:dn]
end

#dn=(newdn) ⇒ Object

sets the distinguished name of the record. The dn can only be set when the record is not originated from the directory (so it is a new record) Otherwise a DistinguishedNameException is raised



571
572
573
574
575
# File 'lib/passiveldap.rb', line 571

def dn=(newdn)
  raise PassiveLDAP::DistinguishedNameException, "DN cannot be changed" unless @oldattr[:dn].nil?
  @dn = newdn
  @attributes[:dn]=newdn
end

#exists_in_directoryObject

returns whether the record is new, or it is originated from the directory

if it exists it will return the dn of the record, if not it will return nil



580
581
582
# File 'lib/passiveldap.rb', line 580

def exists_in_directory
  @oldattr[:dn]
end

#get_attribute(attribute) ⇒ Object

returns the attrbiute. If it is multi_valued no conversion will be done even if the array_separator is something else than nil



605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
# File 'lib/passiveldap.rb', line 605

def get_attribute(attribute)
  attribute = attribute.to_sym unless attribute.kind_of?(Symbol)    
  if self.class.attr_mapfrom.has_key?(attribute) then
    key = self.class.attr_mapfrom[attribute]
    if @attributes.has_key?(key) then
      @attributes[key]
    else
      nil
    end
  else
    if attribute == :id then
      self.id
    else
      raise PassiveLDAP::AttributeAssignmentError, "Attribute #{attribute} does not exist"
    end
  end
end

#get_old_attribute(attribute) ⇒ Object

gets the original value (the value that was read from the directory, or nil if this is a new record) of an attribute



585
586
587
588
589
590
591
592
593
594
595
596
# File 'lib/passiveldap.rb', line 585

def get_old_attribute(attribute)
  attribute = attribute.to_sym unless attribute.kind_of?(Symbol)
  if self.class.attr_mapfrom.has_key?(attribute) then
    @oldattr[self.class.attr_mapfrom[attribute]]
  else
    if attribute == :id then
      @oldattr[self.settings[:id_attribute]]
    else
      raise PassiveLDAP::AttributeAssignmentError, "Attribute #{attribute} does not exist"
    end
  end    
end

#idObject

AR gets the id of the record



819
820
821
# File 'lib/passiveldap.rb', line 819

def id
  @attributes[self.class.settings[:id_attribute]]
end

#id=(a) ⇒ Object

AR sets the id of the record



824
825
826
827
828
# File 'lib/passiveldap.rb', line 824

def id=(a)
  raise PassiveLDAP::AttributeAssignmentError, "Id must be an integer" unless a.kind_of?(Integer) or (a.kind_of?(String) and a.to_i.to_s == a)
  @attributes[self.class.settings[:id_attribute]] = a
  @id = a
end

#id?Boolean

gets whether the id is set. Returns always true

Returns:

  • (Boolean)


560
561
562
# File 'lib/passiveldap.rb', line 560

def id?
  true
end

#inspectObject

AR Returns the contents of the record as a string

should be nicer



833
834
835
# File 'lib/passiveldap.rb', line 833

def inspect
  "#{self.class.name}: #{attributes.inspect}"
end

#new_record?Boolean

AR Returns true if this object hasn’t been saved yet - that is, a record for the object doesn’t exist in the directory yet.

Returns:

  • (Boolean)


838
839
840
841
842
843
844
# File 'lib/passiveldap.rb', line 838

def new_record?
  if exists_in_directory then
    false
  else
    true
  end
end

#reload(options = nil) ⇒ Object

AR reloads the data from the directory. If the record does not exists it will erase all attributes and set id to the old value. If the record was acquired from the directory and the id was changed the old id will be used to load the data, but the id will be set to the new one after the data has benn loaded. This may be changed with the :newid option

options may be

  • :id: set the id to this new value. If set the :newid attribute won’t be checked

  • :oldattr: set to true if you want to load the attributes only into the @oldattr variable, but not into the @attributes

  • :newid: set to true if you want to load the new id’s data (if you changed the id of the data before reloading)

Raises:



854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
# File 'lib/passiveldap.rb', line 854

def reload(options = nil)
  options = {} if options.nil?
  id_set = true
  options[:newid] ||= false
  options[:oldattr] ||= false    
  unless options.has_key?(:id) then
    id_set = false
    new_id = id
    options[:id] ||= id
    options[:id] = @oldattr[self.class.settings[:id_attribute]] unless options[:newid]
  end
  @oldattr = {}
  ldap = self.class.initialize_ldap_con
  entry = ldap.search( :base => self.class.settings[:record_base], :scope => self.class.settings[:record_scope], :filter => self.class.settings[:single_record_filter].call(self.class,options[:id].to_s) )
  raise ConnectionError, ldap.get_operation_result.message unless ldap.get_operation_result.code == 0
  if entry and entry != [] then
    @oldattr[:dn] = entry[0].dn.downcase
    entry[0].each { |name, values|
      if self.class.attrs_all.has_key?(name) then
        if self.class.attrs_all[name][:multi_valued] then
          @oldattr[name] = values
        else
          @oldattr[name] = values[0]
        end
      end
    }
  else
    @oldattr[:dn] = nil
  end
  @oldattr[self.class.settings[:id_attribute]] = options[:id]
  unless options[:oldattr] then
    @attributes = @oldattr.clone
    @dn = @attributes[:dn]
    @attributes.each { |key,value|
      if self.class.attrs.has_key?(key) then
        alt_name = self.class.attrs[key][:name]
        eval "@#{alt_name.to_s} = value"
      end
    }
    @id = options[:id]
    if !id_set and !options[:newid] then
      @attributes[self.class.settings[:id_attribute]] = new_id
      @id = new_id
    end
  end
end

#respond_to_without_attributes?(method, include_priv = false) ⇒ Boolean

AR needed by ActiveRecord::Callbacks

Returns:

  • (Boolean)


902
903
904
905
906
907
908
# File 'lib/passiveldap.rb', line 902

def respond_to_without_attributes?(method, include_priv=false)
  method_name = method.to_s
  method_name.chomp!("?")
  method_name.chomp!("!")
  return false if self.class.attr_mapfrom.has_key?(method_name.to_sym)
  respond_to?(method, include_priv)
end

#saveObject

AR Saves the changes back to the LDAP server. Only the changes will be saved, and only those attributes will be saved whose protection level is less or equal than the actual protection level.

Attributes with default values will get their new values calculated

The modifications will be sent to server as one modification chunk, but it depends on the LDAP server whether it will modify the directory as an atomic transaction. If an error occurs you should check whether the directory remained in a consistent state. See Net::LDAP#modify for more information

Before saving the attributes are loaded from the server to check what has changed. Between the loading and the saving other threads may modify the directory so be aware of this.

TODO: some kind of locking system

Returns false if an error occurs.



930
931
932
933
934
935
936
937
938
# File 'lib/passiveldap.rb', line 930

def save
  save!
rescue RecordNotSaved => e
  return false
rescue ActiveRecord::RecordInvalid
  return false
else
  return true
end

#save!Object

AR saves the record but will raise a RecordNotSaved with the cause of the failure if unsuccesful. See save



941
942
943
944
945
946
# File 'lib/passiveldap.rb', line 941

def save!
  create_or_update
rescue RecordNotSaved => e
  @errors.add_to_base(e)
  raise
end

#set_attribute(attribute, value, raise_error_when_readonly = false) ⇒ Object

sets the attribute. If it is multi_valued you need to pass an array even if the array_separator is set



625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
# File 'lib/passiveldap.rb', line 625

def set_attribute(attribute,value, raise_error_when_readonly = false)
  attribute = attribute.to_sym unless attribute.kind_of?(Symbol)
  if self.class.attr_mapfrom.has_key?(attribute) then
    alt_name = self.class.attr_mapfrom[attribute]
    if self.class.attrs[alt_name][:read_only]
      if raise_error_when_readonly then
        raise PassiveLDAP::AttributeAssignmentError, "Attribute #{attribute} is read-only" 
      else
        return false
      end
    end
    if self.class.attrs[alt_name][:multi_valued] then
      raise PassiveLDAP::AttributeAssignmentError, "Array expected, because #{attribute} is multi-valued" unless value.kind_of?(Array)
    else
      raise PassiveLDAP::AttributeAssignmentError, "Didn't expect an Array, because #{attribute} is not multi-valued" if value.kind_of?(Array)
    end
    eval "@#{attribute.to_s} = value"
    @attributes[alt_name] = value
  else
    if attribute == :id then
      self.id=value
    else
      raise PassiveLDAP::AttributeAssignmentError, "Attribute #{attribute} does not exist"
    end
  end
end

#set_password(newpass, method, options = nil) ⇒ Object

changes the password of the record.

Currently method may only be :active_directory

For options check #set_password_ad

will return false if unsuccesful, adding the response from the server to the errors list



510
511
512
513
514
515
516
# File 'lib/passiveldap.rb', line 510

def set_password(newpass, method, options = nil)
  set_password!
rescue RecordNotSaved
  return false
else
  return true
end

#set_password!(newpass, method, options = nil) ⇒ Object

same as set_password but will raise a RecordNotSaved exception in unsuccesful



519
520
521
522
523
524
525
526
527
528
# File 'lib/passiveldap.rb', line 519

def set_password!(newpass, method, options = nil)
  if method == :active_directory then
    set_password_ad(newpass, options)
  else
    raise ARFeatureMissing, "Only AD password changes supported!"
  end
rescue Exception => e
  @errors.add_to_base(e)
  raise
end

#set_protection_level(level = 0, password = nil, username = nil) ⇒ Object

Attributes may have different protection levels. Protection level means, that some attributes may only be changed by privileged users. Level 0 means that the attribute may be changed by the main connection. Level 1 means, the attribute can be changed by the owner of the attribute, but cannot be changed by the main connection. Level 2 and higher level means that the attribute can only be changed with a user, who has enough privileges.

For example if PassiveLDAP is used for storing User information, you might set most of the attributes to level 1 (so the password of the user will be needed to change those information) and some attributes (such as printAccount, or like) may be set to level 2 or higher, so only privileged users (like administrators) could change those attributes.

the method has 3 paramteres. The first one sets the desired level, the second one is the password of the user (if the level is greater or equal than 1) and the third one is the username (full dn!) of the user (if the level is above 1)

Protection means that when issuing a save method, only those attributes will be saved, that are below or equal to the protection level set here, the other ones won’t be sent to the LDAP server. Of course you should set the appropriate rights in the server too for maximum security.

Class methods (like find) will be run with the connection’s authenticity information while instance methods will run with the actual username and password set with set_protection_level

Beware! the second parameter is the password and the third is the username!



553
554
555
556
557
# File 'lib/passiveldap.rb', line 553

def set_protection_level(level = 0, password = nil, username = nil)
  @protection_level = level
  @protection_username = username
  @protection_password = password
end

#to_sObject

returns the user id as string



599
600
601
# File 'lib/passiveldap.rb', line 599

def to_s
  @id.to_s
end

#update_attribute(name, value) ⇒ Object

AR updates a single attribute and saves the record. See ActiveRecord::Base#update_attribute



949
950
951
952
# File 'lib/passiveldap.rb', line 949

def update_attribute(name, value)
  self[name] = value
  save
end

#update_attributes(attributes) ⇒ Object

AR updates multiple attributes and saves the record. See update_attribute.



955
956
957
958
959
960
961
# File 'lib/passiveldap.rb', line 955

def update_attributes(attributes)
  update_attributes!(attributes)
rescue RecordNotFound
  return false
else
  return true
end

#update_attributes!(attributes) ⇒ Object

AR see update_attributes. Uses save! instead of save



964
965
966
967
# File 'lib/passiveldap.rb', line 964

def update_attributes!(attributes)
  self.attributes=(attributes)
  save!
end