##// END OF EJS Templates
Refactor: move method to model...
Eric Davis -
r4110:747b9ec5683f
parent child
Show More
@@ -1,185 +1,179
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2009 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class MyController < ApplicationController
19 19 before_filter :require_login
20 20
21 21 helper :issues
22 22 helper :custom_fields
23 23
24 24 BLOCKS = { 'issuesassignedtome' => :label_assigned_to_me_issues,
25 25 'issuesreportedbyme' => :label_reported_issues,
26 26 'issueswatched' => :label_watched_issues,
27 27 'news' => :label_news_latest,
28 28 'calendar' => :label_calendar,
29 29 'documents' => :label_document_plural,
30 30 'timelog' => :label_spent_time
31 31 }.merge(Redmine::Views::MyPage::Block.additional_blocks).freeze
32 32
33 33 DEFAULT_LAYOUT = { 'left' => ['issuesassignedtome'],
34 34 'right' => ['issuesreportedbyme']
35 35 }.freeze
36 36
37 37 verify :xhr => true,
38 38 :only => [:add_block, :remove_block, :order_blocks]
39 39
40 40 def index
41 41 page
42 42 render :action => 'page'
43 43 end
44 44
45 45 # Show user's page
46 46 def page
47 47 @user = User.current
48 48 @blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT
49 49 end
50 50
51 51 # Edit user's account
52 52 def account
53 53 @user = User.current
54 54 @pref = @user.pref
55 55 if request.post?
56 56 @user.attributes = params[:user]
57 57 @user.mail_notification = params[:notification_option] || 'only_my_events'
58 58 @user.pref.attributes = params[:pref]
59 59 @user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
60 60 if @user.save
61 61 @user.pref.save
62 62 @user.notified_project_ids = (params[:notification_option] == 'selected' ? params[:notified_project_ids] : [])
63 63 set_language_if_valid @user.language
64 64 flash[:notice] = l(:notice_account_updated)
65 65 redirect_to :action => 'account'
66 66 return
67 67 end
68 68 end
69 @notification_options = User::MAIL_NOTIFICATION_OPTIONS
70 # Only users that belong to more than 1 project can select projects for which they are notified
71 # Note that @user.membership.size would fail since AR ignores
72 # :include association option when doing a count
73 if @user.memberships.length < 1
74 @notification_options.delete_if {|option| option.first == :selected}
75 end
69 @notification_options = @user.valid_notification_options
76 70 @notification_option = @user.mail_notification #? ? 'all' : (@user.notified_projects_ids.empty? ? 'none' : 'selected')
77 71 end
78 72
79 73 # Manage user's password
80 74 def password
81 75 @user = User.current
82 76 unless @user.change_password_allowed?
83 77 flash[:error] = l(:notice_can_t_change_password)
84 78 redirect_to :action => 'account'
85 79 return
86 80 end
87 81 if request.post?
88 82 if @user.check_password?(params[:password])
89 83 @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
90 84 if @user.save
91 85 flash[:notice] = l(:notice_account_password_updated)
92 86 redirect_to :action => 'account'
93 87 end
94 88 else
95 89 flash[:error] = l(:notice_account_wrong_password)
96 90 end
97 91 end
98 92 end
99 93
100 94 # Create a new feeds key
101 95 def reset_rss_key
102 96 if request.post?
103 97 if User.current.rss_token
104 98 User.current.rss_token.destroy
105 99 User.current.reload
106 100 end
107 101 User.current.rss_key
108 102 flash[:notice] = l(:notice_feeds_access_key_reseted)
109 103 end
110 104 redirect_to :action => 'account'
111 105 end
112 106
113 107 # Create a new API key
114 108 def reset_api_key
115 109 if request.post?
116 110 if User.current.api_token
117 111 User.current.api_token.destroy
118 112 User.current.reload
119 113 end
120 114 User.current.api_key
121 115 flash[:notice] = l(:notice_api_access_key_reseted)
122 116 end
123 117 redirect_to :action => 'account'
124 118 end
125 119
126 120 # User's page layout configuration
127 121 def page_layout
128 122 @user = User.current
129 123 @blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT.dup
130 124 @block_options = []
131 125 BLOCKS.each {|k, v| @block_options << [l("my.blocks.#{v}", :default => [v, v.to_s.humanize]), k.dasherize]}
132 126 end
133 127
134 128 # Add a block to user's page
135 129 # The block is added on top of the page
136 130 # params[:block] : id of the block to add
137 131 def add_block
138 132 block = params[:block].to_s.underscore
139 133 (render :nothing => true; return) unless block && (BLOCKS.keys.include? block)
140 134 @user = User.current
141 135 layout = @user.pref[:my_page_layout] || {}
142 136 # remove if already present in a group
143 137 %w(top left right).each {|f| (layout[f] ||= []).delete block }
144 138 # add it on top
145 139 layout['top'].unshift block
146 140 @user.pref[:my_page_layout] = layout
147 141 @user.pref.save
148 142 render :partial => "block", :locals => {:user => @user, :block_name => block}
149 143 end
150 144
151 145 # Remove a block to user's page
152 146 # params[:block] : id of the block to remove
153 147 def remove_block
154 148 block = params[:block].to_s.underscore
155 149 @user = User.current
156 150 # remove block in all groups
157 151 layout = @user.pref[:my_page_layout] || {}
158 152 %w(top left right).each {|f| (layout[f] ||= []).delete block }
159 153 @user.pref[:my_page_layout] = layout
160 154 @user.pref.save
161 155 render :nothing => true
162 156 end
163 157
164 158 # Change blocks order on user's page
165 159 # params[:group] : group to order (top, left or right)
166 160 # params[:list-(top|left|right)] : array of block ids of the group
167 161 def order_blocks
168 162 group = params[:group]
169 163 @user = User.current
170 164 if group.is_a?(String)
171 165 group_items = (params["list-#{group}"] || []).collect(&:underscore)
172 166 if group_items and group_items.is_a? Array
173 167 layout = @user.pref[:my_page_layout] || {}
174 168 # remove group blocks if they are presents in other groups
175 169 %w(top left right).each {|f|
176 170 layout[f] = (layout[f] || []) - group_items
177 171 }
178 172 layout[group] = group_items
179 173 @user.pref[:my_page_layout] = layout
180 174 @user.pref.save
181 175 end
182 176 end
183 177 render :nothing => true
184 178 end
185 179 end
@@ -1,202 +1,187
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2009 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class UsersController < ApplicationController
19 19 layout 'admin'
20 20
21 21 before_filter :require_admin, :except => :show
22 22
23 23 helper :sort
24 24 include SortHelper
25 25 helper :custom_fields
26 26 include CustomFieldsHelper
27 27
28 28 def index
29 29 sort_init 'login', 'asc'
30 30 sort_update %w(login firstname lastname mail admin created_on last_login_on)
31 31
32 32 @status = params[:status] ? params[:status].to_i : 1
33 33 c = ARCondition.new(@status == 0 ? "status <> 0" : ["status = ?", @status])
34 34
35 35 unless params[:name].blank?
36 36 name = "%#{params[:name].strip.downcase}%"
37 37 c << ["LOWER(login) LIKE ? OR LOWER(firstname) LIKE ? OR LOWER(lastname) LIKE ? OR LOWER(mail) LIKE ?", name, name, name, name]
38 38 end
39 39
40 40 @user_count = User.count(:conditions => c.conditions)
41 41 @user_pages = Paginator.new self, @user_count,
42 42 per_page_option,
43 43 params['page']
44 44 @users = User.find :all,:order => sort_clause,
45 45 :conditions => c.conditions,
46 46 :limit => @user_pages.items_per_page,
47 47 :offset => @user_pages.current.offset
48 48
49 49 render :layout => !request.xhr?
50 50 end
51 51
52 52 def show
53 53 @user = User.find(params[:id])
54 54 @custom_values = @user.custom_values
55 55
56 56 # show projects based on current user visibility
57 57 @memberships = @user.memberships.all(:conditions => Project.visible_by(User.current))
58 58
59 59 events = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 10)
60 60 @events_by_day = events.group_by(&:event_date)
61 61
62 62 unless User.current.admin?
63 63 if !@user.active? || (@user != User.current && @memberships.empty? && events.empty?)
64 64 render_404
65 65 return
66 66 end
67 67 end
68 68 render :layout => 'base'
69 69
70 70 rescue ActiveRecord::RecordNotFound
71 71 render_404
72 72 end
73 73
74 74 def add
75 75 @notification_options = User::MAIL_NOTIFICATION_OPTIONS
76 76 @notification_option = Setting.default_notification_option
77 77
78 78 @user = User.new(:language => Setting.default_language)
79 79 @auth_sources = AuthSource.find(:all)
80
81 # TODO: Similar to My#account
82 # Only users that belong to more than 1 project can select projects for which they are notified
83 # Note that @user.membership.size would fail since AR ignores
84 # :include association option when doing a count
85 if @user.memberships.length < 1
86 @notification_options.delete_if {|option| option.first == :selected}
87 end
88 80 end
89 81
90 82 verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
91 83 def create
92 84 @notification_options = User::MAIL_NOTIFICATION_OPTIONS
93 85 @notification_option = Setting.default_notification_option
94 86
95 87 @user = User.new(params[:user])
96 88 @user.admin = params[:user][:admin] || false
97 89 @user.login = params[:user][:login]
98 90 @user.password, @user.password_confirmation = params[:password], params[:password_confirmation] unless @user.auth_source_id
99 91
100 92 # TODO: Similar to My#account
101 93 @user.mail_notification = params[:notification_option] || 'only_my_events'
102 94 @user.pref.attributes = params[:pref]
103 95 @user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
104 96
105 97 if @user.save
106 98 @user.pref.save
107 99 @user.notified_project_ids = (params[:notification_option] == 'selected' ? params[:notified_project_ids] : [])
108 100
109 101 Mailer.deliver_account_information(@user, params[:password]) if params[:send_information]
110 102 flash[:notice] = l(:notice_successful_create)
111 103 redirect_to(params[:continue] ? {:controller => 'users', :action => 'add'} :
112 104 {:controller => 'users', :action => 'edit', :id => @user})
113 105 return
114 106 else
115 107 @auth_sources = AuthSource.find(:all)
116 108 @notification_option = @user.mail_notification
117 109
118 110 render :action => 'add'
119 111 end
120 112 end
121 113
122 114 def edit
123 115 @user = User.find(params[:id])
124 # TODO: Similar to My#account
125 @notification_options = User::MAIL_NOTIFICATION_OPTIONS
126 # Only users that belong to more than 1 project can select projects for which they are notified
127 # Note that @user.membership.size would fail since AR ignores
128 # :include association option when doing a count
129 if @user.memberships.length < 1
130 @notification_options.delete_if {|option| option.first == :selected}
131 end
116 @notification_options = @user.valid_notification_options
132 117 @notification_option = @user.mail_notification
133 118
134 119 if request.post?
135 120 @user.admin = params[:user][:admin] if params[:user][:admin]
136 121 @user.login = params[:user][:login] if params[:user][:login]
137 122 if params[:password].present? && (@user.auth_source_id.nil? || params[:user][:auth_source_id].blank?)
138 123 @user.password, @user.password_confirmation = params[:password], params[:password_confirmation]
139 124 end
140 125 @user.group_ids = params[:user][:group_ids] if params[:user][:group_ids]
141 126 @user.attributes = params[:user]
142 127 # Was the account actived ? (do it before User#save clears the change)
143 128 was_activated = (@user.status_change == [User::STATUS_REGISTERED, User::STATUS_ACTIVE])
144 129 # TODO: Similar to My#account
145 130 @user.mail_notification = params[:notification_option] || 'only_my_events'
146 131 @user.pref.attributes = params[:pref]
147 132 @user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
148 133
149 134 if @user.save
150 135 @user.pref.save
151 136 @user.notified_project_ids = (params[:notification_option] == 'selected' ? params[:notified_project_ids] : [])
152 137
153 138 if was_activated
154 139 Mailer.deliver_account_activated(@user)
155 140 elsif @user.active? && params[:send_information] && !params[:password].blank? && @user.auth_source_id.nil?
156 141 Mailer.deliver_account_information(@user, params[:password])
157 142 end
158 143 flash[:notice] = l(:notice_successful_update)
159 144 redirect_to :back
160 145 end
161 146 end
162 147 @auth_sources = AuthSource.find(:all)
163 148 @membership ||= Member.new
164 149 rescue ::ActionController::RedirectBackError
165 150 redirect_to :controller => 'users', :action => 'edit', :id => @user
166 151 end
167 152
168 153 def edit_membership
169 154 @user = User.find(params[:id])
170 155 @membership = Member.edit_membership(params[:membership_id], params[:membership], @user)
171 156 @membership.save if request.post?
172 157 respond_to do |format|
173 158 if @membership.valid?
174 159 format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' }
175 160 format.js {
176 161 render(:update) {|page|
177 162 page.replace_html "tab-content-memberships", :partial => 'users/memberships'
178 163 page.visual_effect(:highlight, "member-#{@membership.id}")
179 164 }
180 165 }
181 166 else
182 167 format.js {
183 168 render(:update) {|page|
184 169 page.alert(l(:notice_failed_to_save_members, :errors => @membership.errors.full_messages.join(', ')))
185 170 }
186 171 }
187 172 end
188 173 end
189 174 end
190 175
191 176 def destroy_membership
192 177 @user = User.find(params[:id])
193 178 @membership = Member.find(params[:membership_id])
194 179 if request.post? && @membership.deletable?
195 180 @membership.destroy
196 181 end
197 182 respond_to do |format|
198 183 format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' }
199 184 format.js { render(:update) {|page| page.replace_html "tab-content-memberships", :partial => 'users/memberships'} }
200 185 end
201 186 end
202 187 end
@@ -1,460 +1,471
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2009 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require "digest/sha1"
19 19
20 20 class User < Principal
21 21
22 22 # Account statuses
23 23 STATUS_ANONYMOUS = 0
24 24 STATUS_ACTIVE = 1
25 25 STATUS_REGISTERED = 2
26 26 STATUS_LOCKED = 3
27 27
28 28 USER_FORMATS = {
29 29 :firstname_lastname => '#{firstname} #{lastname}',
30 30 :firstname => '#{firstname}',
31 31 :lastname_firstname => '#{lastname} #{firstname}',
32 32 :lastname_coma_firstname => '#{lastname}, #{firstname}',
33 33 :username => '#{login}'
34 34 }
35 35
36 36 MAIL_NOTIFICATION_OPTIONS = [
37 37 [:all, :label_user_mail_option_all],
38 38 [:selected, :label_user_mail_option_selected],
39 39 [:none, :label_user_mail_option_none],
40 40 [:only_my_events, :label_user_mail_option_only_my_events],
41 41 [:only_assigned, :label_user_mail_option_only_assigned],
42 42 [:only_owner, :label_user_mail_option_only_owner]
43 43 ]
44 44
45 45 has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)},
46 46 :after_remove => Proc.new {|user, group| group.user_removed(user)}
47 47 has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
48 48 has_many :changesets, :dependent => :nullify
49 49 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
50 50 has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'"
51 51 has_one :api_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='api'"
52 52 belongs_to :auth_source
53 53
54 54 # Active non-anonymous users scope
55 55 named_scope :active, :conditions => "#{User.table_name}.status = #{STATUS_ACTIVE}"
56 56
57 57 acts_as_customizable
58 58
59 59 attr_accessor :password, :password_confirmation
60 60 attr_accessor :last_before_login_on
61 61 # Prevents unauthorized assignments
62 62 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password, :group_ids
63 63
64 64 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
65 65 validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }, :case_sensitive => false
66 66 validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
67 67 # Login must contain lettres, numbers, underscores only
68 68 validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
69 69 validates_length_of :login, :maximum => 30
70 70 validates_format_of :firstname, :lastname, :with => /^[\w\s\'\-\.]*$/i
71 71 validates_length_of :firstname, :lastname, :maximum => 30
72 72 validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_nil => true
73 73 validates_length_of :mail, :maximum => 60, :allow_nil => true
74 74 validates_confirmation_of :password, :allow_nil => true
75 75
76 76 def before_create
77 77 self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
78 78 true
79 79 end
80 80
81 81 def before_save
82 82 # update hashed_password if password was set
83 83 self.hashed_password = User.hash_password(self.password) if self.password && self.auth_source_id.blank?
84 84 end
85 85
86 86 def reload(*args)
87 87 @name = nil
88 88 super
89 89 end
90 90
91 91 def mail=(arg)
92 92 write_attribute(:mail, arg.to_s.strip)
93 93 end
94 94
95 95 def identity_url=(url)
96 96 if url.blank?
97 97 write_attribute(:identity_url, '')
98 98 else
99 99 begin
100 100 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
101 101 rescue OpenIdAuthentication::InvalidOpenId
102 102 # Invlaid url, don't save
103 103 end
104 104 end
105 105 self.read_attribute(:identity_url)
106 106 end
107 107
108 108 # Returns the user that matches provided login and password, or nil
109 109 def self.try_to_login(login, password)
110 110 # Make sure no one can sign in with an empty password
111 111 return nil if password.to_s.empty?
112 112 user = find_by_login(login)
113 113 if user
114 114 # user is already in local database
115 115 return nil if !user.active?
116 116 if user.auth_source
117 117 # user has an external authentication method
118 118 return nil unless user.auth_source.authenticate(login, password)
119 119 else
120 120 # authentication with local password
121 121 return nil unless User.hash_password(password) == user.hashed_password
122 122 end
123 123 else
124 124 # user is not yet registered, try to authenticate with available sources
125 125 attrs = AuthSource.authenticate(login, password)
126 126 if attrs
127 127 user = new(attrs)
128 128 user.login = login
129 129 user.language = Setting.default_language
130 130 if user.save
131 131 user.reload
132 132 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
133 133 end
134 134 end
135 135 end
136 136 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
137 137 user
138 138 rescue => text
139 139 raise text
140 140 end
141 141
142 142 # Returns the user who matches the given autologin +key+ or nil
143 143 def self.try_to_autologin(key)
144 144 tokens = Token.find_all_by_action_and_value('autologin', key)
145 145 # Make sure there's only 1 token that matches the key
146 146 if tokens.size == 1
147 147 token = tokens.first
148 148 if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
149 149 token.user.update_attribute(:last_login_on, Time.now)
150 150 token.user
151 151 end
152 152 end
153 153 end
154 154
155 155 # Return user's full name for display
156 156 def name(formatter = nil)
157 157 if formatter
158 158 eval('"' + (USER_FORMATS[formatter] || USER_FORMATS[:firstname_lastname]) + '"')
159 159 else
160 160 @name ||= eval('"' + (USER_FORMATS[Setting.user_format] || USER_FORMATS[:firstname_lastname]) + '"')
161 161 end
162 162 end
163 163
164 164 def active?
165 165 self.status == STATUS_ACTIVE
166 166 end
167 167
168 168 def registered?
169 169 self.status == STATUS_REGISTERED
170 170 end
171 171
172 172 def locked?
173 173 self.status == STATUS_LOCKED
174 174 end
175 175
176 176 def activate
177 177 self.status = STATUS_ACTIVE
178 178 end
179 179
180 180 def register
181 181 self.status = STATUS_REGISTERED
182 182 end
183 183
184 184 def lock
185 185 self.status = STATUS_LOCKED
186 186 end
187 187
188 188 def activate!
189 189 update_attribute(:status, STATUS_ACTIVE)
190 190 end
191 191
192 192 def register!
193 193 update_attribute(:status, STATUS_REGISTERED)
194 194 end
195 195
196 196 def lock!
197 197 update_attribute(:status, STATUS_LOCKED)
198 198 end
199 199
200 200 def check_password?(clear_password)
201 201 if auth_source_id.present?
202 202 auth_source.authenticate(self.login, clear_password)
203 203 else
204 204 User.hash_password(clear_password) == self.hashed_password
205 205 end
206 206 end
207 207
208 208 # Does the backend storage allow this user to change their password?
209 209 def change_password_allowed?
210 210 return true if auth_source_id.blank?
211 211 return auth_source.allow_password_changes?
212 212 end
213 213
214 214 # Generate and set a random password. Useful for automated user creation
215 215 # Based on Token#generate_token_value
216 216 #
217 217 def random_password
218 218 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
219 219 password = ''
220 220 40.times { |i| password << chars[rand(chars.size-1)] }
221 221 self.password = password
222 222 self.password_confirmation = password
223 223 self
224 224 end
225 225
226 226 def pref
227 227 self.preference ||= UserPreference.new(:user => self)
228 228 end
229 229
230 230 def time_zone
231 231 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
232 232 end
233 233
234 234 def wants_comments_in_reverse_order?
235 235 self.pref[:comments_sorting] == 'desc'
236 236 end
237 237
238 238 # Return user's RSS key (a 40 chars long string), used to access feeds
239 239 def rss_key
240 240 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
241 241 token.value
242 242 end
243 243
244 244 # Return user's API key (a 40 chars long string), used to access the API
245 245 def api_key
246 246 token = self.api_token || self.create_api_token(:action => 'api')
247 247 token.value
248 248 end
249 249
250 250 # Return an array of project ids for which the user has explicitly turned mail notifications on
251 251 def notified_projects_ids
252 252 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
253 253 end
254 254
255 255 def notified_project_ids=(ids)
256 256 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
257 257 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
258 258 @notified_projects_ids = nil
259 259 notified_projects_ids
260 260 end
261 261
262 # Only users that belong to more than 1 project can select projects for which they are notified
263 def valid_notification_options
264 # Note that @user.membership.size would fail since AR ignores
265 # :include association option when doing a count
266 if memberships.length < 1
267 MAIL_NOTIFICATION_OPTIONS.delete_if {|option| option.first == :selected}
268 else
269 MAIL_NOTIFICATION_OPTIONS
270 end
271 end
272
262 273 # Find a user account by matching the exact login and then a case-insensitive
263 274 # version. Exact matches will be given priority.
264 275 def self.find_by_login(login)
265 276 # force string comparison to be case sensitive on MySQL
266 277 type_cast = (ActiveRecord::Base.connection.adapter_name == 'MySQL') ? 'BINARY' : ''
267 278
268 279 # First look for an exact match
269 280 user = first(:conditions => ["#{type_cast} login = ?", login])
270 281 # Fail over to case-insensitive if none was found
271 282 user ||= first(:conditions => ["#{type_cast} LOWER(login) = ?", login.to_s.downcase])
272 283 end
273 284
274 285 def self.find_by_rss_key(key)
275 286 token = Token.find_by_value(key)
276 287 token && token.user.active? ? token.user : nil
277 288 end
278 289
279 290 def self.find_by_api_key(key)
280 291 token = Token.find_by_action_and_value('api', key)
281 292 token && token.user.active? ? token.user : nil
282 293 end
283 294
284 295 # Makes find_by_mail case-insensitive
285 296 def self.find_by_mail(mail)
286 297 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
287 298 end
288 299
289 300 def to_s
290 301 name
291 302 end
292 303
293 304 # Returns the current day according to user's time zone
294 305 def today
295 306 if time_zone.nil?
296 307 Date.today
297 308 else
298 309 Time.now.in_time_zone(time_zone).to_date
299 310 end
300 311 end
301 312
302 313 def logged?
303 314 true
304 315 end
305 316
306 317 def anonymous?
307 318 !logged?
308 319 end
309 320
310 321 # Return user's roles for project
311 322 def roles_for_project(project)
312 323 roles = []
313 324 # No role on archived projects
314 325 return roles unless project && project.active?
315 326 if logged?
316 327 # Find project membership
317 328 membership = memberships.detect {|m| m.project_id == project.id}
318 329 if membership
319 330 roles = membership.roles
320 331 else
321 332 @role_non_member ||= Role.non_member
322 333 roles << @role_non_member
323 334 end
324 335 else
325 336 @role_anonymous ||= Role.anonymous
326 337 roles << @role_anonymous
327 338 end
328 339 roles
329 340 end
330 341
331 342 # Return true if the user is a member of project
332 343 def member_of?(project)
333 344 !roles_for_project(project).detect {|role| role.member?}.nil?
334 345 end
335 346
336 347 # Return true if the user is allowed to do the specified action on project
337 348 # action can be:
338 349 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
339 350 # * a permission Symbol (eg. :edit_project)
340 351 def allowed_to?(action, project, options={})
341 352 if project
342 353 # No action allowed on archived projects
343 354 return false unless project.active?
344 355 # No action allowed on disabled modules
345 356 return false unless project.allows_to?(action)
346 357 # Admin users are authorized for anything else
347 358 return true if admin?
348 359
349 360 roles = roles_for_project(project)
350 361 return false unless roles
351 362 roles.detect {|role| (project.is_public? || role.member?) && role.allowed_to?(action)}
352 363
353 364 elsif options[:global]
354 365 # Admin users are always authorized
355 366 return true if admin?
356 367
357 368 # authorize if user has at least one role that has this permission
358 369 roles = memberships.collect {|m| m.roles}.flatten.uniq
359 370 roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
360 371 else
361 372 false
362 373 end
363 374 end
364 375
365 376 # Is the user allowed to do the specified action on any project?
366 377 # See allowed_to? for the actions and valid options.
367 378 def allowed_to_globally?(action, options)
368 379 allowed_to?(action, nil, options.reverse_merge(:global => true))
369 380 end
370 381
371 382 # Utility method to help check if a user should be notified about an
372 383 # event.
373 384 #
374 385 # TODO: only supports Issue events currently
375 386 def notify_about?(object)
376 387 case mail_notification.to_sym
377 388 when :all
378 389 true
379 390 when :selected
380 391 # Handled by the Project
381 392 when :none
382 393 false
383 394 when :only_my_events
384 395 if object.is_a?(Issue) && (object.author == self || object.assigned_to == self)
385 396 true
386 397 else
387 398 false
388 399 end
389 400 when :only_assigned
390 401 if object.is_a?(Issue) && object.assigned_to == self
391 402 true
392 403 else
393 404 false
394 405 end
395 406 when :only_owner
396 407 if object.is_a?(Issue) && object.author == self
397 408 true
398 409 else
399 410 false
400 411 end
401 412 else
402 413 false
403 414 end
404 415 end
405 416
406 417 def self.current=(user)
407 418 @current_user = user
408 419 end
409 420
410 421 def self.current
411 422 @current_user ||= User.anonymous
412 423 end
413 424
414 425 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
415 426 # one anonymous user per database.
416 427 def self.anonymous
417 428 anonymous_user = AnonymousUser.find(:first)
418 429 if anonymous_user.nil?
419 430 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
420 431 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
421 432 end
422 433 anonymous_user
423 434 end
424 435
425 436 protected
426 437
427 438 def validate
428 439 # Password length validation based on setting
429 440 if !password.nil? && password.size < Setting.password_min_length.to_i
430 441 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
431 442 end
432 443 end
433 444
434 445 private
435 446
436 447 # Return password digest
437 448 def self.hash_password(clear_password)
438 449 Digest::SHA1.hexdigest(clear_password || "")
439 450 end
440 451 end
441 452
442 453 class AnonymousUser < User
443 454
444 455 def validate_on_create
445 456 # There should be only one AnonymousUser in the database
446 457 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
447 458 end
448 459
449 460 def available_custom_fields
450 461 []
451 462 end
452 463
453 464 # Overrides a few properties
454 465 def logged?; false end
455 466 def admin; false end
456 467 def name(*args); I18n.t(:label_user_anonymous) end
457 468 def mail; nil end
458 469 def time_zone; nil end
459 470 def rss_key; nil end
460 471 end
General Comments 0
You need to be logged in to leave comments. Login now