require 'digest/sha1' # this model expects a certain database layout and its based on the name/login pattern. #class User < ActiveRecord::Base class User < Party class_table_inheritance # TODO (uwe): We need to specify :join_table, :foreign_key and :association_foreign_key # even if they follow the defaults since ClassTableInheritanceInRails breaks it. has_and_belongs_to_many :groups, :join_table => "groups_users", :foreign_key => "user_id", :association_foreign_key => 'group_id' has_and_belongs_to_many :work_lock_subscribers, :class_name => 'User', :join_table => "user_work_lock_subscriptions", :foreign_key => "user_id", :association_foreign_key => 'subscriber_user_id' has_many :works, :foreign_key => :user_id, :order => :completed_at has_many :work_locks, :foreign_key => :user_id, :order => :end_on attr_accessor :password_needs_confirmation after_save '@password_needs_confirmation = false' after_validation :crypt_password validates_presence_of :login validates_length_of :login, :within => 3..40 validates_uniqueness_of :login validates_presence_of :email validates_length_of :email, :allow_nil => false, :maximum => 60, :if => :email validates_uniqueness_of :email # This is commented out since I want to allow empty passwords # validates_presence_of :password, :if => :validate_password? validates_confirmation_of :password, :if => :validate_password? # This is commented out since I want to allow empty passwords # validates_length_of :password, { :minimum => 5, :if => :validate_password? } validates_length_of :password, { :maximum => 40, :if => :validate_password? } validates_length_of :first_name, :allow_nil => true, :maximum => 40 validates_length_of :last_name, :allow_nil => true, :maximum => 40 validates_length_of :role, :allow_nil => true, :maximum => 40 validates_length_of :security_token, :allow_nil => true, :maximum => 40 validates_inclusion_of :deleted, :in => [true, false], :allow_nil => true, :message => ActiveRecord::Errors.default_error_messages[:blank] def initialize(attributes = nil) super @password_needs_confirmation = false end def controller self.class.name.downcase end def self.authenticate(login, pass) u = find(:first, :conditions => ["login = ? AND verified = ? AND deleted = ?", login, true, false]) return nil if u.nil? find(:first, :conditions => ["login = ? AND salted_password = ? AND verified = ?", login, salted_password(u.salt, hashed(pass)), true]) end def self.authenticate_by_token(id, token) # Allow logins for deleted accounts, but only via this method (and # not the regular authenticate call) logger.info "Attempting authorization of #{id} with #{token}" u = find(:first, :conditions => ["id = ? AND security_token = ?", id, token]) if u logger.info "Authenticated by token: #{u.inspect}" else logger.info "Not authenticated" if u.nil? end return nil if (u.nil? or u.token_expired?) u.update_attribute :verified, true return u end def token_expired? self.security_token.nil? or self.token_expiry.nil? or (Clock.now > self.token_expiry) end def token_stale? token_expired? or Clock.now.to_i >= (self.token_expiry.to_i - self.class.token_lifetime / 2) end def generate_security_token if token_stale? token = new_security_token return token else return self.security_token end end def change_password(pass, confirm = nil) self.password = pass self.password_confirmation = confirm.nil? ? pass : confirm @password_needs_confirmation = true end def self.token_lifetime UserSystem::CONFIG[:security_token_life_hours] * 60 * 60 end def name [first_name, last_name].compact.join(' ') end def includes?(user) return user == self end protected attr_accessor :password, :password_confirmation def validate_password? @password_needs_confirmation end def self.hashed(str) hashed_password = Digest::SHA1.hexdigest("change-me--#{str}--")[0..39] return hashed_password end def crypt_password if @password_needs_confirmation write_attribute("salt", self.class.hashed("salt-#{Clock.now}")) write_attribute("salted_password", self.class.salted_password(salt, self.class.hashed(@password))) end end def new_security_token expiry = Time.at(Clock.now.to_i + self.class.token_lifetime) write_attribute('security_token', self.class.hashed(self.salted_password + Clock.now.to_i.to_s + rand.to_s)) write_attribute('token_expiry', expiry) # update_without_callbacks update return self.security_token end def self.salted_password(salt, hashed_password) salted_password = hashed(salt + hashed_password) return salted_password end end