Mercurial > hg > soundsoftware-site
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 |