#-- # Copyright (C) 2008 Dimitrij Denissenko # Please read LICENSE document for more information. #++ class User < ActiveRecord::Base has_and_belongs_to_many :groups, :order => 'groups.name', :uniq => true, :include => [:projects] has_and_belongs_to_many :ticket_subscriptions, :class_name => 'Ticket', :join_table => 'ticket_subscribers', :uniq => true has_many :ticket_changes, :dependent => :nullify has_many :tickets_composed, :foreign_key => :user_id, :class_name => 'Ticket', :dependent => :nullify has_many :tickets_assigned_to, :foreign_key => :assigned_user_id, :class_name => 'Ticket', :dependent => :nullify before_create :setup_activation before_create :assign_to_user_groups before_create :reset_private_key before_destroy :validate_on_destroy validates_presence_of :login validates_uniqueness_of :login, :allow_nil => true validates_presence_of :plain_password, :on => :create validates_confirmation_of :plain_password validates_length_of :login, :within => 3..40, :allow_nil => true validates_length_of :plain_password, :within => 6..40, :on => :create validates_presence_of :name, :email, :if => Proc.new {|u| !u.public? } validates_uniqueness_of :email, :allow_nil => true, :if => Proc.new {|u| !u.public? } validates_as_email :email attr_accessible :name, :plain_password, :plain_password_confirmation attr_accessor :plain_password, :plain_password_confirmation protected def before_validation if admin? self.groups = [] else self.groups << Group.default_group end true end def after_validation #nodoc: unless plain_password.blank? self.password = hash_crypt(plain_password) end true end def after_save #nodoc: self.plain_password = self.plain_password_confirmation = nil true end def validate if public? && admin? errors.add(:admin, 'must be disabled. Public can never have admin rights.') end if !admin? && last_admin? errors.add(:admin, "must be enabled. User '#{self.login}' is the last available admin.") end if current? && !admin? && User.current.admin? errors.add(:admin, 'must be enabled. You cannot downgrade your own account.') end if public? && !active? errors.add(:active, 'must be enabled. Public must be active.') end if current? && !active? && User.current.active? errors.add(:active, 'must be enabled. You cannot deactivate your own account.') end errors.empty? end def validate_on_update if public? errors.add_to_base('Public user cannot be modified.') end errors.empty? end # Check if the user can be deleted # Callback: before_destroy def validate_on_destroy if public? errors.add_to_base('Public user cannot be deleted.') end if last_admin? errors.add_to_base("Cannot delete. User '#{self.login}' is the last available admin.") end if current? errors.add_to_base('You cannot delete your own account.') end errors.empty? end # Assigns the user to user groups as defined in the configuration # Callback: before_create def assign_to_user_groups Group.find(:all, :conditions => ['id IN (?)', RetroCM[:general][:user_management][:assign_to_groups]]).each do |group| self.groups << group end unless RetroCM[:general][:user_management][:assign_to_groups].blank? true end # Sets the activation attributes based on configuration # Callback: before_create def setup_activation activation = RetroCM[:general][:user_management][:activation] if activation == 'admin' self.active = false elsif activation == 'email' self.active = false self.reset_activation_code end true end def hash_crypt(pass) Digest::SHA1.hexdigest("+++{{#{pass}:#{self.salt}}}---") end private def self.admin_users User.find(:all, :conditions => ["admin = ? AND login <> ?", true, 'Public']) end def write_attribute(attr_name, value) if self.new_record? || !self.public? super(attr_name, value) end end public def self.current=(user) @current_user = user end def self.current @current_user.is_a?(User) ? @current_user : public_user end def self.public_user(options = {:include => [{:groups => :projects}]}) User.find_by_login('Public', options) end def self.authenticate(params) return nil if params[:login] == 'Public' if user = find_by_login_and_active(params[:login], true) if secure_auth? tan = Tan.spend(params[:tan]) if tan && Digest::SHA1.hexdigest("#{tan}:#{user.password}") == params[:hash] return user end else if user.valid_password?(params[:password]) return user elsif user.outdated_valid_password?(params[:password]) user.plain_password = user.plain_password_confirmation = params[:password] user.save! return user end end end nil end def self.secure_auth? RetroCM[:general][:user_management][:secure_auth] end def self.destroy_all_expired timestamp = (Time.now - RetroCM[:general][:user_management][:expiration].hours) conditions = ['activation_code IS NOT NULL AND active = ? AND created_at < ?', false, timestamp] destroy_all(conditions) end def protected_attributes=(attrs) [:admin, :login, :email, :active, :group_ids].each do |name| self.send("#{name}=", attrs[name]) if attrs[name] end end def public? login == 'Public' end def current? self == User.current end def last_admin? self.class.admin_users.size == 1 && self.class.admin_users.first == self end def valid_password?(pass) password == hash_crypt(pass) end def outdated_valid_password?(pass) ["+++{{#{pass}}}---", "c-o-l-l-a-b-o-a--#{pass}--"].map do |pattern| Digest::SHA1.hexdigest(pattern) end.include?(password) end def salt read_attribute(:salt) || write_attribute(:salt, Randomizer.string) end def activation_code read_attribute(:activation_code) || reset_activation_code end def reset_activation_code self.activation_code = Randomizer.pronounceable end def private_key read_attribute(:private_key) || reset_private_key end # Resets user's privte key # Callback: before_create def reset_private_key self.private_key = hash_crypt(Randomizer.string) end def reset_password self.plain_password = self.plain_password_confirmation = Randomizer.string end def projects @projects ||= if self.admin? Project.find(:all, :conditions => ['closed = ?', false], :order => 'name') else groups.map do |group| group.projects.select {|project| !project.closed? } end.flatten.uniq.sort {|a,b| a.name <=> b.name } end end def project_contributor?(project) projects.include?(project) end def find_in_projects_by_short_name(short_name) projects.find {|pr| pr.short_name == short_name } end # Arguments may be either # an url_for-style options hash or a path string according to the # Route map. Examples: # # user.access_permitted?(:controller => 'post', :action => 'view', :id => 25) # user.access_permitted?('/post/view/25') # def access_permitted?(params) path_string = params.is_a?(Hash) ? ActionController::Routing::Routes.generate_extras(params.dup, {}).first : params.dup options = ActionController::Routing::Routes.recognize_path(path_string) controller_class = "#{options[:controller].camelize}Controller".constantize action_name = options[:action] ? options[:action].to_s : 'index' project = options[:project_name] ? Project.find_by_short_name(options[:project_name]) : Project.current controller_class.authorize?(action_name, options, self, project) end def permissions(project) @permissions ||= {} @permissions[project.short_name] ||= groups.inject([]) do |res, group| res += group.permissions if group.access_to_project?(project) res.uniq! res end end # Expects a permission name, eg. 'view_tickets' or 'browse_source' for the given project def has_permission?(project, permission_name) admin? || permissions(project).include?(permission_name.to_s) end def username login end def group_names groups.map(&:name) end def member_of_group?(group) groups.include?(group) end def clear_association_cache #:nodoc: super @projects = nil @permissions = nil end end