# == Schema Information # # Table name: users # # id :integer(4) not null, primary key # first_name :string(255) # last_name :string(255) # company :string(255) # password_salt :string(255) default(""), not null # remember_token :string(255) # first_login :boolean(1) default(TRUE) # is_admin :boolean(1) # created_at :datetime # updated_at :datetime # email :string(255) default(""), not null # encrypted_password :string(128) default(""), not null # reset_password_token :string(255) # reset_password_sent_at :datetime # remember_created_at :datetime # sign_in_count :integer(4) default(0) # current_sign_in_at :datetime # last_sign_in_at :datetime # current_sign_in_ip :string(255) # last_sign_in_ip :string(255) # failed_attempts :integer(4) default(0) # unlock_token :string(255) # locked_at :datetime # login :string(255) # class LoginFormatValidator < ActiveModel::EachValidator def validate_each(object, attribute, value) if value.present? unless value.gsub(" ",'') == value object.errors[attribute] << (options[:message] || "cannot contain any whitespace") end if [value[0], value[-1]].any?{ |x| x == "." } object.errors[attribute] << (options[:message] || "cannot contain a period at the start or end") end unless value =~ /[a-zA-Z]/ object.errors[attribute] << (options[:message] || "must contain at least one letter") end unless value =~ /[0-9]/ object.errors[attribute] << (options[:message] || "must contain at least one number") end unless value =~ /[_\-.]/ object.errors[attribute] << (options[:message] || "must contain at least one these special characters \"-_.\"") end unless value =~ /^[a-zA-Z0-9_\-.]+$/ object.errors[attribute] << (options[:message] || "can only contain letters number and special characters \"-_.\"") end end end end class UnchangeableValidator < ActiveModel::EachValidator def validate_each(object, attribute, value) if !object.new_record? && value.present? original = object.class.send(:where, "id = #{object.id}").select("id, #{attribute.to_s}").first if original.send(attribute) != value object.errors[attribute] << (options[:message] || "cannot be changed once assigned") end end end end module Protected require File.dirname(__FILE__) + '/../../../lib/protected/devise_recoverable_extensions' require File.dirname(__FILE__) + '/../../../lib/protected/password_utils' class User < ActiveRecord::Base devise :database_authenticatable, :recoverable, :trackable, :validatable, :lockable, :timeoutable, :password_archivable, :maximum_attempts => 4, :unlock_strategy => :none has_many :old_passwords, :as => :password_archivable, :dependent => :destroy validates :first_name, :format => { :with => /^[a-zA-Z0-9_\s\-.]+$/, :allow_nil => true } validates :last_name, :format => { :with => /^[a-zA-Z0-9_\s\-.]+$/, :allow_nil => true } validate :validate_password_strength, :if => lambda{new_record? || (not password.blank?)} validates :login, :unchangeable => true, :presence => true, :login_format => true, :uniqueness => { :case_sensitive => false }, :length => { :maximum => 64, :minimum => 6 } validates :email, :unchangeable => true # Setup accessible (or protected) attributes for your model attr_accessible :login, :email, :password, :password_confirmation, :remember_me, :first_name, :last_name, :company, :locked_at, :failed_attempts, :first_login, :current_password attr_accessor :current_password after_save :send_mail_if_needed # TODO: Extract this code into security pack extension def archive_password if(self.encrypted_password_changed?) self.old_passwords.create! :encrypted_password => self.encrypted_password, :password_salt => self.password_salt, :password_archivable_id => self.id, :password_archivable_type => self.class.to_s end end def update_on_first_login!(modified_user) if password_used?(modified_user[:password]) raise PasswordAlreadyUsedException else update_attributes!( :password => modified_user[:password], :password_confirmation => modified_user[:password_confirmation], :first_login => false) archive_password end end def name [first_name, last_name].join(' ') end def password_used?(password) self.password = password return password_archive_included? end def reset_password!(pass,conf) if reset_password_token.present? and pass == conf and encrypted_password_changed? and password_archive_included? errors.add(:base, I18n.t('errors.messages.taken_in_past')) else super end self end def random_password self.password = self.password_confirmation = PasswordUtils.generate_random_password end def send_mail_if_needed if !self.changes.empty? && self.changes[:id].present? && self.changes[:id][0].nil? && self.changes[:id][1].present? if self.first_login? self.archive_password UserMailer.welcome_login_instructions(self).deliver UserMailer.welcome_password_instructions(self).deliver end end end class << self def reset_password_and_send_password_instructions(params) record = find_by_email(params["email"]) record.random_password && record.save unless record.nil? send_reset_password_instructions(params) end end # NOTE: To audit the devise actions we need to ensure we include this after devise has been added. This is a bug in the gem # and should be fixed. audit :methods => [:destroy, :unlock_access!], :attributes => [:first_name,:last_name, :company, :is_admin] private def validate_password_strength errors.add(:password, I18n.t('devise.passwords.password_strength')) if not PasswordUtils.strong?(password) end end end