comparison app/controllers/application_controller.rb @ 1338:25603efa57b5

Merge from live branch
author Chris Cannam
date Thu, 20 Jun 2013 13:14:14 +0100
parents bb32da3bea34
children 4f746d8966dd 51364c0cd58f
comparison
equal deleted inserted replaced
1209:1b1138f6f55e 1338:25603efa57b5
1 # Redmine - project management software 1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang 2 # Copyright (C) 2006-2012 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.
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 class_attribute :accept_api_auth_actions
27 class_attribute :accept_rss_auth_actions
28 class_attribute :model_object
29
26 layout 'base' 30 layout 'base'
27 exempt_from_layout 'builder', 'rsb'
28 31
29 protect_from_forgery 32 protect_from_forgery
30 def handle_unverified_request 33 def handle_unverified_request
31 super 34 super
32 cookies.delete(:autologin) 35 cookies.delete(:autologin)
33 end 36 end
34 # Remove broken cookie after upgrade from 0.8.x (#4292) 37
35 # See https://rails.lighthouseapp.com/projects/8994/tickets/3360 38 before_filter :session_expiration, :user_setup, :check_if_login_required, :set_localization
36 # TODO: remove it when Rails is fixed
37 before_filter :delete_broken_cookies
38 def delete_broken_cookies
39 if cookies['_redmine_session'] && cookies['_redmine_session'] !~ /--/
40 cookies.delete '_redmine_session'
41 redirect_to home_path
42 return false
43 end
44 end
45
46 before_filter :user_setup, :check_if_login_required, :set_localization
47 filter_parameter_logging :password
48 39
49 rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token 40 rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
50 rescue_from ::Unauthorized, :with => :deny_access 41 rescue_from ::Unauthorized, :with => :deny_access
42 rescue_from ::ActionView::MissingTemplate, :with => :missing_template
51 43
52 include Redmine::Search::Controller 44 include Redmine::Search::Controller
53 include Redmine::MenuManager::MenuController 45 include Redmine::MenuManager::MenuController
54 helper Redmine::MenuManager::MenuHelper 46 helper Redmine::MenuManager::MenuHelper
55 47
56 Redmine::Scm::Base.all.each do |scm| 48 def session_expiration
57 require_dependency "repository/#{scm.underscore}" 49 if session[:user_id]
50 if session_expired? && !try_to_autologin
51 reset_session
52 flash[:error] = l(:error_session_expired)
53 redirect_to signin_url
54 else
55 session[:atime] = Time.now.utc.to_i
56 end
57 end
58 end
59
60 def session_expired?
61 if Setting.session_lifetime?
62 unless session[:ctime] && (Time.now.utc.to_i - session[:ctime].to_i <= Setting.session_lifetime.to_i * 60)
63 return true
64 end
65 end
66 if Setting.session_timeout?
67 unless session[:atime] && (Time.now.utc.to_i - session[:atime].to_i <= Setting.session_timeout.to_i * 60)
68 return true
69 end
70 end
71 false
72 end
73
74 def start_user_session(user)
75 session[:user_id] = user.id
76 session[:ctime] = Time.now.utc.to_i
77 session[:atime] = Time.now.utc.to_i
58 end 78 end
59 79
60 def user_setup 80 def user_setup
61 # Check the settings cache for each request 81 # Check the settings cache for each request
62 Setting.check_cache 82 Setting.check_cache
63 # Find the current user 83 # Find the current user
64 User.current = find_current_user 84 User.current = find_current_user
85 logger.info(" Current user: " + (User.current.logged? ? "#{User.current.login} (id=#{User.current.id})" : "anonymous")) if logger
65 end 86 end
66 87
67 # Returns the current user or nil if no user is logged in 88 # Returns the current user or nil if no user is logged in
68 # and starts a session if needed 89 # and starts a session if needed
69 def find_current_user 90 def find_current_user
70 if session[:user_id] 91 user = nil
71 # existing session 92 unless api_request?
72 (User.active.find(session[:user_id]) rescue nil) 93 if session[:user_id]
73 elsif cookies[:autologin] && Setting.autologin? 94 # existing session
74 # auto-login feature starts a new session 95 user = (User.active.find(session[:user_id]) rescue nil)
75 user = User.try_to_autologin(cookies[:autologin]) 96 elsif autologin_user = try_to_autologin
76 session[:user_id] = user.id if user 97 user = autologin_user
77 user 98 elsif params[:format] == 'atom' && params[:key] && request.get? && accept_rss_auth?
78 elsif params[:format] == 'atom' && params[:key] && request.get? && accept_rss_auth? 99 # RSS key authentication does not start a session
79 # RSS key authentication does not start a session 100 user = User.find_by_rss_key(params[:key])
80 User.find_by_rss_key(params[:key]) 101 end
81 elsif Setting.rest_api_enabled? && accept_api_auth? 102 end
103 if user.nil? && Setting.rest_api_enabled? && accept_api_auth?
82 if (key = api_key_from_request) 104 if (key = api_key_from_request)
83 # Use API key 105 # Use API key
84 User.find_by_api_key(key) 106 user = User.find_by_api_key(key)
85 else 107 else
86 # HTTP Basic, either username/password or API key/random 108 # HTTP Basic, either username/password or API key/random
87 authenticate_with_http_basic do |username, password| 109 authenticate_with_http_basic do |username, password|
88 User.try_to_login(username, password) || User.find_by_api_key(username) 110 user = User.try_to_login(username, password) || User.find_by_api_key(username)
89 end 111 end
90 end 112 end
113 # Switch user if requested by an admin user
114 if user && user.admin? && (username = api_switch_user_from_request)
115 su = User.find_by_login(username)
116 if su && su.active?
117 logger.info(" User switched by: #{user.login} (id=#{user.id})") if logger
118 user = su
119 else
120 render_error :message => 'Invalid X-Redmine-Switch-User header', :status => 412
121 end
122 end
123 end
124 user
125 end
126
127 def try_to_autologin
128 if cookies[:autologin] && Setting.autologin?
129 # auto-login feature starts a new session
130 user = User.try_to_autologin(cookies[:autologin])
131 if user
132 reset_session
133 start_user_session(user)
134 end
135 user
91 end 136 end
92 end 137 end
93 138
94 # Sets the logged in user 139 # Sets the logged in user
95 def logged_user=(user) 140 def logged_user=(user)
96 reset_session 141 reset_session
97 if user && user.is_a?(User) 142 if user && user.is_a?(User)
98 User.current = user 143 User.current = user
99 session[:user_id] = user.id 144 start_user_session(user)
100 else 145 else
101 User.current = User.anonymous 146 User.current = User.anonymous
147 end
148 end
149
150 # Logs out current user
151 def logout_user
152 if User.current.logged?
153 cookies.delete :autologin
154 Token.delete_all(["user_id = ? AND action = ?", User.current.id, 'autologin'])
155 self.logged_user = nil
102 end 156 end
103 end 157 end
104 158
105 # check if login is globally required to access the application 159 # check if login is globally required to access the application
106 def check_if_login_required 160 def check_if_login_required
207 261
208 @project = @object.project 262 @project = @object.project
209 end 263 end
210 264
211 def find_model_object 265 def find_model_object
212 model = self.class.read_inheritable_attribute('model_object') 266 model = self.class.model_object
213 if model 267 if model
214 @object = model.find(params[:id]) 268 @object = model.find(params[:id])
215 self.instance_variable_set('@' + controller_name.singularize, @object) if @object 269 self.instance_variable_set('@' + controller_name.singularize, @object) if @object
216 end 270 end
217 rescue ActiveRecord::RecordNotFound 271 rescue ActiveRecord::RecordNotFound
218 render_404 272 render_404
219 end 273 end
220 274
221 def self.model_object(model) 275 def self.model_object(model)
222 write_inheritable_attribute('model_object', model) 276 self.model_object = model
223 end 277 end
224 278
225 # Filter for bulk issue operations 279 # Find the issue whose id is the :id parameter
280 # Raises a Unauthorized exception if the issue is not visible
281 def find_issue
282 # Issue.visible.find(...) can not be used to redirect user to the login form
283 # if the issue actually exists but requires authentication
284 @issue = Issue.find(params[:id])
285 raise Unauthorized unless @issue.visible?
286 @project = @issue.project
287 rescue ActiveRecord::RecordNotFound
288 render_404
289 end
290
291 # Find issues with a single :id param or :ids array param
292 # Raises a Unauthorized exception if one of the issues is not visible
226 def find_issues 293 def find_issues
227 @issues = Issue.find_all_by_id(params[:id] || params[:ids]) 294 @issues = Issue.find_all_by_id(params[:id] || params[:ids])
228 raise ActiveRecord::RecordNotFound if @issues.empty? 295 raise ActiveRecord::RecordNotFound if @issues.empty?
229 if @issues.detect {|issue| !issue.visible?} 296 raise Unauthorized unless @issues.all?(&:visible?)
230 deny_access
231 return
232 end
233 @projects = @issues.collect(&:project).compact.uniq 297 @projects = @issues.collect(&:project).compact.uniq
234 @project = @projects.first if @projects.size == 1 298 @project = @projects.first if @projects.size == 1
235 rescue ActiveRecord::RecordNotFound 299 rescue ActiveRecord::RecordNotFound
236 render_404 300 render_404
237 end 301 end
238 302
239 # Check if project is unique before bulk operations
240 def check_project_uniqueness
241 unless @project
242 # TODO: let users bulk edit/move/destroy issues from different projects
243 render_error 'Can not bulk edit/move/destroy issues from different projects'
244 return false
245 end
246 end
247
248 # make sure that the user is a member of the project (or admin) if project is private 303 # make sure that the user is a member of the project (or admin) if project is private
249 # used as a before_filter for actions that do not require any particular permission on the project 304 # used as a before_filter for actions that do not require any particular permission on the project
250 def check_project_privacy 305 def check_project_privacy
251 if @project && @project.active? 306 if @project && !@project.archived?
252 if @project.is_public? || User.current.member_of?(@project) || User.current.admin? 307 if @project.visible?
253 true 308 true
254 else 309 else
255 deny_access 310 deny_access
256 end 311 end
257 else 312 else
260 false 315 false
261 end 316 end
262 end 317 end
263 318
264 def back_url 319 def back_url
265 params[:back_url] || request.env['HTTP_REFERER'] 320 url = params[:back_url]
321 if url.nil? && referer = request.env['HTTP_REFERER']
322 url = CGI.unescape(referer.to_s)
323 end
324 url
266 end 325 end
267 326
268 def redirect_back_or_default(default) 327 def redirect_back_or_default(default)
269 back_url = CGI.unescape(params[:back_url].to_s) 328 back_url = params[:back_url].to_s
270 if !back_url.blank? 329 if back_url.present?
271 begin 330 begin
272 uri = URI.parse(back_url) 331 uri = URI.parse(back_url)
273 # do not redirect user to another host or to the login or register page 332 # do not redirect user to another host or to the login or register page
274 if (uri.relative? || (uri.host == request.host)) && !uri.path.match(%r{/(login|account/register)}) 333 if (uri.relative? || (uri.host == request.host)) && !uri.path.match(%r{/(login|account/register)})
275 # soundsoftware: if back_url is the home page, 334 # soundsoftware: if back_url is the home page,
289 back_url = uri.to_s 348 back_url = uri.to_s
290 redirect_to(back_url) 349 redirect_to(back_url)
291 return 350 return
292 end 351 end
293 rescue URI::InvalidURIError 352 rescue URI::InvalidURIError
353 logger.warn("Could not redirect to invalid URL #{back_url}")
294 # redirect to default 354 # redirect to default
295 end 355 end
296 end 356 end
297 redirect_to default 357 redirect_to default
298 false 358 false
359 end
360
361 # Redirects to the request referer if present, redirects to args or call block otherwise.
362 def redirect_to_referer_or(*args, &block)
363 redirect_to :back
364 rescue ::ActionController::RedirectBackError
365 if args.any?
366 redirect_to *args
367 elsif block_given?
368 block.call
369 else
370 raise "#redirect_to_referer_or takes arguments or a block"
371 end
299 end 372 end
300 373
301 def render_403(options={}) 374 def render_403(options={})
302 @project = nil 375 @project = nil
303 render_error({:message => :notice_not_authorized, :status => 403}.merge(options)) 376 render_error({:message => :notice_not_authorized, :status => 403}.merge(options))
319 392
320 respond_to do |format| 393 respond_to do |format|
321 format.html { 394 format.html {
322 render :template => 'common/error', :layout => use_layout, :status => @status 395 render :template => 'common/error', :layout => use_layout, :status => @status
323 } 396 }
324 format.atom { head @status } 397 format.any { head @status }
325 format.xml { head @status } 398 end
326 format.js { head @status } 399 end
327 format.json { head @status } 400
328 end 401 # Handler for ActionView::MissingTemplate exception
329 end 402 def missing_template
330 403 logger.warn "Missing template, responding with 404"
404 @project = nil
405 render_404
406 end
407
331 # Filter for actions that provide an API response 408 # Filter for actions that provide an API response
332 # but have no HTML representation for non admin users 409 # but have no HTML representation for non admin users
333 def require_admin_or_api_request 410 def require_admin_or_api_request
334 return true if api_request? 411 return true if api_request?
335 if User.current.admin? 412 if User.current.admin?
358 def render_feed(items, options={}) 435 def render_feed(items, options={})
359 @items = items || [] 436 @items = items || []
360 @items.sort! {|x,y| y.event_datetime <=> x.event_datetime } 437 @items.sort! {|x,y| y.event_datetime <=> x.event_datetime }
361 @items = @items.slice(0, Setting.feeds_limit.to_i) 438 @items = @items.slice(0, Setting.feeds_limit.to_i)
362 @title = options[:title] || Setting.app_title 439 @title = options[:title] || Setting.app_title
363 render :template => "common/feed.atom", :layout => false, 440 render :template => "common/feed", :formats => [:atom], :layout => false,
364 :content_type => 'application/atom+xml' 441 :content_type => 'application/atom+xml'
365 end
366
367 # TODO: remove in Redmine 1.4
368 def self.accept_key_auth(*actions)
369 ActiveSupport::Deprecation.warn "ApplicationController.accept_key_auth is deprecated and will be removed in Redmine 1.4. Use accept_rss_auth (or accept_api_auth) instead."
370 accept_rss_auth(*actions)
371 end
372
373 # TODO: remove in Redmine 1.4
374 def accept_key_auth_actions
375 ActiveSupport::Deprecation.warn "ApplicationController.accept_key_auth_actions is deprecated and will be removed in Redmine 1.4. Use accept_rss_auth (or accept_api_auth) instead."
376 self.class.accept_rss_auth
377 end 442 end
378 443
379 def self.accept_rss_auth(*actions) 444 def self.accept_rss_auth(*actions)
380 if actions.any? 445 if actions.any?
381 write_inheritable_attribute('accept_rss_auth_actions', actions) 446 self.accept_rss_auth_actions = actions
382 else 447 else
383 read_inheritable_attribute('accept_rss_auth_actions') || [] 448 self.accept_rss_auth_actions || []
384 end 449 end
385 end 450 end
386 451
387 def accept_rss_auth?(action=action_name) 452 def accept_rss_auth?(action=action_name)
388 self.class.accept_rss_auth.include?(action.to_sym) 453 self.class.accept_rss_auth.include?(action.to_sym)
389 end 454 end
390 455
391 def self.accept_api_auth(*actions) 456 def self.accept_api_auth(*actions)
392 if actions.any? 457 if actions.any?
393 write_inheritable_attribute('accept_api_auth_actions', actions) 458 self.accept_api_auth_actions = actions
394 else 459 else
395 read_inheritable_attribute('accept_api_auth_actions') || [] 460 self.accept_api_auth_actions || []
396 end 461 end
397 end 462 end
398 463
399 def accept_api_auth?(action=action_name) 464 def accept_api_auth?(action=action_name)
400 self.class.accept_api_auth.include?(action.to_sym) 465 self.class.accept_api_auth.include?(action.to_sym)
470 end 535 end
471 536
472 # Returns the API key present in the request 537 # Returns the API key present in the request
473 def api_key_from_request 538 def api_key_from_request
474 if params[:key].present? 539 if params[:key].present?
475 params[:key] 540 params[:key].to_s
476 elsif request.headers["X-Redmine-API-Key"].present? 541 elsif request.headers["X-Redmine-API-Key"].present?
477 request.headers["X-Redmine-API-Key"] 542 request.headers["X-Redmine-API-Key"].to_s
478 end 543 end
544 end
545
546 # Returns the API 'switch user' value if present
547 def api_switch_user_from_request
548 request.headers["X-Redmine-Switch-User"].to_s.presence
479 end 549 end
480 550
481 # Renders a warning flash if obj has unsaved attachments 551 # Renders a warning flash if obj has unsaved attachments
482 def render_attachment_warning_if_needed(obj) 552 def render_attachment_warning_if_needed(obj)
483 flash[:warning] = l(:warning_attachments_not_saved, obj.unsaved_attachments.size) if obj.unsaved_attachments.present? 553 flash[:warning] = l(:warning_attachments_not_saved, obj.unsaved_attachments.size) if obj.unsaved_attachments.present?
504 session.delete(:query) 574 session.delete(:query)
505 sort_clear if respond_to?(:sort_clear) 575 sort_clear if respond_to?(:sort_clear)
506 render_error "An error occurred while executing the query and has been logged. Please report this error to your Redmine administrator." 576 render_error "An error occurred while executing the query and has been logged. Please report this error to your Redmine administrator."
507 end 577 end
508 578
579 # Renders a 200 response for successfull updates or deletions via the API
580 def render_api_ok
581 render_api_head :ok
582 end
583
584 # Renders a head API response
585 def render_api_head(status)
586 # #head would return a response body with one space
587 render :text => '', :status => status, :layout => nil
588 end
589
509 # Renders API response on validation failure 590 # Renders API response on validation failure
510 def render_validation_errors(object) 591 def render_validation_errors(objects)
511 options = { :status => :unprocessable_entity, :layout => false } 592 if objects.is_a?(Array)
512 options.merge!(case params[:format] 593 @error_messages = objects.map {|object| object.errors.full_messages}.flatten
513 when 'xml'; { :xml => object.errors } 594 else
514 when 'json'; { :json => {'errors' => object.errors} } # ActiveResource client compliance 595 @error_messages = objects.errors.full_messages
515 else 596 end
516 raise "Unknown format #{params[:format]} in #render_validation_errors" 597 render :template => 'common/error_messages.api', :status => :unprocessable_entity, :layout => nil
517 end 598 end
518 ) 599
519 render options 600 # Overrides #_include_layout? so that #render with no arguments
520 end
521
522 # Overrides #default_template so that the api template
523 # is used automatically if it exists
524 def default_template(action_name = self.action_name)
525 if api_request?
526 begin
527 return self.view_paths.find_template(default_template_name(action_name), 'api')
528 rescue ::ActionView::MissingTemplate
529 # the api template was not found
530 # fallback to the default behaviour
531 end
532 end
533 super
534 end
535
536 # Overrides #pick_layout so that #render with no arguments
537 # doesn't use the layout for api requests 601 # doesn't use the layout for api requests
538 def pick_layout(*args) 602 def _include_layout?(*args)
539 api_request? ? nil : super 603 api_request? ? false : super
540 end 604 end
541 end 605 end