1 module ActiveRbacMixins
2 # The UserMixin module provides the functionality for the User
3 # ActiveRecord class. You can use it the following way: Create a file
4 # "model/user.rb" in your "RAILS_ENV/app" directory.
6 # Here, create the User class and import the User mixin modules,
9 # class User < ActiveRecord::Base
10 # include ActiveRbacMixins::UserMixins::Core
11 # include ActiveRbacMixins::UserMixins::Validation
13 # # insert your custom code here
16 # This will create a ActiveRecord class you can then extend to your liking (i.e.
17 # just imagine you had written all the stuff that ActiveRbac's User
18 # class provides and you can now write some custom lines below it).
21 @@ldap_search_con = nil
22 # This method is called when the module is included.
24 # On inclusion, we do a nifty bit of meta programming and make the
25 # including class behave like ActiveRBAC's User class without some
26 # of the validation. Extensive validation can be done by including the
28 def self.included(base)
30 # users have a n:m relation to group
31 has_and_belongs_to_many :groups, :uniq => true
32 # users have a n:m relation to roles
33 has_and_belongs_to_many :roles, :uniq => true
34 # users have 0..1 user_registration records assigned to them
35 has_one :user_registration
37 # We don't want to assign things to roles and groups in bulk assigns.
38 attr_protected :roles, :groups, :created_at, :updated_at, :last_logged_in_at, :login_failure_count, :password_hash_type
40 # This method returns a hash with the the available user states.
41 # By default it returns the private class constant DEFAULT_STATES.
46 # This method returns an array with the names of all available
47 # password hash types supported by this User class.
48 def self.password_hash_types
49 default_password_hash_types
52 # When a record object is initialized, we set the state, password
53 # hash type, indicator whether the password has freshly been set
54 # (@new_password) and the login failure count to
55 # unconfirmed/false/0 when it has not been set yet.
56 def initialize (attributes = nil)
59 self.state = User.states['unconfirmed'] if self.state.nil?
60 self.password_hash_type = 'md5' if self.password_hash_type.to_s == ''
62 @new_password = false if @new_password.nil?
63 @new_hash_type = false if @new_hash_type.nil?
65 self.login_failure_count = 0 if self.login_failure_count.nil?
68 # Set the last login time etc. when the record is created at first.
70 self.last_logged_in_at = Time.now
73 # Override the accessor for the "password_hash_type" property so it sets
74 # the "@new_hash_type" private property to signal that the password's
75 # hash method has been changed. Changing the password hash type is only
76 # possible if a new password has been provided.
77 def password_hash_type=(value)
78 write_attribute(:password_hash_type, value)
82 # After saving, we want to set the "@new_hash_type" value set to false
84 after_save '@new_hash_type = false'
86 # Add accessors for "new_password" property. This boolean property is set
87 # to true when the password has been set and validation on this password is
89 attr_accessor :new_password
91 # Generate accessors for the password confirmation property.
92 attr_accessor :password_confirmation
94 # Overriding the default accessor to update @new_password on setting this
97 write_attribute(:password, value)
101 # Returns true if the password has been set after the User has been loaded
102 # from the database and false otherwise
104 @new_password == true
107 # Method to update the password and confirmation at the same time. Call
108 # this method when you update the password from code and don't need
109 # password_confirmation - which should really only be used when data
114 # user = User.find(1)
115 # user.update_password "n1C3s3cUreP4sSw0rD"
118 def update_password(pass)
119 self.password_confirmation = pass
123 # After saving the object into the database, the password is not new any more.
124 after_save '@new_password = false'
126 # This method writes the attribute "password" to the hashed version. It is
127 # called in the after_validation hook set by the "after_validation" command
129 # The password is only encrypted when no errors occurred on validation, the
130 # password is new and the password is not nil.
131 # This method also sets the "password_salt" property's value used in
133 # After encryption, the password's "new" state is reset and the confirmation
134 # is cleared. The password hash's type will also be set to "not new" since
135 # we get problems with double validation (as it happens when using save!)
138 if errors.count == 0 and @new_password and not password.nil?
139 # generate a new 10-char long hash only Base64 encoded so things are compatible
140 self.password_salt = [Array.new(10){rand(256).chr}.join].pack("m")[0..9];
142 # write encrypted password to object property
143 write_attribute(:password, hash_string(password))
145 # mark password as "not new" any more
146 @new_password = false
147 password_confirmation = nil
149 # mark the hash type as "not new" any more
150 @new_hash_type = false
154 # This method returns all roles assigned to the given user - including
155 # the ones he gets by being assigned a child role (i.e. the parents)
156 # and the one he gets through his groups (inheritance is also considered)
161 for role in self.roles
162 result << role.ancestors_and_self
165 for group in self.groups
166 result << group.all_roles
175 # This method returns all groups assigned to the given user - including
176 # the ones he gets by being assigned through group inheritance.
180 for group in self.groups
181 result << group.ancestors_and_self
190 # This method returns all groups assigned to the given user via ldap - including
191 # the ones he gets by being assigned through group inheritance.
192 def all_groups_ldap(group_ldap)
194 for group in group_ldap
195 result << group.ancestors_and_self
204 # This method returns true if the user is assigned the role with one of the
205 # role titles given as parameters. False otherwise.
206 def has_role?(*role_titles)
207 obj = all_roles.detect do |role|
208 role_titles.include?(role.title)
214 # This method returns a list of all the StaticPermission entities that
215 # have been assigned to this user through his roles.
216 def all_static_permissions
217 permissions = Array.new
219 all_roles.each do |role|
220 permissions.concat(role.static_permissions)
226 # This method returns true if the user is granted the permission with one
227 # of the given permission titles.
228 def has_permission?(*permission_titles)
229 all_roles.detect do |role|
230 role.static_permissions.detect do |permission|
231 permission_titles.include?(permission.title)
236 # Returns false. is_anonymous? will only return true on AnonymousUser
243 # This method creates a new registration token for the current user. Raises
244 # a MultipleRegistrationTokens Exception if the user already has a
245 # registration token assigned to him.
247 # Use this method instead of creating user_registration objects directly!
248 def create_user_registration
249 raise unless user_registration.nil?
251 token = UserRegistration.new
252 self.user_registration = token
255 # This method expects the token for the current user. If the token is
256 # correct, the user's state will be set to "confirmed" and the associated
257 # "user_registration" record will be removed.
258 # Returns "true" on success and "false" on failure/or the user is already
259 # confirmed and/or has no "user_registration" record.
260 def confirm_registration(token)
261 return false if self.user_registration.nil?
262 return false if user_registration.token != token
263 return false unless state_transition_allowed?(state, User.states['confirmed'])
265 self.state = User.states['confirmed']
267 user_registration.destroy
272 # Returns the default state of new User objects.
273 def self.default_state
274 User.states['unconfirmed']
277 # Returns true when users with the given state may log in. False otherwise.
278 # The given parameter must be an integer.
279 def self.state_allows_login?(state)
280 [ User.states['confirmed'], User.states['retrieved_password'] ].include?(state)
283 # Overwrite the state setting so it backs up the initial state from
286 @old_state = state if @old_state.nil?
287 write_attribute(:state, value)
290 # Overriding this method to make "login" visible as "User name". This is called in
291 # forms to create error messages.
292 def self.human_attribute_name (attr)
294 when 'login' then 'User name'
299 # This static method removes all users with state "unconfirmed" and expired
300 # registration tokens.
301 def self.purge_users_with_expired_registration
302 registrations = UserRegistration.find :all,
303 :conditions => [ 'expires_at < ?', Time.now.ago(2.days) ]
304 registrations.each do |registration|
305 registration.user.destroy
309 # This static method tries to find a user with the given login and password
310 # in the database. Returns the user or nil if he could not be found
311 def self.find_with_credentials(login, password)
313 user = User.find :first,
314 :conditions => [ 'login = ?', login ]
316 # If the user could be found and the passwords equal then return the user
317 if not user.nil? and user.password_equals? password
318 if user.login_failure_count > 0
319 user.login_failure_count = 0
320 self.execute_without_timestamps { user.save! }
326 # Otherwise increase the login count - if the user could be found - and return nil
328 user.login_failure_count = user.login_failure_count + 1
329 self.execute_without_timestamps { user.save! }
335 # This static method tries to update the entry with the given info in the
336 # active directory server. Return the error msg if any error occurred
337 def self.update_entry_ldap(login, newlogin, newemail, newpassword)
338 logger.debug( " Modifying #{login} to #{newlogin} #{newemail} using ldap" )
340 if @@ldap_search_con.nil?
341 @@ldap_search_con = initialize_ldap_con(LDAP_SEARCH_USER, LDAP_SEARCH_AUTH)
343 ldap_con = @@ldap_search_con
345 logger.debug( "Unable to connect to LDAP server" )
346 return "Unable to connect to LDAP server"
348 user_filter = "(#{LDAP_SEARCH_ATTR}=#{login})"
350 ldap_con.search( LDAP_SEARCH_BASE, LDAP::LDAP_SCOPE_SUBTREE, user_filter ) do |entry|
354 logger.debug( "User not found in ldap" )
355 return "User not found in ldap"
358 # Update mail/password info
360 LDAP.mod(LDAP::LDAP_MOD_REPLACE,LDAP_MAIL_ATTR,[newemail]),
365 entry << LDAP.mod(LDAP::LDAP_MOD_REPLACE,LDAP_AUTH_ATTR,[newpassword])
369 entry << LDAP.mod(LDAP::LDAP_MOD_REPLACE,LDAP_AUTH_ATTR,["{MD5}"+Base64.b64encode(Digest::MD5.digest(newpassword)).chomp])
373 ldap_con.modify(dn, entry)
374 rescue LDAP::ResultError
375 logger.debug("Error #{ldap_con.err} for #{login} mail/password changing")
376 return "Failed to update entry for #{login}: error #{ldap_con.err}"
379 # Update the dn name if it is changed
380 if not login == newlogin
382 ldap_con.modrdn(dn,"#{LDAP_NAME_ATTR}=#{newlogin}", true)
383 rescue LDAP::ResultError
384 logger.debug("Error #{ldap_con.err} for #{login} dn name changing")
385 return "Failed to update dn name for #{login}: error #{ldap_con.err}"
392 # This static method tries to add the new entry with the given name/password/mail info in the
393 # active directory server. Return the error msg if any error occurred
394 def self.new_entry_ldap(login, password, mail)
396 logger.debug( "Add new entry for #{login} using ldap" )
397 if @@ldap_search_con.nil?
398 @@ldap_search_con = initialize_ldap_con(LDAP_SEARCH_USER, LDAP_SEARCH_AUTH)
400 ldap_con = @@ldap_search_con
402 logger.debug( "Unable to connect to LDAP server" )
403 return "Unable to connect to LDAP server"
407 ldap_password = password
411 ldap_password = "{MD5}"+Base64.b64encode(Digest::MD5.digest(password)).chomp
414 LDAP.mod(LDAP::LDAP_MOD_ADD,'objectclass',LDAP_OBJECT_CLASS),
415 LDAP.mod(LDAP::LDAP_MOD_ADD,LDAP_NAME_ATTR,[login]),
416 LDAP.mod(LDAP::LDAP_MOD_ADD,LDAP_AUTH_ATTR,[ldap_password]),
417 LDAP.mod(LDAP::LDAP_MOD_ADD,LDAP_MAIL_ATTR,[mail]),
419 # Added required sn attr
420 if defined?( LDAP_SN_ATTR_REQUIRED ) && LDAP_SN_ATTR_REQUIRED == :on
421 entry << LDAP.mod(LDAP::LDAP_MOD_ADD,'sn',[login])
425 ldap_con.add("#{LDAP_NAME_ATTR}=#{login},#{LDAP_ENTRY_BASE}", entry)
426 rescue LDAP::ResultError
427 logger.debug("Error #{ldap_con.err} for #{login}")
428 return "Failed to add a new entry for #{login}: error #{ldap_con.err}"
433 # This static method tries to delete the entry with the given login in the
434 # active directory server. Return the error msg if any error occurred
435 def self.delete_entry_ldap(login)
436 logger.debug( "Deleting #{login} using ldap" )
437 if @@ldap_search_con.nil?
438 @@ldap_search_con = initialize_ldap_con(LDAP_SEARCH_USER, LDAP_SEARCH_AUTH)
440 ldap_con = @@ldap_search_con
442 logger.debug( "Unable to connect to LDAP server" )
443 return "Unable to connect to LDAP server"
445 user_filter = "(#{LDAP_SEARCH_ATTR}=#{login})"
447 ldap_con.search( LDAP_SEARCH_BASE, LDAP::LDAP_SCOPE_SUBTREE, user_filter ) do |entry|
451 logger.debug( "User not found in ldap" )
452 return "User not found in ldap"
456 rescue LDAP::ResultError
457 logger.debug( "Failed to delete: error #{ldap_con.err} for #{login}" )
458 return "Failed to delete the entry #{login}: error #{ldap_con.err}"
463 # Check if ldap group support is enabled?
464 def self.ldapgroup_enabled?
465 if defined?( LDAP_MODE ) && LDAP_MODE == :on
466 if defined?( LDAP_GROUP_SUPPORT ) && LDAP_GROUP_SUPPORT == :on
473 # This static method tries to find a group with the given gorup_title to check whether the group is in the LDAP server.
474 def self.find_group_with_ldap(group)
475 if defined?( LDAP_GROUP_OBJECTCLASS_ATTR )
476 filter = "(&(#{LDAP_GROUP_TITLE_ATTR}=#{group})(objectclass=#{LDAP_GROUP_OBJECTCLASS_ATTR}))"
478 filter = "(#{LDAP_GROUP_TITLE_ATTR}=#{group})"
480 result = search_ldap(LDAP_GROUP_SEARCH_BASE, filter)
482 logger.debug( "Fail to find group: #{group} in LDAP" )
485 logger.debug( "group dn: #{result[0]}" )
490 # This static method performs the search with the given search_base, filter
491 def self.search_ldap(search_base, filter, required_attr = nil)
492 if @@ldap_search_con.nil?
493 @@ldap_search_con = initialize_ldap_con(LDAP_SEARCH_USER, LDAP_SEARCH_AUTH)
495 ldap_con = @@ldap_search_con
497 logger.debug( "Unable to connect to LDAP server" )
500 logger.debug( "Search: #{filter}" )
502 ldap_con.search( search_base, LDAP::LDAP_SCOPE_SUBTREE, filter ) do |entry|
504 result << entry.attrs
505 if required_attr and entry.attrs.include?(required_attr)
506 result << entry.vals(required_attr)
516 # This static method performs the search with the given grouplist, user to return the groups that the user in
517 def self.render_grouplist_ldap(grouplist, user = nil)
519 if @@ldap_search_con.nil?
520 @@ldap_search_con = initialize_ldap_con(LDAP_SEARCH_USER, LDAP_SEARCH_AUTH)
522 ldap_con = @@ldap_search_con
524 logger.debug( "Unable to connect to LDAP server" )
530 if defined?( LDAP_USER_FILTER )
531 filter = "(&(#{LDAP_SEARCH_ATTR}=#{user})#{LDAP_USER_FILTER})"
533 filter = "(#{LDAP_SEARCH_ATTR}=#{user})"
536 user_memberof_attr = String.new
537 ldap_con.search( LDAP_SEARCH_BASE, LDAP::LDAP_SCOPE_SUBTREE, filter ) do |entry|
539 if defined?( LDAP_USER_MEMBEROF_ATTR ) && entry.attrs.include?( LDAP_USER_MEMBEROF_ATTR )
540 user_memberof_attr=entry.vals(LDAP_USER_MEMBEROF_ATTR)
544 logger.debug( "Failed to find #{user} in ldap" )
547 logger.debug( "User dn: #{user_dn} user_memberof_attr: #{user_memberof_attr}" )
550 group_dn = String.new
551 group_member_attr = String.new
552 grouplist.each do |eachgroup|
553 if eachgroup.kind_of? String
556 if eachgroup.kind_of? Group
557 group = eachgroup.title
560 unless group.kind_of? String
561 raise ArgumentError, "illegal parameter type to user#render_grouplist_ldap?: #{eachgroup.class.name}"
565 if defined?( LDAP_GROUP_OBJECTCLASS_ATTR )
566 filter = "(&(#{LDAP_GROUP_TITLE_ATTR}=#{group})(objectclass=#{LDAP_GROUP_OBJECTCLASS_ATTR}))"
568 filter = "(#{LDAP_GROUP_TITLE_ATTR}=#{group})"
571 # clean group_dn, group_member_attr
573 group_member_attr = ""
574 logger.debug( "Search group: #{filter}" )
575 ldap_con.search( LDAP_GROUP_SEARCH_BASE, LDAP::LDAP_SCOPE_SUBTREE, filter ) do |entry|
577 if defined?( LDAP_GROUP_MEMBER_ATTR ) && entry.attrs.include?(LDAP_GROUP_MEMBER_ATTR)
578 group_member_attr = entry.vals(LDAP_GROUP_MEMBER_ATTR)
582 logger.debug( "Failed to find #{group} in ldap" )
591 # user memberof attr exist?
592 if user_memberof_attr and user_memberof_attr.include?(group_dn)
594 logger.debug( "#{user} is in #{group}" )
597 # group member attr exist?
598 if group_member_attr and group_member_attr.include?(user_dn)
600 logger.debug( "#{user} is in #{group}" )
603 logger.debug("#{user} is not in #{group}")
609 # This static method tries to update the password with the given login in the
610 # active directory server. Return the error msg if any error occurred
611 def self.change_password_ldap(login, password)
612 if @@ldap_search_con.nil?
613 @@ldap_search_con = initialize_ldap_con(LDAP_SEARCH_USER, LDAP_SEARCH_AUTH)
615 ldap_con = @@ldap_search_con
617 logger.debug( "Unable to connect to LDAP server" )
618 return "Unable to connect to LDAP server"
620 user_filter = "(#{LDAP_SEARCH_ATTR}=#{login})"
622 ldap_con.search( LDAP_SEARCH_BASE, LDAP::LDAP_SCOPE_SUBTREE, user_filter ) do |entry|
626 logger.debug( "User not found in ldap" )
627 return "User not found in ldap"
632 ldap_password = password
636 ldap_password = "{MD5}"+Base64.b64encode(Digest::MD5.digest(password)).chomp
639 LDAP.mod(LDAP::LDAP_MOD_REPLACE, LDAP_AUTH_ATTR, [ldap_password]),
642 ldap_con.modify(dn, entry)
643 rescue LDAP::ResultError
644 logger.debug("Error #{ldap_con.err} for #{login}")
645 return "#{ldap_con.err}"
652 # This static method tries to find a user with the given login and
653 # password in the active directory server. Returns nil unless
654 # credentials are correctly found using LDAP.
655 def self.find_with_ldap(login, password)
656 logger.debug( "Looking for #{login} using ldap" )
657 ldap_info = Array.new
658 # use cache to check the password firstly
659 key="ldap_cache_userpasswd:" + login
661 if Rails.cache.exist?(key)
662 ar = Rails.cache.read(key)
663 if ar[0] == Digest::MD5.digest(password)
666 logger.debug("login success for checking with ldap cache")
671 if @@ldap_search_con.nil?
672 @@ldap_search_con = initialize_ldap_con(LDAP_SEARCH_USER, LDAP_SEARCH_AUTH)
674 ldap_con = @@ldap_search_con
676 logger.debug( "Unable to connect to LDAP server" )
680 if defined?( LDAP_USER_FILTER )
681 user_filter = "(&(#{LDAP_SEARCH_ATTR}=#{login})#{LDAP_USER_FILTER})"
683 user_filter = "(#{LDAP_SEARCH_ATTR}=#{login})"
685 logger.debug( "Search for #{user_filter}" )
687 ldap_password = String.new
689 ldap_con.search( LDAP_SEARCH_BASE, LDAP::LDAP_SCOPE_SUBTREE, user_filter ) do |entry|
691 ldap_info[0] = String.new(entry[LDAP_MAIL_ATTR][0])
692 if defined?( LDAP_AUTHENTICATE ) && LDAP_AUTHENTICATE == :local
693 if entry[LDAP_AUTH_ATTR] then
694 ldap_password = entry[LDAP_AUTH_ATTR][0]
695 logger.debug( "Get auth_attr:#{ldap_password}" )
697 logger.debug( "Failed to get attr:#{LDAP_AUTH_ATTR}" )
702 logger.debug( "Search failed: error #{ @@ldap_search_con.err}" )
703 @@ldap_search_con.unbind
704 @@ldap_search_con = nil
708 logger.debug( "User not found in ldap" )
711 # Attempt to authenticate user
712 case LDAP_AUTHENTICATE
714 authenticated = false
717 if ldap_password == password then
723 if ldap_password == "{MD5}"+Base64.b64encode(Digest::MD5.digest(password)) then
727 if authenticated == true
728 ldap_info[0] = String.new(entry[LDAP_MAIL_ATTR][0])
729 ldap_info[1] = String.new(entry[LDAP_NAME_ATTR][0])
732 # Don't match the passwd locally, try to bind to the ldap server
733 user_con= initialize_ldap_con(dn,password)
735 logger.debug( "Unable to connect to LDAP server as #{dn} using credentials supplied" )
737 # Redo the search as the user for situations where the anon search may not be able to see attributes
738 user_con.search( LDAP_SEARCH_BASE, LDAP::LDAP_SCOPE_SUBTREE, user_filter ) do |entry|
739 if entry[LDAP_MAIL_ATTR] then
740 ldap_info[0] = String.new(entry[LDAP_MAIL_ATTR][0])
742 if entry[LDAP_NAME_ATTR] then
743 ldap_info[1] = String.new(entry[LDAP_NAME_ATTR][0])
751 Rails.cache.write(key, [Digest::MD5.digest(password), ldap_info[0], ldap_info[1]], :expires_in => 2.minute)
752 logger.debug( "login success for checking with ldap server" )
756 # This method checks whether the given value equals the password when
757 # hashed with this user's password hash type. Returns a boolean.
758 def password_equals?(value)
759 return hash_string(value) == self.password
762 # Sets the last login time and saves the object. Note: Must currently be
763 # called explicitely!
765 self.last_logged_in_at = DateTime.now
766 self.class.execute_without_timestamps { save }
769 # Returns true if the the state transition from "from" state to "to" state
770 # is valid. Returns false otherwise. +new_state+ must be the integer value
771 # of the state as returned by +User.states['state_name']+.
773 # Note that currently no permission checking is included here; It does not
774 # matter what permissions the currently logged in user has, only that the
775 # state transition is legal in principle.
776 def state_transition_allowed?(from, to)
780 return true if from == to # allow keeping state
783 when User.states['unconfirmed']
785 when User.states['confirmed']
786 [ User.states['retrieved_password'], User.states['locked'], User.states['deleted'] ].include?(to)
787 when User.states['locked']
788 [ User.states['confirmed'], User.states['deleted'] ].include?(to)
789 when User.states['deleted']
790 [ User.states['confirmed'] ].include?(to)
791 when User.states['retrieved_password']
792 [ User.states['confirmed'], User.states['locked'], User.states['deleted'] ].include?(to)
794 User.states.value?(to)
800 # After validation, the password should be encrypted
801 after_validation :encrypt_password
804 # This method allows to execute a block while deactivating timestamp
806 def self.execute_without_timestamps
807 old_state = ActiveRecord::Base.record_timestamps
808 ActiveRecord::Base.record_timestamps = false
812 ActiveRecord::Base.record_timestamps = old_state
817 validates_presence_of :login, :email, :password, :password_hash_type, :state,
818 :message => 'must be given'
820 validates_uniqueness_of :login,
821 :message => 'is the name of an already existing user.'
823 # Overriding this method to do some more validation: Password equals
824 # password_confirmation, state an password hash type being in the range
827 # validate state and password has type to be in the valid range of values
828 errors.add(:password_hash_type, "must be in the list of hash types.") unless User.password_hash_types.include? password_hash_type
829 # check that the state transition is valid
830 errors.add(:state, "must be a valid new state from the current state.") unless state_transition_allowed?(@old_state, state)
832 # validate the password
833 if @new_password and not password.nil?
834 errors.add(:password, 'must match the confirmation.') unless password_confirmation == password
837 # check that the password hash type has not been set if no new password
839 if @new_hash_type and (!@new_password or password.nil?) then
840 errors.add(:password_hash_type, 'cannot be changed unless a new password has been provided.')
845 # This method returns a hash which contains a mapping of user states
846 # valid by default and their description.
847 def self.default_states
853 # The user has just retrieved his password and he must now
854 # it. The user cannot anything in this state but change his
855 # password after having logged in and retrieve another one.
856 'retrieved_password' => 5
860 # This method returns an array which contains all valid hash types.
861 def self.default_password_hash_types
865 # Hashes the given parameter by the selected hashing method. It uses the
866 # "password_salt" property's value to make the hashing more secure.
867 def hash_string(value)
868 return case password_hash_type
869 when 'md5' then Digest::MD5.hexdigest(value + self.password_salt)
873 # this method returns a ldap object using the provided user name
875 def self.initialize_ldap_con(user_name, password)
876 return nil unless defined?( LDAP_SERVERS )
877 ldap_servers = LDAP_SERVERS.split(":")
882 max_ldap_attempts = defined?( LDAP_MAX_ATTEMPTS ) ? LDAP_MAX_ATTEMPTS : 10
884 while !ping and count < max_ldap_attempts
886 server = ldap_servers[rand(ldap_servers.length)]
887 # Ruby only contains TCP echo ping. Use system ping for real ICMP ping.
888 ping = system("ping -c 1 #{server} >/dev/null 2>/dev/null")
891 if count == max_ldap_attempts
892 logger.debug("Unable to ping to any LDAP server: #{LDAP_SERVERS}")
896 logger.debug( "Connecting to #{server} as '#{user_name}'" )
898 if defined?( LDAP_SSL ) && LDAP_SSL == :on
899 port = defined?( LDAP_PORT ) ? LDAP_PORT : 636
900 conn = LDAP::SSLConn.new( server, port)
902 port = defined?( LDAP_PORT ) ? LDAP_PORT : 389
903 # Use LDAP StartTLS. By default start_tls is off.
904 if defined?( LDAP_START_TLS ) && LDAP_START_TLS == :on
905 conn = LDAP::SSLConn.new( server, port, true)
907 conn = LDAP::Conn.new( server, port)
910 conn.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3)
911 if defined?( LDAP_REFERRALS ) && LDAP_REFERRALS == :off
912 conn.set_option(LDAP::LDAP_OPT_REFERRALS, LDAP::LDAP_OPT_OFF)
914 conn.bind(user_name, password)
915 rescue LDAP::ResultError
919 logger.debug( "Not bound: error #{conn.err} for #{user_name}" )
922 logger.debug( "Bound as #{user_name}" )
930 # This method is called when the module is included.
932 # On inclusion, we do a nifty bit of meta programming and make the
933 # including class validate as ActiveRBAC's User class does.
934 def self.included(base)
936 validates_format_of :login,
937 :with => %r{^[\w \$\^\-\.#\*\+&'"]*$},
938 :message => 'must not contain invalid characters.'
939 validates_length_of :login,
940 :in => 2..100, :allow_nil => true,
941 :too_long => 'must have less than 100 characters.',
942 :too_short => 'must have more than two characters.'
944 # We want a valid email address. Note that the checking done here is very
945 # rough. Email adresses are hard to validate now domain names may include
946 # language specific characters and user names can be about anything anyway.
947 # However, this is not *so* bad since users have to answer on their email
948 # to confirm their registration.
949 validates_format_of :email,
950 :with => %r{^([\w\-\.\#\$%&!?*\'\+=(){}|~_]+)@([0-9a-zA-Z\-\.\#\$%&!?*\'=(){}|~]+)+$},
951 :message => 'must be a valid email address.'
953 # We want to validate the format of the password and only allow alphanumeric
954 # and some punctiation characters.
955 # The format must only be checked if the password has been set and the record
956 # has not been stored yet and it has actually been set at all. Make sure you
957 # include this condition in your :if parameter to validates_format_of when
958 # overriding the password format validation.
959 validates_format_of :password,
960 :with => %r{^[\w\.\- !?(){}|~*_]+$},
961 :message => 'must not contain invalid characters.',
962 :if => Proc.new { |user| user.new_password? and not user.password.nil? }
964 # We want the password to have between 6 and 64 characters.
965 # The length must only be checked if the password has been set and the record
966 # has not been stored yet and it has actually been set at all. Make sure you
967 # include this condition in your :if parameter to validates_length_of when
968 # overriding the length format validation.
969 validates_length_of :password,
971 :too_long => 'must have between 6 and 64 characters.',
972 :too_short => 'must have between 6 and 64 characters.',
973 :if => Proc.new { |user| user.new_password? and not user.password.nil? }