##// END OF EJS Templates
Merged r9798 to r9801 from trunk....
Jean-Philippe Lang -
r9620:fe1a152e02b4
parent child
Show More
@@ -1,524 +1,524
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'uri'
18 require 'uri'
19 require 'cgi'
19 require 'cgi'
20
20
21 class Unauthorized < Exception; end
21 class Unauthorized < Exception; end
22
22
23 class ApplicationController < ActionController::Base
23 class ApplicationController < ActionController::Base
24 include Redmine::I18n
24 include Redmine::I18n
25
25
26 layout 'base'
26 layout 'base'
27 exempt_from_layout 'builder', 'rsb'
27 exempt_from_layout 'builder', 'rsb'
28
28
29 protect_from_forgery
29 protect_from_forgery
30 def handle_unverified_request
30 def handle_unverified_request
31 super
31 super
32 cookies.delete(:autologin)
32 cookies.delete(:autologin)
33 end
33 end
34
34
35 # FIXME: Remove this when all of Rack and Rails have learned how to
35 # FIXME: Remove this when all of Rack and Rails have learned how to
36 # properly use encodings
36 # properly use encodings
37 before_filter :params_filter
37 before_filter :params_filter
38
38
39 def params_filter
39 def params_filter
40 if RUBY_VERSION >= '1.9' && defined?(Rails) && Rails::VERSION::MAJOR < 3
40 if RUBY_VERSION >= '1.9' && defined?(Rails) && Rails::VERSION::MAJOR < 3
41 self.utf8nize!(params)
41 self.utf8nize!(params)
42 end
42 end
43 end
43 end
44
44
45 def utf8nize!(obj)
45 def utf8nize!(obj)
46 if obj.frozen?
46 if obj.frozen?
47 obj
47 obj
48 elsif obj.is_a? String
48 elsif obj.is_a? String
49 obj.respond_to?(:force_encoding) ? obj.force_encoding("UTF-8") : obj
49 obj.respond_to?(:force_encoding) ? obj.force_encoding("UTF-8") : obj
50 elsif obj.is_a? Hash
50 elsif obj.is_a? Hash
51 obj.each {|k, v| obj[k] = self.utf8nize!(v)}
51 obj.each {|k, v| obj[k] = self.utf8nize!(v)}
52 elsif obj.is_a? Array
52 elsif obj.is_a? Array
53 obj.each {|v| self.utf8nize!(v)}
53 obj.each {|v| self.utf8nize!(v)}
54 else
54 else
55 obj
55 obj
56 end
56 end
57 end
57 end
58
58
59 before_filter :user_setup, :check_if_login_required, :set_localization
59 before_filter :user_setup, :check_if_login_required, :set_localization
60 filter_parameter_logging :password
60 filter_parameter_logging :password
61
61
62 rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
62 rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
63 rescue_from ::Unauthorized, :with => :deny_access
63 rescue_from ::Unauthorized, :with => :deny_access
64
64
65 include Redmine::Search::Controller
65 include Redmine::Search::Controller
66 include Redmine::MenuManager::MenuController
66 include Redmine::MenuManager::MenuController
67 helper Redmine::MenuManager::MenuHelper
67 helper Redmine::MenuManager::MenuHelper
68
68
69 Redmine::Scm::Base.all.each do |scm|
69 Redmine::Scm::Base.all.each do |scm|
70 require_dependency "repository/#{scm.underscore}"
70 require_dependency "repository/#{scm.underscore}"
71 end
71 end
72
72
73 def user_setup
73 def user_setup
74 # Check the settings cache for each request
74 # Check the settings cache for each request
75 Setting.check_cache
75 Setting.check_cache
76 # Find the current user
76 # Find the current user
77 User.current = find_current_user
77 User.current = find_current_user
78 end
78 end
79
79
80 # Returns the current user or nil if no user is logged in
80 # Returns the current user or nil if no user is logged in
81 # and starts a session if needed
81 # and starts a session if needed
82 def find_current_user
82 def find_current_user
83 if session[:user_id]
83 if session[:user_id]
84 # existing session
84 # existing session
85 (User.active.find(session[:user_id]) rescue nil)
85 (User.active.find(session[:user_id]) rescue nil)
86 elsif cookies[:autologin] && Setting.autologin?
86 elsif cookies[:autologin] && Setting.autologin?
87 # auto-login feature starts a new session
87 # auto-login feature starts a new session
88 user = User.try_to_autologin(cookies[:autologin])
88 user = User.try_to_autologin(cookies[:autologin])
89 session[:user_id] = user.id if user
89 session[:user_id] = user.id if user
90 user
90 user
91 elsif params[:format] == 'atom' && params[:key] && request.get? && accept_rss_auth?
91 elsif params[:format] == 'atom' && params[:key] && request.get? && accept_rss_auth?
92 # RSS key authentication does not start a session
92 # RSS key authentication does not start a session
93 User.find_by_rss_key(params[:key])
93 User.find_by_rss_key(params[:key])
94 elsif Setting.rest_api_enabled? && accept_api_auth?
94 elsif Setting.rest_api_enabled? && accept_api_auth?
95 if (key = api_key_from_request)
95 if (key = api_key_from_request)
96 # Use API key
96 # Use API key
97 User.find_by_api_key(key)
97 User.find_by_api_key(key)
98 else
98 else
99 # HTTP Basic, either username/password or API key/random
99 # HTTP Basic, either username/password or API key/random
100 authenticate_with_http_basic do |username, password|
100 authenticate_with_http_basic do |username, password|
101 User.try_to_login(username, password) || User.find_by_api_key(username)
101 User.try_to_login(username, password) || User.find_by_api_key(username)
102 end
102 end
103 end
103 end
104 end
104 end
105 end
105 end
106
106
107 # Sets the logged in user
107 # Sets the logged in user
108 def logged_user=(user)
108 def logged_user=(user)
109 reset_session
109 reset_session
110 if user && user.is_a?(User)
110 if user && user.is_a?(User)
111 User.current = user
111 User.current = user
112 session[:user_id] = user.id
112 session[:user_id] = user.id
113 else
113 else
114 User.current = User.anonymous
114 User.current = User.anonymous
115 end
115 end
116 end
116 end
117
117
118 # Logs out current user
118 # Logs out current user
119 def logout_user
119 def logout_user
120 if User.current.logged?
120 if User.current.logged?
121 cookies.delete :autologin
121 cookies.delete :autologin
122 Token.delete_all(["user_id = ? AND action = ?", User.current.id, 'autologin'])
122 Token.delete_all(["user_id = ? AND action = ?", User.current.id, 'autologin'])
123 self.logged_user = nil
123 self.logged_user = nil
124 end
124 end
125 end
125 end
126
126
127 # check if login is globally required to access the application
127 # check if login is globally required to access the application
128 def check_if_login_required
128 def check_if_login_required
129 # no check needed if user is already logged in
129 # no check needed if user is already logged in
130 return true if User.current.logged?
130 return true if User.current.logged?
131 require_login if Setting.login_required?
131 require_login if Setting.login_required?
132 end
132 end
133
133
134 def set_localization
134 def set_localization
135 lang = nil
135 lang = nil
136 if User.current.logged?
136 if User.current.logged?
137 lang = find_language(User.current.language)
137 lang = find_language(User.current.language)
138 end
138 end
139 if lang.nil? && request.env['HTTP_ACCEPT_LANGUAGE']
139 if lang.nil? && request.env['HTTP_ACCEPT_LANGUAGE']
140 accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first
140 accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first
141 if !accept_lang.blank?
141 if !accept_lang.blank?
142 accept_lang = accept_lang.downcase
142 accept_lang = accept_lang.downcase
143 lang = find_language(accept_lang) || find_language(accept_lang.split('-').first)
143 lang = find_language(accept_lang) || find_language(accept_lang.split('-').first)
144 end
144 end
145 end
145 end
146 lang ||= Setting.default_language
146 lang ||= Setting.default_language
147 set_language_if_valid(lang)
147 set_language_if_valid(lang)
148 end
148 end
149
149
150 def require_login
150 def require_login
151 if !User.current.logged?
151 if !User.current.logged?
152 # Extract only the basic url parameters on non-GET requests
152 # Extract only the basic url parameters on non-GET requests
153 if request.get?
153 if request.get?
154 url = url_for(params)
154 url = url_for(params)
155 else
155 else
156 url = url_for(:controller => params[:controller], :action => params[:action], :id => params[:id], :project_id => params[:project_id])
156 url = url_for(:controller => params[:controller], :action => params[:action], :id => params[:id], :project_id => params[:project_id])
157 end
157 end
158 respond_to do |format|
158 respond_to do |format|
159 format.html { redirect_to :controller => "account", :action => "login", :back_url => url }
159 format.html { redirect_to :controller => "account", :action => "login", :back_url => url }
160 format.atom { redirect_to :controller => "account", :action => "login", :back_url => url }
160 format.atom { redirect_to :controller => "account", :action => "login", :back_url => url }
161 format.xml { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
161 format.xml { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
162 format.js { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
162 format.js { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
163 format.json { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
163 format.json { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
164 end
164 end
165 return false
165 return false
166 end
166 end
167 true
167 true
168 end
168 end
169
169
170 def require_admin
170 def require_admin
171 return unless require_login
171 return unless require_login
172 if !User.current.admin?
172 if !User.current.admin?
173 render_403
173 render_403
174 return false
174 return false
175 end
175 end
176 true
176 true
177 end
177 end
178
178
179 def deny_access
179 def deny_access
180 User.current.logged? ? render_403 : require_login
180 User.current.logged? ? render_403 : require_login
181 end
181 end
182
182
183 # Authorize the user for the requested action
183 # Authorize the user for the requested action
184 def authorize(ctrl = params[:controller], action = params[:action], global = false)
184 def authorize(ctrl = params[:controller], action = params[:action], global = false)
185 allowed = User.current.allowed_to?({:controller => ctrl, :action => action}, @project || @projects, :global => global)
185 allowed = User.current.allowed_to?({:controller => ctrl, :action => action}, @project || @projects, :global => global)
186 if allowed
186 if allowed
187 true
187 true
188 else
188 else
189 if @project && @project.archived?
189 if @project && @project.archived?
190 render_403 :message => :notice_not_authorized_archived_project
190 render_403 :message => :notice_not_authorized_archived_project
191 else
191 else
192 deny_access
192 deny_access
193 end
193 end
194 end
194 end
195 end
195 end
196
196
197 # Authorize the user for the requested action outside a project
197 # Authorize the user for the requested action outside a project
198 def authorize_global(ctrl = params[:controller], action = params[:action], global = true)
198 def authorize_global(ctrl = params[:controller], action = params[:action], global = true)
199 authorize(ctrl, action, global)
199 authorize(ctrl, action, global)
200 end
200 end
201
201
202 # Find project of id params[:id]
202 # Find project of id params[:id]
203 def find_project
203 def find_project
204 @project = Project.find(params[:id])
204 @project = Project.find(params[:id])
205 rescue ActiveRecord::RecordNotFound
205 rescue ActiveRecord::RecordNotFound
206 render_404
206 render_404
207 end
207 end
208
208
209 # Find project of id params[:project_id]
209 # Find project of id params[:project_id]
210 def find_project_by_project_id
210 def find_project_by_project_id
211 @project = Project.find(params[:project_id])
211 @project = Project.find(params[:project_id])
212 rescue ActiveRecord::RecordNotFound
212 rescue ActiveRecord::RecordNotFound
213 render_404
213 render_404
214 end
214 end
215
215
216 # Find a project based on params[:project_id]
216 # Find a project based on params[:project_id]
217 # TODO: some subclasses override this, see about merging their logic
217 # TODO: some subclasses override this, see about merging their logic
218 def find_optional_project
218 def find_optional_project
219 @project = Project.find(params[:project_id]) unless params[:project_id].blank?
219 @project = Project.find(params[:project_id]) unless params[:project_id].blank?
220 allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
220 allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
221 allowed ? true : deny_access
221 allowed ? true : deny_access
222 rescue ActiveRecord::RecordNotFound
222 rescue ActiveRecord::RecordNotFound
223 render_404
223 render_404
224 end
224 end
225
225
226 # Finds and sets @project based on @object.project
226 # Finds and sets @project based on @object.project
227 def find_project_from_association
227 def find_project_from_association
228 render_404 unless @object.present?
228 render_404 unless @object.present?
229
229
230 @project = @object.project
230 @project = @object.project
231 end
231 end
232
232
233 def find_model_object
233 def find_model_object
234 model = self.class.read_inheritable_attribute('model_object')
234 model = self.class.read_inheritable_attribute('model_object')
235 if model
235 if model
236 @object = model.find(params[:id])
236 @object = model.find(params[:id])
237 self.instance_variable_set('@' + controller_name.singularize, @object) if @object
237 self.instance_variable_set('@' + controller_name.singularize, @object) if @object
238 end
238 end
239 rescue ActiveRecord::RecordNotFound
239 rescue ActiveRecord::RecordNotFound
240 render_404
240 render_404
241 end
241 end
242
242
243 def self.model_object(model)
243 def self.model_object(model)
244 write_inheritable_attribute('model_object', model)
244 write_inheritable_attribute('model_object', model)
245 end
245 end
246
246
247 # Filter for bulk issue operations
247 # Filter for bulk issue operations
248 def find_issues
248 def find_issues
249 @issues = Issue.find_all_by_id(params[:id] || params[:ids])
249 @issues = Issue.find_all_by_id(params[:id] || params[:ids])
250 raise ActiveRecord::RecordNotFound if @issues.empty?
250 raise ActiveRecord::RecordNotFound if @issues.empty?
251 if @issues.detect {|issue| !issue.visible?}
251 if @issues.detect {|issue| !issue.visible?}
252 deny_access
252 deny_access
253 return
253 return
254 end
254 end
255 @projects = @issues.collect(&:project).compact.uniq
255 @projects = @issues.collect(&:project).compact.uniq
256 @project = @projects.first if @projects.size == 1
256 @project = @projects.first if @projects.size == 1
257 rescue ActiveRecord::RecordNotFound
257 rescue ActiveRecord::RecordNotFound
258 render_404
258 render_404
259 end
259 end
260
260
261 # make sure that the user is a member of the project (or admin) if project is private
261 # make sure that the user is a member of the project (or admin) if project is private
262 # used as a before_filter for actions that do not require any particular permission on the project
262 # used as a before_filter for actions that do not require any particular permission on the project
263 def check_project_privacy
263 def check_project_privacy
264 if @project && @project.active?
264 if @project && @project.active?
265 if @project.visible?
265 if @project.visible?
266 true
266 true
267 else
267 else
268 deny_access
268 deny_access
269 end
269 end
270 else
270 else
271 @project = nil
271 @project = nil
272 render_404
272 render_404
273 false
273 false
274 end
274 end
275 end
275 end
276
276
277 def back_url
277 def back_url
278 params[:back_url] || request.env['HTTP_REFERER']
278 params[:back_url] || request.env['HTTP_REFERER']
279 end
279 end
280
280
281 def redirect_back_or_default(default)
281 def redirect_back_or_default(default)
282 back_url = CGI.unescape(params[:back_url].to_s)
282 back_url = CGI.unescape(params[:back_url].to_s)
283 if !back_url.blank?
283 if !back_url.blank?
284 begin
284 begin
285 uri = URI.parse(back_url)
285 uri = URI.parse(back_url)
286 # do not redirect user to another host or to the login or register page
286 # do not redirect user to another host or to the login or register page
287 if (uri.relative? || (uri.host == request.host)) && !uri.path.match(%r{/(login|account/register)})
287 if (uri.relative? || (uri.host == request.host)) && !uri.path.match(%r{/(login|account/register)})
288 redirect_to(back_url)
288 redirect_to(back_url)
289 return
289 return
290 end
290 end
291 rescue URI::InvalidURIError
291 rescue URI::InvalidURIError
292 # redirect to default
292 # redirect to default
293 end
293 end
294 end
294 end
295 redirect_to default
295 redirect_to default
296 false
296 false
297 end
297 end
298
298
299 def render_403(options={})
299 def render_403(options={})
300 @project = nil
300 @project = nil
301 render_error({:message => :notice_not_authorized, :status => 403}.merge(options))
301 render_error({:message => :notice_not_authorized, :status => 403}.merge(options))
302 return false
302 return false
303 end
303 end
304
304
305 def render_404(options={})
305 def render_404(options={})
306 render_error({:message => :notice_file_not_found, :status => 404}.merge(options))
306 render_error({:message => :notice_file_not_found, :status => 404}.merge(options))
307 return false
307 return false
308 end
308 end
309
309
310 # Renders an error response
310 # Renders an error response
311 def render_error(arg)
311 def render_error(arg)
312 arg = {:message => arg} unless arg.is_a?(Hash)
312 arg = {:message => arg} unless arg.is_a?(Hash)
313
313
314 @message = arg[:message]
314 @message = arg[:message]
315 @message = l(@message) if @message.is_a?(Symbol)
315 @message = l(@message) if @message.is_a?(Symbol)
316 @status = arg[:status] || 500
316 @status = arg[:status] || 500
317
317
318 respond_to do |format|
318 respond_to do |format|
319 format.html {
319 format.html {
320 render :template => 'common/error', :layout => use_layout, :status => @status
320 render :template => 'common/error', :layout => use_layout, :status => @status
321 }
321 }
322 format.atom { head @status }
322 format.atom { head @status }
323 format.xml { head @status }
323 format.xml { head @status }
324 format.js { head @status }
324 format.js { head @status }
325 format.json { head @status }
325 format.json { head @status }
326 end
326 end
327 end
327 end
328
328
329 # Filter for actions that provide an API response
329 # Filter for actions that provide an API response
330 # but have no HTML representation for non admin users
330 # but have no HTML representation for non admin users
331 def require_admin_or_api_request
331 def require_admin_or_api_request
332 return true if api_request?
332 return true if api_request?
333 if User.current.admin?
333 if User.current.admin?
334 true
334 true
335 elsif User.current.logged?
335 elsif User.current.logged?
336 render_error(:status => 406)
336 render_error(:status => 406)
337 else
337 else
338 deny_access
338 deny_access
339 end
339 end
340 end
340 end
341
341
342 # Picks which layout to use based on the request
342 # Picks which layout to use based on the request
343 #
343 #
344 # @return [boolean, string] name of the layout to use or false for no layout
344 # @return [boolean, string] name of the layout to use or false for no layout
345 def use_layout
345 def use_layout
346 request.xhr? ? false : 'base'
346 request.xhr? ? false : 'base'
347 end
347 end
348
348
349 def invalid_authenticity_token
349 def invalid_authenticity_token
350 if api_request?
350 if api_request?
351 logger.error "Form authenticity token is missing or is invalid. API calls must include a proper Content-type header (text/xml or text/json)."
351 logger.error "Form authenticity token is missing or is invalid. API calls must include a proper Content-type header (text/xml or text/json)."
352 end
352 end
353 render_error "Invalid form authenticity token."
353 render_error "Invalid form authenticity token."
354 end
354 end
355
355
356 def render_feed(items, options={})
356 def render_feed(items, options={})
357 @items = items || []
357 @items = items || []
358 @items.sort! {|x,y| y.event_datetime <=> x.event_datetime }
358 @items.sort! {|x,y| y.event_datetime <=> x.event_datetime }
359 @items = @items.slice(0, Setting.feeds_limit.to_i)
359 @items = @items.slice(0, Setting.feeds_limit.to_i)
360 @title = options[:title] || Setting.app_title
360 @title = options[:title] || Setting.app_title
361 render :template => "common/feed.atom", :layout => false,
361 render :template => "common/feed.atom", :layout => false,
362 :content_type => 'application/atom+xml'
362 :content_type => 'application/atom+xml'
363 end
363 end
364
364
365 def self.accept_rss_auth(*actions)
365 def self.accept_rss_auth(*actions)
366 if actions.any?
366 if actions.any?
367 write_inheritable_attribute('accept_rss_auth_actions', actions)
367 write_inheritable_attribute('accept_rss_auth_actions', actions)
368 else
368 else
369 read_inheritable_attribute('accept_rss_auth_actions') || []
369 read_inheritable_attribute('accept_rss_auth_actions') || []
370 end
370 end
371 end
371 end
372
372
373 def accept_rss_auth?(action=action_name)
373 def accept_rss_auth?(action=action_name)
374 self.class.accept_rss_auth.include?(action.to_sym)
374 self.class.accept_rss_auth.include?(action.to_sym)
375 end
375 end
376
376
377 def self.accept_api_auth(*actions)
377 def self.accept_api_auth(*actions)
378 if actions.any?
378 if actions.any?
379 write_inheritable_attribute('accept_api_auth_actions', actions)
379 write_inheritable_attribute('accept_api_auth_actions', actions)
380 else
380 else
381 read_inheritable_attribute('accept_api_auth_actions') || []
381 read_inheritable_attribute('accept_api_auth_actions') || []
382 end
382 end
383 end
383 end
384
384
385 def accept_api_auth?(action=action_name)
385 def accept_api_auth?(action=action_name)
386 self.class.accept_api_auth.include?(action.to_sym)
386 self.class.accept_api_auth.include?(action.to_sym)
387 end
387 end
388
388
389 # Returns the number of objects that should be displayed
389 # Returns the number of objects that should be displayed
390 # on the paginated list
390 # on the paginated list
391 def per_page_option
391 def per_page_option
392 per_page = nil
392 per_page = nil
393 if params[:per_page] && Setting.per_page_options_array.include?(params[:per_page].to_s.to_i)
393 if params[:per_page] && Setting.per_page_options_array.include?(params[:per_page].to_s.to_i)
394 per_page = params[:per_page].to_s.to_i
394 per_page = params[:per_page].to_s.to_i
395 session[:per_page] = per_page
395 session[:per_page] = per_page
396 elsif session[:per_page]
396 elsif session[:per_page]
397 per_page = session[:per_page]
397 per_page = session[:per_page]
398 else
398 else
399 per_page = Setting.per_page_options_array.first || 25
399 per_page = Setting.per_page_options_array.first || 25
400 end
400 end
401 per_page
401 per_page
402 end
402 end
403
403
404 # Returns offset and limit used to retrieve objects
404 # Returns offset and limit used to retrieve objects
405 # for an API response based on offset, limit and page parameters
405 # for an API response based on offset, limit and page parameters
406 def api_offset_and_limit(options=params)
406 def api_offset_and_limit(options=params)
407 if options[:offset].present?
407 if options[:offset].present?
408 offset = options[:offset].to_i
408 offset = options[:offset].to_i
409 if offset < 0
409 if offset < 0
410 offset = 0
410 offset = 0
411 end
411 end
412 end
412 end
413 limit = options[:limit].to_i
413 limit = options[:limit].to_i
414 if limit < 1
414 if limit < 1
415 limit = 25
415 limit = 25
416 elsif limit > 100
416 elsif limit > 100
417 limit = 100
417 limit = 100
418 end
418 end
419 if offset.nil? && options[:page].present?
419 if offset.nil? && options[:page].present?
420 offset = (options[:page].to_i - 1) * limit
420 offset = (options[:page].to_i - 1) * limit
421 offset = 0 if offset < 0
421 offset = 0 if offset < 0
422 end
422 end
423 offset ||= 0
423 offset ||= 0
424
424
425 [offset, limit]
425 [offset, limit]
426 end
426 end
427
427
428 # qvalues http header parser
428 # qvalues http header parser
429 # code taken from webrick
429 # code taken from webrick
430 def parse_qvalues(value)
430 def parse_qvalues(value)
431 tmp = []
431 tmp = []
432 if value
432 if value
433 parts = value.split(/,\s*/)
433 parts = value.split(/,\s*/)
434 parts.each {|part|
434 parts.each {|part|
435 if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
435 if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
436 val = m[1]
436 val = m[1]
437 q = (m[2] or 1).to_f
437 q = (m[2] or 1).to_f
438 tmp.push([val, q])
438 tmp.push([val, q])
439 end
439 end
440 }
440 }
441 tmp = tmp.sort_by{|val, q| -q}
441 tmp = tmp.sort_by{|val, q| -q}
442 tmp.collect!{|val, q| val}
442 tmp.collect!{|val, q| val}
443 end
443 end
444 return tmp
444 return tmp
445 rescue
445 rescue
446 nil
446 nil
447 end
447 end
448
448
449 # Returns a string that can be used as filename value in Content-Disposition header
449 # Returns a string that can be used as filename value in Content-Disposition header
450 def filename_for_content_disposition(name)
450 def filename_for_content_disposition(name)
451 request.env['HTTP_USER_AGENT'] =~ %r{MSIE} ? ERB::Util.url_encode(name) : name
451 request.env['HTTP_USER_AGENT'] =~ %r{MSIE} ? ERB::Util.url_encode(name) : name
452 end
452 end
453
453
454 def api_request?
454 def api_request?
455 %w(xml json).include? params[:format]
455 %w(xml json).include? params[:format]
456 end
456 end
457
457
458 # Returns the API key present in the request
458 # Returns the API key present in the request
459 def api_key_from_request
459 def api_key_from_request
460 if params[:key].present?
460 if params[:key].present?
461 params[:key]
461 params[:key].to_s
462 elsif request.headers["X-Redmine-API-Key"].present?
462 elsif request.headers["X-Redmine-API-Key"].present?
463 request.headers["X-Redmine-API-Key"]
463 request.headers["X-Redmine-API-Key"].to_s
464 end
464 end
465 end
465 end
466
466
467 # Renders a warning flash if obj has unsaved attachments
467 # Renders a warning flash if obj has unsaved attachments
468 def render_attachment_warning_if_needed(obj)
468 def render_attachment_warning_if_needed(obj)
469 flash[:warning] = l(:warning_attachments_not_saved, obj.unsaved_attachments.size) if obj.unsaved_attachments.present?
469 flash[:warning] = l(:warning_attachments_not_saved, obj.unsaved_attachments.size) if obj.unsaved_attachments.present?
470 end
470 end
471
471
472 # Sets the `flash` notice or error based the number of issues that did not save
472 # Sets the `flash` notice or error based the number of issues that did not save
473 #
473 #
474 # @param [Array, Issue] issues all of the saved and unsaved Issues
474 # @param [Array, Issue] issues all of the saved and unsaved Issues
475 # @param [Array, Integer] unsaved_issue_ids the issue ids that were not saved
475 # @param [Array, Integer] unsaved_issue_ids the issue ids that were not saved
476 def set_flash_from_bulk_issue_save(issues, unsaved_issue_ids)
476 def set_flash_from_bulk_issue_save(issues, unsaved_issue_ids)
477 if unsaved_issue_ids.empty?
477 if unsaved_issue_ids.empty?
478 flash[:notice] = l(:notice_successful_update) unless issues.empty?
478 flash[:notice] = l(:notice_successful_update) unless issues.empty?
479 else
479 else
480 flash[:error] = l(:notice_failed_to_save_issues,
480 flash[:error] = l(:notice_failed_to_save_issues,
481 :count => unsaved_issue_ids.size,
481 :count => unsaved_issue_ids.size,
482 :total => issues.size,
482 :total => issues.size,
483 :ids => '#' + unsaved_issue_ids.join(', #'))
483 :ids => '#' + unsaved_issue_ids.join(', #'))
484 end
484 end
485 end
485 end
486
486
487 # Rescues an invalid query statement. Just in case...
487 # Rescues an invalid query statement. Just in case...
488 def query_statement_invalid(exception)
488 def query_statement_invalid(exception)
489 logger.error "Query::StatementInvalid: #{exception.message}" if logger
489 logger.error "Query::StatementInvalid: #{exception.message}" if logger
490 session.delete(:query)
490 session.delete(:query)
491 sort_clear if respond_to?(:sort_clear)
491 sort_clear if respond_to?(:sort_clear)
492 render_error "An error occurred while executing the query and has been logged. Please report this error to your Redmine administrator."
492 render_error "An error occurred while executing the query and has been logged. Please report this error to your Redmine administrator."
493 end
493 end
494
494
495 # Renders API response on validation failure
495 # Renders API response on validation failure
496 def render_validation_errors(objects)
496 def render_validation_errors(objects)
497 if objects.is_a?(Array)
497 if objects.is_a?(Array)
498 @error_messages = objects.map {|object| object.errors.full_messages}.flatten
498 @error_messages = objects.map {|object| object.errors.full_messages}.flatten
499 else
499 else
500 @error_messages = objects.errors.full_messages
500 @error_messages = objects.errors.full_messages
501 end
501 end
502 render :template => 'common/error_messages.api', :status => :unprocessable_entity, :layout => false
502 render :template => 'common/error_messages.api', :status => :unprocessable_entity, :layout => false
503 end
503 end
504
504
505 # Overrides #default_template so that the api template
505 # Overrides #default_template so that the api template
506 # is used automatically if it exists
506 # is used automatically if it exists
507 def default_template(action_name = self.action_name)
507 def default_template(action_name = self.action_name)
508 if api_request?
508 if api_request?
509 begin
509 begin
510 return self.view_paths.find_template(default_template_name(action_name), 'api')
510 return self.view_paths.find_template(default_template_name(action_name), 'api')
511 rescue ::ActionView::MissingTemplate
511 rescue ::ActionView::MissingTemplate
512 # the api template was not found
512 # the api template was not found
513 # fallback to the default behaviour
513 # fallback to the default behaviour
514 end
514 end
515 end
515 end
516 super
516 super
517 end
517 end
518
518
519 # Overrides #pick_layout so that #render with no arguments
519 # Overrides #pick_layout so that #render with no arguments
520 # doesn't use the layout for api requests
520 # doesn't use the layout for api requests
521 def pick_layout(*args)
521 def pick_layout(*args)
522 api_request? ? nil : super
522 api_request? ? nil : super
523 end
523 end
524 end
524 end
@@ -1,657 +1,660
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require "digest/sha1"
18 require "digest/sha1"
19
19
20 class User < Principal
20 class User < Principal
21 include Redmine::SafeAttributes
21 include Redmine::SafeAttributes
22
22
23 # Account statuses
23 # Account statuses
24 STATUS_ANONYMOUS = 0
24 STATUS_ANONYMOUS = 0
25 STATUS_ACTIVE = 1
25 STATUS_ACTIVE = 1
26 STATUS_REGISTERED = 2
26 STATUS_REGISTERED = 2
27 STATUS_LOCKED = 3
27 STATUS_LOCKED = 3
28
28
29 # Different ways of displaying/sorting users
29 # Different ways of displaying/sorting users
30 USER_FORMATS = {
30 USER_FORMATS = {
31 :firstname_lastname => {:string => '#{firstname} #{lastname}', :order => %w(firstname lastname id)},
31 :firstname_lastname => {:string => '#{firstname} #{lastname}', :order => %w(firstname lastname id)},
32 :firstname => {:string => '#{firstname}', :order => %w(firstname id)},
32 :firstname => {:string => '#{firstname}', :order => %w(firstname id)},
33 :lastname_firstname => {:string => '#{lastname} #{firstname}', :order => %w(lastname firstname id)},
33 :lastname_firstname => {:string => '#{lastname} #{firstname}', :order => %w(lastname firstname id)},
34 :lastname_coma_firstname => {:string => '#{lastname}, #{firstname}', :order => %w(lastname firstname id)},
34 :lastname_coma_firstname => {:string => '#{lastname}, #{firstname}', :order => %w(lastname firstname id)},
35 :username => {:string => '#{login}', :order => %w(login id)},
35 :username => {:string => '#{login}', :order => %w(login id)},
36 }
36 }
37
37
38 MAIL_NOTIFICATION_OPTIONS = [
38 MAIL_NOTIFICATION_OPTIONS = [
39 ['all', :label_user_mail_option_all],
39 ['all', :label_user_mail_option_all],
40 ['selected', :label_user_mail_option_selected],
40 ['selected', :label_user_mail_option_selected],
41 ['only_my_events', :label_user_mail_option_only_my_events],
41 ['only_my_events', :label_user_mail_option_only_my_events],
42 ['only_assigned', :label_user_mail_option_only_assigned],
42 ['only_assigned', :label_user_mail_option_only_assigned],
43 ['only_owner', :label_user_mail_option_only_owner],
43 ['only_owner', :label_user_mail_option_only_owner],
44 ['none', :label_user_mail_option_none]
44 ['none', :label_user_mail_option_none]
45 ]
45 ]
46
46
47 has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)},
47 has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)},
48 :after_remove => Proc.new {|user, group| group.user_removed(user)}
48 :after_remove => Proc.new {|user, group| group.user_removed(user)}
49 has_many :changesets, :dependent => :nullify
49 has_many :changesets, :dependent => :nullify
50 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
50 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
51 has_one :rss_token, :class_name => 'Token', :conditions => "action='feeds'"
51 has_one :rss_token, :class_name => 'Token', :conditions => "action='feeds'"
52 has_one :api_token, :class_name => 'Token', :conditions => "action='api'"
52 has_one :api_token, :class_name => 'Token', :conditions => "action='api'"
53 belongs_to :auth_source
53 belongs_to :auth_source
54
54
55 # Active non-anonymous users scope
55 # Active non-anonymous users scope
56 named_scope :active, :conditions => "#{User.table_name}.status = #{STATUS_ACTIVE}"
56 named_scope :active, :conditions => "#{User.table_name}.status = #{STATUS_ACTIVE}"
57 named_scope :logged, :conditions => "#{User.table_name}.status <> #{STATUS_ANONYMOUS}"
57 named_scope :logged, :conditions => "#{User.table_name}.status <> #{STATUS_ANONYMOUS}"
58 named_scope :status, lambda {|arg| arg.blank? ? {} : {:conditions => {:status => arg.to_i}} }
58 named_scope :status, lambda {|arg| arg.blank? ? {} : {:conditions => {:status => arg.to_i}} }
59
59
60 acts_as_customizable
60 acts_as_customizable
61
61
62 attr_accessor :password, :password_confirmation
62 attr_accessor :password, :password_confirmation
63 attr_accessor :last_before_login_on
63 attr_accessor :last_before_login_on
64 # Prevents unauthorized assignments
64 # Prevents unauthorized assignments
65 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password
65 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password
66
66
67 LOGIN_LENGTH_LIMIT = 60
67 LOGIN_LENGTH_LIMIT = 60
68 MAIL_LENGTH_LIMIT = 60
68 MAIL_LENGTH_LIMIT = 60
69
69
70 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
70 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
71 validates_uniqueness_of :login, :if => Proc.new { |user| user.login_changed? && user.login.present? }, :case_sensitive => false
71 validates_uniqueness_of :login, :if => Proc.new { |user| user.login_changed? && user.login.present? }, :case_sensitive => false
72 validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
72 validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
73 # Login must contain lettres, numbers, underscores only
73 # Login must contain lettres, numbers, underscores only
74 validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
74 validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
75 validates_length_of :login, :maximum => LOGIN_LENGTH_LIMIT
75 validates_length_of :login, :maximum => LOGIN_LENGTH_LIMIT
76 validates_length_of :firstname, :lastname, :maximum => 30
76 validates_length_of :firstname, :lastname, :maximum => 30
77 validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_blank => true
77 validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_blank => true
78 validates_length_of :mail, :maximum => MAIL_LENGTH_LIMIT, :allow_nil => true
78 validates_length_of :mail, :maximum => MAIL_LENGTH_LIMIT, :allow_nil => true
79 validates_confirmation_of :password, :allow_nil => true
79 validates_confirmation_of :password, :allow_nil => true
80 validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
80 validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
81 validate :validate_password_length
81 validate :validate_password_length
82
82
83 before_create :set_mail_notification
83 before_create :set_mail_notification
84 before_save :update_hashed_password
84 before_save :update_hashed_password
85 before_destroy :remove_references_before_destroy
85 before_destroy :remove_references_before_destroy
86
86
87 named_scope :in_group, lambda {|group|
87 named_scope :in_group, lambda {|group|
88 group_id = group.is_a?(Group) ? group.id : group.to_i
88 group_id = group.is_a?(Group) ? group.id : group.to_i
89 { :conditions => ["#{User.table_name}.id IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id] }
89 { :conditions => ["#{User.table_name}.id IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id] }
90 }
90 }
91 named_scope :not_in_group, lambda {|group|
91 named_scope :not_in_group, lambda {|group|
92 group_id = group.is_a?(Group) ? group.id : group.to_i
92 group_id = group.is_a?(Group) ? group.id : group.to_i
93 { :conditions => ["#{User.table_name}.id NOT IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id] }
93 { :conditions => ["#{User.table_name}.id NOT IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id] }
94 }
94 }
95
95
96 def set_mail_notification
96 def set_mail_notification
97 self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
97 self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
98 true
98 true
99 end
99 end
100
100
101 def update_hashed_password
101 def update_hashed_password
102 # update hashed_password if password was set
102 # update hashed_password if password was set
103 if self.password && self.auth_source_id.blank?
103 if self.password && self.auth_source_id.blank?
104 salt_password(password)
104 salt_password(password)
105 end
105 end
106 end
106 end
107
107
108 def reload(*args)
108 def reload(*args)
109 @name = nil
109 @name = nil
110 @projects_by_role = nil
110 @projects_by_role = nil
111 super
111 super
112 end
112 end
113
113
114 def mail=(arg)
114 def mail=(arg)
115 write_attribute(:mail, arg.to_s.strip)
115 write_attribute(:mail, arg.to_s.strip)
116 end
116 end
117
117
118 def identity_url=(url)
118 def identity_url=(url)
119 if url.blank?
119 if url.blank?
120 write_attribute(:identity_url, '')
120 write_attribute(:identity_url, '')
121 else
121 else
122 begin
122 begin
123 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
123 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
124 rescue OpenIdAuthentication::InvalidOpenId
124 rescue OpenIdAuthentication::InvalidOpenId
125 # Invlaid url, don't save
125 # Invlaid url, don't save
126 end
126 end
127 end
127 end
128 self.read_attribute(:identity_url)
128 self.read_attribute(:identity_url)
129 end
129 end
130
130
131 # Returns the user that matches provided login and password, or nil
131 # Returns the user that matches provided login and password, or nil
132 def self.try_to_login(login, password)
132 def self.try_to_login(login, password)
133 login = login.to_s
134 password = password.to_s
135
133 # Make sure no one can sign in with an empty password
136 # Make sure no one can sign in with an empty password
134 return nil if password.to_s.empty?
137 return nil if password.empty?
135 user = find_by_login(login)
138 user = find_by_login(login)
136 if user
139 if user
137 # user is already in local database
140 # user is already in local database
138 return nil if !user.active?
141 return nil if !user.active?
139 if user.auth_source
142 if user.auth_source
140 # user has an external authentication method
143 # user has an external authentication method
141 return nil unless user.auth_source.authenticate(login, password)
144 return nil unless user.auth_source.authenticate(login, password)
142 else
145 else
143 # authentication with local password
146 # authentication with local password
144 return nil unless user.check_password?(password)
147 return nil unless user.check_password?(password)
145 end
148 end
146 else
149 else
147 # user is not yet registered, try to authenticate with available sources
150 # user is not yet registered, try to authenticate with available sources
148 attrs = AuthSource.authenticate(login, password)
151 attrs = AuthSource.authenticate(login, password)
149 if attrs
152 if attrs
150 user = new(attrs)
153 user = new(attrs)
151 user.login = login
154 user.login = login
152 user.language = Setting.default_language
155 user.language = Setting.default_language
153 if user.save
156 if user.save
154 user.reload
157 user.reload
155 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
158 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
156 end
159 end
157 end
160 end
158 end
161 end
159 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
162 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
160 user
163 user
161 rescue => text
164 rescue => text
162 raise text
165 raise text
163 end
166 end
164
167
165 # Returns the user who matches the given autologin +key+ or nil
168 # Returns the user who matches the given autologin +key+ or nil
166 def self.try_to_autologin(key)
169 def self.try_to_autologin(key)
167 tokens = Token.find_all_by_action_and_value('autologin', key)
170 tokens = Token.find_all_by_action_and_value('autologin', key.to_s)
168 # Make sure there's only 1 token that matches the key
171 # Make sure there's only 1 token that matches the key
169 if tokens.size == 1
172 if tokens.size == 1
170 token = tokens.first
173 token = tokens.first
171 if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
174 if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
172 token.user.update_attribute(:last_login_on, Time.now)
175 token.user.update_attribute(:last_login_on, Time.now)
173 token.user
176 token.user
174 end
177 end
175 end
178 end
176 end
179 end
177
180
178 def self.name_formatter(formatter = nil)
181 def self.name_formatter(formatter = nil)
179 USER_FORMATS[formatter || Setting.user_format] || USER_FORMATS[:firstname_lastname]
182 USER_FORMATS[formatter || Setting.user_format] || USER_FORMATS[:firstname_lastname]
180 end
183 end
181
184
182 # Returns an array of fields names than can be used to make an order statement for users
185 # Returns an array of fields names than can be used to make an order statement for users
183 # according to how user names are displayed
186 # according to how user names are displayed
184 # Examples:
187 # Examples:
185 #
188 #
186 # User.fields_for_order_statement => ['users.login', 'users.id']
189 # User.fields_for_order_statement => ['users.login', 'users.id']
187 # User.fields_for_order_statement('authors') => ['authors.login', 'authors.id']
190 # User.fields_for_order_statement('authors') => ['authors.login', 'authors.id']
188 def self.fields_for_order_statement(table=nil)
191 def self.fields_for_order_statement(table=nil)
189 table ||= table_name
192 table ||= table_name
190 name_formatter[:order].map {|field| "#{table}.#{field}"}
193 name_formatter[:order].map {|field| "#{table}.#{field}"}
191 end
194 end
192
195
193 # Return user's full name for display
196 # Return user's full name for display
194 def name(formatter = nil)
197 def name(formatter = nil)
195 f = self.class.name_formatter(formatter)
198 f = self.class.name_formatter(formatter)
196 if formatter
199 if formatter
197 eval('"' + f[:string] + '"')
200 eval('"' + f[:string] + '"')
198 else
201 else
199 @name ||= eval('"' + f[:string] + '"')
202 @name ||= eval('"' + f[:string] + '"')
200 end
203 end
201 end
204 end
202
205
203 def active?
206 def active?
204 self.status == STATUS_ACTIVE
207 self.status == STATUS_ACTIVE
205 end
208 end
206
209
207 def registered?
210 def registered?
208 self.status == STATUS_REGISTERED
211 self.status == STATUS_REGISTERED
209 end
212 end
210
213
211 def locked?
214 def locked?
212 self.status == STATUS_LOCKED
215 self.status == STATUS_LOCKED
213 end
216 end
214
217
215 def activate
218 def activate
216 self.status = STATUS_ACTIVE
219 self.status = STATUS_ACTIVE
217 end
220 end
218
221
219 def register
222 def register
220 self.status = STATUS_REGISTERED
223 self.status = STATUS_REGISTERED
221 end
224 end
222
225
223 def lock
226 def lock
224 self.status = STATUS_LOCKED
227 self.status = STATUS_LOCKED
225 end
228 end
226
229
227 def activate!
230 def activate!
228 update_attribute(:status, STATUS_ACTIVE)
231 update_attribute(:status, STATUS_ACTIVE)
229 end
232 end
230
233
231 def register!
234 def register!
232 update_attribute(:status, STATUS_REGISTERED)
235 update_attribute(:status, STATUS_REGISTERED)
233 end
236 end
234
237
235 def lock!
238 def lock!
236 update_attribute(:status, STATUS_LOCKED)
239 update_attribute(:status, STATUS_LOCKED)
237 end
240 end
238
241
239 # Returns true if +clear_password+ is the correct user's password, otherwise false
242 # Returns true if +clear_password+ is the correct user's password, otherwise false
240 def check_password?(clear_password)
243 def check_password?(clear_password)
241 if auth_source_id.present?
244 if auth_source_id.present?
242 auth_source.authenticate(self.login, clear_password)
245 auth_source.authenticate(self.login, clear_password)
243 else
246 else
244 User.hash_password("#{salt}#{User.hash_password clear_password}") == hashed_password
247 User.hash_password("#{salt}#{User.hash_password clear_password}") == hashed_password
245 end
248 end
246 end
249 end
247
250
248 # Generates a random salt and computes hashed_password for +clear_password+
251 # Generates a random salt and computes hashed_password for +clear_password+
249 # The hashed password is stored in the following form: SHA1(salt + SHA1(password))
252 # The hashed password is stored in the following form: SHA1(salt + SHA1(password))
250 def salt_password(clear_password)
253 def salt_password(clear_password)
251 self.salt = User.generate_salt
254 self.salt = User.generate_salt
252 self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}")
255 self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}")
253 end
256 end
254
257
255 # Does the backend storage allow this user to change their password?
258 # Does the backend storage allow this user to change their password?
256 def change_password_allowed?
259 def change_password_allowed?
257 return true if auth_source.nil?
260 return true if auth_source.nil?
258 return auth_source.allow_password_changes?
261 return auth_source.allow_password_changes?
259 end
262 end
260
263
261 # Generate and set a random password. Useful for automated user creation
264 # Generate and set a random password. Useful for automated user creation
262 # Based on Token#generate_token_value
265 # Based on Token#generate_token_value
263 #
266 #
264 def random_password
267 def random_password
265 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
268 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
266 password = ''
269 password = ''
267 40.times { |i| password << chars[rand(chars.size-1)] }
270 40.times { |i| password << chars[rand(chars.size-1)] }
268 self.password = password
271 self.password = password
269 self.password_confirmation = password
272 self.password_confirmation = password
270 self
273 self
271 end
274 end
272
275
273 def pref
276 def pref
274 self.preference ||= UserPreference.new(:user => self)
277 self.preference ||= UserPreference.new(:user => self)
275 end
278 end
276
279
277 def time_zone
280 def time_zone
278 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
281 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
279 end
282 end
280
283
281 def wants_comments_in_reverse_order?
284 def wants_comments_in_reverse_order?
282 self.pref[:comments_sorting] == 'desc'
285 self.pref[:comments_sorting] == 'desc'
283 end
286 end
284
287
285 # Return user's RSS key (a 40 chars long string), used to access feeds
288 # Return user's RSS key (a 40 chars long string), used to access feeds
286 def rss_key
289 def rss_key
287 if rss_token.nil?
290 if rss_token.nil?
288 create_rss_token(:action => 'feeds')
291 create_rss_token(:action => 'feeds')
289 end
292 end
290 rss_token.value
293 rss_token.value
291 end
294 end
292
295
293 # Return user's API key (a 40 chars long string), used to access the API
296 # Return user's API key (a 40 chars long string), used to access the API
294 def api_key
297 def api_key
295 if api_token.nil?
298 if api_token.nil?
296 create_api_token(:action => 'api')
299 create_api_token(:action => 'api')
297 end
300 end
298 api_token.value
301 api_token.value
299 end
302 end
300
303
301 # Return an array of project ids for which the user has explicitly turned mail notifications on
304 # Return an array of project ids for which the user has explicitly turned mail notifications on
302 def notified_projects_ids
305 def notified_projects_ids
303 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
306 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
304 end
307 end
305
308
306 def notified_project_ids=(ids)
309 def notified_project_ids=(ids)
307 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
310 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
308 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
311 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
309 @notified_projects_ids = nil
312 @notified_projects_ids = nil
310 notified_projects_ids
313 notified_projects_ids
311 end
314 end
312
315
313 def valid_notification_options
316 def valid_notification_options
314 self.class.valid_notification_options(self)
317 self.class.valid_notification_options(self)
315 end
318 end
316
319
317 # Only users that belong to more than 1 project can select projects for which they are notified
320 # Only users that belong to more than 1 project can select projects for which they are notified
318 def self.valid_notification_options(user=nil)
321 def self.valid_notification_options(user=nil)
319 # Note that @user.membership.size would fail since AR ignores
322 # Note that @user.membership.size would fail since AR ignores
320 # :include association option when doing a count
323 # :include association option when doing a count
321 if user.nil? || user.memberships.length < 1
324 if user.nil? || user.memberships.length < 1
322 MAIL_NOTIFICATION_OPTIONS.reject {|option| option.first == 'selected'}
325 MAIL_NOTIFICATION_OPTIONS.reject {|option| option.first == 'selected'}
323 else
326 else
324 MAIL_NOTIFICATION_OPTIONS
327 MAIL_NOTIFICATION_OPTIONS
325 end
328 end
326 end
329 end
327
330
328 # Find a user account by matching the exact login and then a case-insensitive
331 # Find a user account by matching the exact login and then a case-insensitive
329 # version. Exact matches will be given priority.
332 # version. Exact matches will be given priority.
330 def self.find_by_login(login)
333 def self.find_by_login(login)
331 # First look for an exact match
334 # First look for an exact match
332 user = all(:conditions => {:login => login}).detect {|u| u.login == login}
335 user = all(:conditions => {:login => login}).detect {|u| u.login == login}
333 unless user
336 unless user
334 # Fail over to case-insensitive if none was found
337 # Fail over to case-insensitive if none was found
335 user = first(:conditions => ["LOWER(login) = ?", login.to_s.downcase])
338 user = first(:conditions => ["LOWER(login) = ?", login.to_s.downcase])
336 end
339 end
337 user
340 user
338 end
341 end
339
342
340 def self.find_by_rss_key(key)
343 def self.find_by_rss_key(key)
341 token = Token.find_by_value(key)
344 token = Token.find_by_action_and_value('feeds', key.to_s)
342 token && token.user.active? ? token.user : nil
345 token && token.user.active? ? token.user : nil
343 end
346 end
344
347
345 def self.find_by_api_key(key)
348 def self.find_by_api_key(key)
346 token = Token.find_by_action_and_value('api', key)
349 token = Token.find_by_action_and_value('api', key.to_s)
347 token && token.user.active? ? token.user : nil
350 token && token.user.active? ? token.user : nil
348 end
351 end
349
352
350 # Makes find_by_mail case-insensitive
353 # Makes find_by_mail case-insensitive
351 def self.find_by_mail(mail)
354 def self.find_by_mail(mail)
352 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
355 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
353 end
356 end
354
357
355 # Returns true if the default admin account can no longer be used
358 # Returns true if the default admin account can no longer be used
356 def self.default_admin_account_changed?
359 def self.default_admin_account_changed?
357 !User.active.find_by_login("admin").try(:check_password?, "admin")
360 !User.active.find_by_login("admin").try(:check_password?, "admin")
358 end
361 end
359
362
360 def to_s
363 def to_s
361 name
364 name
362 end
365 end
363
366
364 # Returns the current day according to user's time zone
367 # Returns the current day according to user's time zone
365 def today
368 def today
366 if time_zone.nil?
369 if time_zone.nil?
367 Date.today
370 Date.today
368 else
371 else
369 Time.now.in_time_zone(time_zone).to_date
372 Time.now.in_time_zone(time_zone).to_date
370 end
373 end
371 end
374 end
372
375
373 def logged?
376 def logged?
374 true
377 true
375 end
378 end
376
379
377 def anonymous?
380 def anonymous?
378 !logged?
381 !logged?
379 end
382 end
380
383
381 # Return user's roles for project
384 # Return user's roles for project
382 def roles_for_project(project)
385 def roles_for_project(project)
383 roles = []
386 roles = []
384 # No role on archived projects
387 # No role on archived projects
385 return roles unless project && project.active?
388 return roles unless project && project.active?
386 if logged?
389 if logged?
387 # Find project membership
390 # Find project membership
388 membership = memberships.detect {|m| m.project_id == project.id}
391 membership = memberships.detect {|m| m.project_id == project.id}
389 if membership
392 if membership
390 roles = membership.roles
393 roles = membership.roles
391 else
394 else
392 @role_non_member ||= Role.non_member
395 @role_non_member ||= Role.non_member
393 roles << @role_non_member
396 roles << @role_non_member
394 end
397 end
395 else
398 else
396 @role_anonymous ||= Role.anonymous
399 @role_anonymous ||= Role.anonymous
397 roles << @role_anonymous
400 roles << @role_anonymous
398 end
401 end
399 roles
402 roles
400 end
403 end
401
404
402 # Return true if the user is a member of project
405 # Return true if the user is a member of project
403 def member_of?(project)
406 def member_of?(project)
404 !roles_for_project(project).detect {|role| role.member?}.nil?
407 !roles_for_project(project).detect {|role| role.member?}.nil?
405 end
408 end
406
409
407 # Returns a hash of user's projects grouped by roles
410 # Returns a hash of user's projects grouped by roles
408 def projects_by_role
411 def projects_by_role
409 return @projects_by_role if @projects_by_role
412 return @projects_by_role if @projects_by_role
410
413
411 @projects_by_role = Hash.new {|h,k| h[k]=[]}
414 @projects_by_role = Hash.new {|h,k| h[k]=[]}
412 memberships.each do |membership|
415 memberships.each do |membership|
413 membership.roles.each do |role|
416 membership.roles.each do |role|
414 @projects_by_role[role] << membership.project if membership.project
417 @projects_by_role[role] << membership.project if membership.project
415 end
418 end
416 end
419 end
417 @projects_by_role.each do |role, projects|
420 @projects_by_role.each do |role, projects|
418 projects.uniq!
421 projects.uniq!
419 end
422 end
420
423
421 @projects_by_role
424 @projects_by_role
422 end
425 end
423
426
424 # Returns true if user is arg or belongs to arg
427 # Returns true if user is arg or belongs to arg
425 def is_or_belongs_to?(arg)
428 def is_or_belongs_to?(arg)
426 if arg.is_a?(User)
429 if arg.is_a?(User)
427 self == arg
430 self == arg
428 elsif arg.is_a?(Group)
431 elsif arg.is_a?(Group)
429 arg.users.include?(self)
432 arg.users.include?(self)
430 else
433 else
431 false
434 false
432 end
435 end
433 end
436 end
434
437
435 # Return true if the user is allowed to do the specified action on a specific context
438 # Return true if the user is allowed to do the specified action on a specific context
436 # Action can be:
439 # Action can be:
437 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
440 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
438 # * a permission Symbol (eg. :edit_project)
441 # * a permission Symbol (eg. :edit_project)
439 # Context can be:
442 # Context can be:
440 # * a project : returns true if user is allowed to do the specified action on this project
443 # * a project : returns true if user is allowed to do the specified action on this project
441 # * an array of projects : returns true if user is allowed on every project
444 # * an array of projects : returns true if user is allowed on every project
442 # * nil with options[:global] set : check if user has at least one role allowed for this action,
445 # * nil with options[:global] set : check if user has at least one role allowed for this action,
443 # or falls back to Non Member / Anonymous permissions depending if the user is logged
446 # or falls back to Non Member / Anonymous permissions depending if the user is logged
444 def allowed_to?(action, context, options={}, &block)
447 def allowed_to?(action, context, options={}, &block)
445 if context && context.is_a?(Project)
448 if context && context.is_a?(Project)
446 # No action allowed on archived projects
449 # No action allowed on archived projects
447 return false unless context.active?
450 return false unless context.active?
448 # No action allowed on disabled modules
451 # No action allowed on disabled modules
449 return false unless context.allows_to?(action)
452 return false unless context.allows_to?(action)
450 # Admin users are authorized for anything else
453 # Admin users are authorized for anything else
451 return true if admin?
454 return true if admin?
452
455
453 roles = roles_for_project(context)
456 roles = roles_for_project(context)
454 return false unless roles
457 return false unless roles
455 roles.detect {|role|
458 roles.detect {|role|
456 (context.is_public? || role.member?) &&
459 (context.is_public? || role.member?) &&
457 role.allowed_to?(action) &&
460 role.allowed_to?(action) &&
458 (block_given? ? yield(role, self) : true)
461 (block_given? ? yield(role, self) : true)
459 }
462 }
460 elsif context && context.is_a?(Array)
463 elsif context && context.is_a?(Array)
461 # Authorize if user is authorized on every element of the array
464 # Authorize if user is authorized on every element of the array
462 context.map do |project|
465 context.map do |project|
463 allowed_to?(action, project, options, &block)
466 allowed_to?(action, project, options, &block)
464 end.inject do |memo,allowed|
467 end.inject do |memo,allowed|
465 memo && allowed
468 memo && allowed
466 end
469 end
467 elsif options[:global]
470 elsif options[:global]
468 # Admin users are always authorized
471 # Admin users are always authorized
469 return true if admin?
472 return true if admin?
470
473
471 # authorize if user has at least one role that has this permission
474 # authorize if user has at least one role that has this permission
472 roles = memberships.collect {|m| m.roles}.flatten.uniq
475 roles = memberships.collect {|m| m.roles}.flatten.uniq
473 roles << (self.logged? ? Role.non_member : Role.anonymous)
476 roles << (self.logged? ? Role.non_member : Role.anonymous)
474 roles.detect {|role|
477 roles.detect {|role|
475 role.allowed_to?(action) &&
478 role.allowed_to?(action) &&
476 (block_given? ? yield(role, self) : true)
479 (block_given? ? yield(role, self) : true)
477 }
480 }
478 else
481 else
479 false
482 false
480 end
483 end
481 end
484 end
482
485
483 # Is the user allowed to do the specified action on any project?
486 # Is the user allowed to do the specified action on any project?
484 # See allowed_to? for the actions and valid options.
487 # See allowed_to? for the actions and valid options.
485 def allowed_to_globally?(action, options, &block)
488 def allowed_to_globally?(action, options, &block)
486 allowed_to?(action, nil, options.reverse_merge(:global => true), &block)
489 allowed_to?(action, nil, options.reverse_merge(:global => true), &block)
487 end
490 end
488
491
489 # Returns true if the user is allowed to delete his own account
492 # Returns true if the user is allowed to delete his own account
490 def own_account_deletable?
493 def own_account_deletable?
491 Setting.unsubscribe? &&
494 Setting.unsubscribe? &&
492 (!admin? || User.active.first(:conditions => ["admin = ? AND id <> ?", true, id]).present?)
495 (!admin? || User.active.first(:conditions => ["admin = ? AND id <> ?", true, id]).present?)
493 end
496 end
494
497
495 safe_attributes 'login',
498 safe_attributes 'login',
496 'firstname',
499 'firstname',
497 'lastname',
500 'lastname',
498 'mail',
501 'mail',
499 'mail_notification',
502 'mail_notification',
500 'language',
503 'language',
501 'custom_field_values',
504 'custom_field_values',
502 'custom_fields',
505 'custom_fields',
503 'identity_url'
506 'identity_url'
504
507
505 safe_attributes 'status',
508 safe_attributes 'status',
506 'auth_source_id',
509 'auth_source_id',
507 :if => lambda {|user, current_user| current_user.admin?}
510 :if => lambda {|user, current_user| current_user.admin?}
508
511
509 safe_attributes 'group_ids',
512 safe_attributes 'group_ids',
510 :if => lambda {|user, current_user| current_user.admin? && !user.new_record?}
513 :if => lambda {|user, current_user| current_user.admin? && !user.new_record?}
511
514
512 # Utility method to help check if a user should be notified about an
515 # Utility method to help check if a user should be notified about an
513 # event.
516 # event.
514 #
517 #
515 # TODO: only supports Issue events currently
518 # TODO: only supports Issue events currently
516 def notify_about?(object)
519 def notify_about?(object)
517 case mail_notification
520 case mail_notification
518 when 'all'
521 when 'all'
519 true
522 true
520 when 'selected'
523 when 'selected'
521 # user receives notifications for created/assigned issues on unselected projects
524 # user receives notifications for created/assigned issues on unselected projects
522 if object.is_a?(Issue) && (object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was))
525 if object.is_a?(Issue) && (object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was))
523 true
526 true
524 else
527 else
525 false
528 false
526 end
529 end
527 when 'none'
530 when 'none'
528 false
531 false
529 when 'only_my_events'
532 when 'only_my_events'
530 if object.is_a?(Issue) && (object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was))
533 if object.is_a?(Issue) && (object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was))
531 true
534 true
532 else
535 else
533 false
536 false
534 end
537 end
535 when 'only_assigned'
538 when 'only_assigned'
536 if object.is_a?(Issue) && (is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was))
539 if object.is_a?(Issue) && (is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was))
537 true
540 true
538 else
541 else
539 false
542 false
540 end
543 end
541 when 'only_owner'
544 when 'only_owner'
542 if object.is_a?(Issue) && object.author == self
545 if object.is_a?(Issue) && object.author == self
543 true
546 true
544 else
547 else
545 false
548 false
546 end
549 end
547 else
550 else
548 false
551 false
549 end
552 end
550 end
553 end
551
554
552 def self.current=(user)
555 def self.current=(user)
553 @current_user = user
556 @current_user = user
554 end
557 end
555
558
556 def self.current
559 def self.current
557 @current_user ||= User.anonymous
560 @current_user ||= User.anonymous
558 end
561 end
559
562
560 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
563 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
561 # one anonymous user per database.
564 # one anonymous user per database.
562 def self.anonymous
565 def self.anonymous
563 anonymous_user = AnonymousUser.find(:first)
566 anonymous_user = AnonymousUser.find(:first)
564 if anonymous_user.nil?
567 if anonymous_user.nil?
565 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
568 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
566 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
569 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
567 end
570 end
568 anonymous_user
571 anonymous_user
569 end
572 end
570
573
571 # Salts all existing unsalted passwords
574 # Salts all existing unsalted passwords
572 # It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password))
575 # It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password))
573 # This method is used in the SaltPasswords migration and is to be kept as is
576 # This method is used in the SaltPasswords migration and is to be kept as is
574 def self.salt_unsalted_passwords!
577 def self.salt_unsalted_passwords!
575 transaction do
578 transaction do
576 User.find_each(:conditions => "salt IS NULL OR salt = ''") do |user|
579 User.find_each(:conditions => "salt IS NULL OR salt = ''") do |user|
577 next if user.hashed_password.blank?
580 next if user.hashed_password.blank?
578 salt = User.generate_salt
581 salt = User.generate_salt
579 hashed_password = User.hash_password("#{salt}#{user.hashed_password}")
582 hashed_password = User.hash_password("#{salt}#{user.hashed_password}")
580 User.update_all("salt = '#{salt}', hashed_password = '#{hashed_password}'", ["id = ?", user.id] )
583 User.update_all("salt = '#{salt}', hashed_password = '#{hashed_password}'", ["id = ?", user.id] )
581 end
584 end
582 end
585 end
583 end
586 end
584
587
585 protected
588 protected
586
589
587 def validate_password_length
590 def validate_password_length
588 # Password length validation based on setting
591 # Password length validation based on setting
589 if !password.nil? && password.size < Setting.password_min_length.to_i
592 if !password.nil? && password.size < Setting.password_min_length.to_i
590 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
593 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
591 end
594 end
592 end
595 end
593
596
594 private
597 private
595
598
596 # Removes references that are not handled by associations
599 # Removes references that are not handled by associations
597 # Things that are not deleted are reassociated with the anonymous user
600 # Things that are not deleted are reassociated with the anonymous user
598 def remove_references_before_destroy
601 def remove_references_before_destroy
599 return if self.id.nil?
602 return if self.id.nil?
600
603
601 substitute = User.anonymous
604 substitute = User.anonymous
602 Attachment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
605 Attachment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
603 Comment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
606 Comment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
604 Issue.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
607 Issue.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
605 Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id]
608 Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id]
606 Journal.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
609 Journal.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
607 JournalDetail.update_all ['old_value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]
610 JournalDetail.update_all ['old_value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]
608 JournalDetail.update_all ['value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]
611 JournalDetail.update_all ['value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]
609 Message.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
612 Message.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
610 News.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
613 News.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
611 # Remove private queries and keep public ones
614 # Remove private queries and keep public ones
612 ::Query.delete_all ['user_id = ? AND is_public = ?', id, false]
615 ::Query.delete_all ['user_id = ? AND is_public = ?', id, false]
613 ::Query.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
616 ::Query.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
614 TimeEntry.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
617 TimeEntry.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
615 Token.delete_all ['user_id = ?', id]
618 Token.delete_all ['user_id = ?', id]
616 Watcher.delete_all ['user_id = ?', id]
619 Watcher.delete_all ['user_id = ?', id]
617 WikiContent.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
620 WikiContent.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
618 WikiContent::Version.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
621 WikiContent::Version.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
619 end
622 end
620
623
621 # Return password digest
624 # Return password digest
622 def self.hash_password(clear_password)
625 def self.hash_password(clear_password)
623 Digest::SHA1.hexdigest(clear_password || "")
626 Digest::SHA1.hexdigest(clear_password || "")
624 end
627 end
625
628
626 # Returns a 128bits random salt as a hex string (32 chars long)
629 # Returns a 128bits random salt as a hex string (32 chars long)
627 def self.generate_salt
630 def self.generate_salt
628 Redmine::Utils.random_hex(16)
631 Redmine::Utils.random_hex(16)
629 end
632 end
630
633
631 end
634 end
632
635
633 class AnonymousUser < User
636 class AnonymousUser < User
634 validate :validate_anonymous_uniqueness, :on => :create
637 validate :validate_anonymous_uniqueness, :on => :create
635
638
636 def validate_anonymous_uniqueness
639 def validate_anonymous_uniqueness
637 # There should be only one AnonymousUser in the database
640 # There should be only one AnonymousUser in the database
638 errors.add :base, 'An anonymous user already exists.' if AnonymousUser.find(:first)
641 errors.add :base, 'An anonymous user already exists.' if AnonymousUser.find(:first)
639 end
642 end
640
643
641 def available_custom_fields
644 def available_custom_fields
642 []
645 []
643 end
646 end
644
647
645 # Overrides a few properties
648 # Overrides a few properties
646 def logged?; false end
649 def logged?; false end
647 def admin; false end
650 def admin; false end
648 def name(*args); I18n.t(:label_user_anonymous) end
651 def name(*args); I18n.t(:label_user_anonymous) end
649 def mail; nil end
652 def mail; nil end
650 def time_zone; nil end
653 def time_zone; nil end
651 def rss_key; nil end
654 def rss_key; nil end
652
655
653 # Anonymous user can not be destroyed
656 # Anonymous user can not be destroyed
654 def destroy
657 def destroy
655 false
658 false
656 end
659 end
657 end
660 end
General Comments 0
You need to be logged in to leave comments. Login now