add support for openid
[gitorious:jsharp-adv_dev-clone.git] / app / models / user.rb
1 require 'digest/sha1'
2
3 class User < ActiveRecord::Base
4   has_many :projects
5   has_many :committerships
6   has_many :repositories, :through => :committerships
7   has_many :ssh_keys, :order => "id desc"
8   has_many :comments
9   has_many :events, :order => "events.created_at asc", :dependent => :destroy
10   
11   # Virtual attribute for the unencrypted password
12   attr_accessor :password, :current_password
13
14   attr_protected :login
15
16   validates_presence_of     :login, :email,               :if => :password_required?
17   validates_format_of       :login, :with => /^[a-z0-9\-_\.]+$/i
18   validates_format_of       :email, :with => /^[^@\s]+@([\-a-z0-9]+\.)+[a-z]{2,}$/i
19   validates_presence_of     :password,                   :if => :password_required?
20   validates_presence_of     :password_confirmation,      :if => :password_required?
21   validates_length_of       :password, :within => 4..40, :if => :password_required?
22   validates_confirmation_of :password,                   :if => :password_required?
23   validates_length_of       :login,    :within => 3..40
24   validates_length_of       :email,    :within => 3..100
25   validates_uniqueness_of   :login, :email, :case_sensitive => false
26
27   before_save :encrypt_password
28   before_create :make_activation_code
29
30   def self.find_by_login!(name)
31     find_by_login(name) || raise(ActiveRecord::RecordNotFound )
32   end
33
34   # Authenticates a user by their login name and unencrypted password.  Returns the user or nil.
35   def self.authenticate(email, password)
36     u = find :first, :conditions => ['email = ? and activated_at IS NOT NULL', email] # need to get the salt
37     u && u.authenticated?(password) ? u : nil
38   end
39
40   # Encrypts some data with the salt.
41   def self.encrypt(password, salt)
42     Digest::SHA1.hexdigest("--#{salt}--#{password}--")
43   end
44
45   def self.generate_random_password(password_size = 12)
46     characters = (("a".."z").to_a + ("0".."9").to_a) - %w[0 o i l 1]
47     (0...password_size).collect do |char|
48       characters[rand(characters.length)]
49     end.join
50   end
51   
52   # Activates the user in the database.
53   def activate
54     @activated = true
55     self.attributes = {:activated_at => Time.now.utc, :activation_code => nil}
56     save(false)
57   end
58
59   def activated?
60     # the existence of an activation code means they have not activated yet
61     activation_code.nil?
62   end
63
64   # Returns true if the user has just been activated.
65   def recently_activated?
66     @activated
67   end
68
69   # Encrypts the password with the user salt
70   def encrypt(password)
71     self.class.encrypt(password, salt)
72   end
73
74   def authenticated?(password)
75     crypted_password == encrypt(password)
76   end
77
78   def remember_token?
79     remember_token_expires_at && Time.now.utc < remember_token_expires_at
80   end
81
82   # These create and unset the fields required for remembering users between browser closes
83   def remember_me
84     remember_me_for 2.weeks
85   end
86
87   def remember_me_for(time)
88     remember_me_until time.from_now.utc
89   end
90
91   def remember_me_until(time)
92     self.remember_token_expires_at = time
93     self.remember_token            = encrypt("#{email}--#{remember_token_expires_at}")
94     save(false)
95   end
96
97   def forget_me
98     self.remember_token_expires_at = nil
99     self.remember_token            = nil
100     save(false)
101   end
102
103   def reset_password!
104     generated = User.generate_random_password
105     self.password = generated
106     self.password_confirmation = generated
107     self.save!
108     generated
109   end
110
111   def can_write_to?(repository)
112     !!committerships.find_by_repository_id(repository.id)
113   end
114
115   def to_param
116     login
117   end
118
119   def to_xml(opts = {})
120     super({:except => [:activation_code, :crypted_password, :remember_token, :remember_token_expires_at, :salt, :ssh_key_id]}.merge(opts))
121   end
122   
123   protected
124     # before filter
125     def encrypt_password
126       return if password.blank?
127       self.salt = Digest::SHA1.hexdigest("--#{Time.now.to_s}--#{login}--") if new_record?
128       self.crypted_password = encrypt(password)
129     end
130
131     def password_required?
132       not_openid? && (crypted_password.blank? || !password.blank?)
133     end
134
135     def not_openid?
136       identity_url.blank?
137     end
138
139     def make_activation_code
140       self.activation_code = Digest::SHA1.hexdigest( Time.now.to_s.split(//).sort_by {rand}.join )
141     end
142 end