comparison app/controllers/application_controller.rb @ 511:107d36338b70 live

Merge from branch "cannam"
author Chris Cannam
date Thu, 14 Jul 2011 10:43:07 +0100
parents 851510f1b535
children 7ded87cc4b80
comparison
equal deleted inserted replaced
451:a9f6345cb43d 511:107d36338b70
1 # redMine - project management software 1 # Redmine - project management software
2 # Copyright (C) 2006-2007 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
22
21 class ApplicationController < ActionController::Base 23 class ApplicationController < ActionController::Base
22 include Redmine::I18n 24 include Redmine::I18n
23 25
24 layout 'base' 26 layout 'base'
25 exempt_from_layout 'builder' 27 exempt_from_layout 'builder', 'rsb'
26 28
27 # Remove broken cookie after upgrade from 0.8.x (#4292) 29 # Remove broken cookie after upgrade from 0.8.x (#4292)
28 # See https://rails.lighthouseapp.com/projects/8994/tickets/3360 30 # See https://rails.lighthouseapp.com/projects/8994/tickets/3360
29 # TODO: remove it when Rails is fixed 31 # TODO: remove it when Rails is fixed
30 before_filter :delete_broken_cookies 32 before_filter :delete_broken_cookies
31 def delete_broken_cookies 33 def delete_broken_cookies
32 if cookies['_redmine_session'] && cookies['_redmine_session'] !~ /--/ 34 if cookies['_redmine_session'] && cookies['_redmine_session'] !~ /--/
33 cookies.delete '_redmine_session' 35 cookies.delete '_redmine_session'
34 redirect_to home_path 36 redirect_to home_path
35 return false 37 return false
36 end 38 end
37 end 39 end
38 40
39 before_filter :user_setup, :check_if_login_required, :set_localization 41 before_filter :user_setup, :check_if_login_required, :set_localization
40 filter_parameter_logging :password 42 filter_parameter_logging :password
41 protect_from_forgery 43 protect_from_forgery
42 44
43 rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token 45 rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
44 46 rescue_from ::Unauthorized, :with => :deny_access
47
45 include Redmine::Search::Controller 48 include Redmine::Search::Controller
46 include Redmine::MenuManager::MenuController 49 include Redmine::MenuManager::MenuController
47 helper Redmine::MenuManager::MenuHelper 50 helper Redmine::MenuManager::MenuHelper
48 51
49 Redmine::Scm::Base.all.each do |scm| 52 Redmine::Scm::Base.all.each do |scm|
50 require_dependency "repository/#{scm.underscore}" 53 require_dependency "repository/#{scm.underscore}"
51 end 54 end
52 55
53 def user_setup 56 def user_setup
54 # Check the settings cache for each request 57 # Check the settings cache for each request
55 Setting.check_cache 58 Setting.check_cache
56 # Find the current user 59 # Find the current user
57 User.current = find_current_user 60 User.current = find_current_user
58 end 61 end
59 62
60 # Returns the current user or nil if no user is logged in 63 # Returns the current user or nil if no user is logged in
61 # and starts a session if needed 64 # and starts a session if needed
62 def find_current_user 65 def find_current_user
63 if session[:user_id] 66 if session[:user_id]
64 # existing session 67 # existing session
66 elsif cookies[:autologin] && Setting.autologin? 69 elsif cookies[:autologin] && Setting.autologin?
67 # auto-login feature starts a new session 70 # auto-login feature starts a new session
68 user = User.try_to_autologin(cookies[:autologin]) 71 user = User.try_to_autologin(cookies[:autologin])
69 session[:user_id] = user.id if user 72 session[:user_id] = user.id if user
70 user 73 user
71 elsif params[:format] == 'atom' && params[:key] && accept_key_auth_actions.include?(params[:action]) 74 elsif params[:format] == 'atom' && params[:key] && request.get? && accept_rss_auth?
72 # RSS key authentication does not start a session 75 # RSS key authentication does not start a session
73 User.find_by_rss_key(params[:key]) 76 User.find_by_rss_key(params[:key])
74 elsif Setting.rest_api_enabled? && ['xml', 'json'].include?(params[:format]) 77 elsif Setting.rest_api_enabled? && accept_api_auth?
75 if params[:key].present? && accept_key_auth_actions.include?(params[:action]) 78 if (key = api_key_from_request)
76 # Use API key 79 # Use API key
77 User.find_by_api_key(params[:key]) 80 User.find_by_api_key(key)
78 else 81 else
79 # HTTP Basic, either username/password or API key/random 82 # HTTP Basic, either username/password or API key/random
80 authenticate_with_http_basic do |username, password| 83 authenticate_with_http_basic do |username, password|
81 User.try_to_login(username, password) || User.find_by_api_key(username) 84 User.try_to_login(username, password) || User.find_by_api_key(username)
82 end 85 end
92 session[:user_id] = user.id 95 session[:user_id] = user.id
93 else 96 else
94 User.current = User.anonymous 97 User.current = User.anonymous
95 end 98 end
96 end 99 end
97 100
98 # check if login is globally required to access the application 101 # check if login is globally required to access the application
99 def check_if_login_required 102 def check_if_login_required
100 # no check needed if user is already logged in 103 # no check needed if user is already logged in
101 return true if User.current.logged? 104 return true if User.current.logged?
102 require_login if Setting.login_required? 105 require_login if Setting.login_required?
103 end 106 end
104 107
105 def set_localization 108 def set_localization
106 lang = nil 109 lang = nil
107 if User.current.logged? 110 if User.current.logged?
108 lang = find_language(User.current.language) 111 lang = find_language(User.current.language)
109 end 112 end
115 end 118 end
116 end 119 end
117 lang ||= Setting.default_language 120 lang ||= Setting.default_language
118 set_language_if_valid(lang) 121 set_language_if_valid(lang)
119 end 122 end
120 123
121 def require_login 124 def require_login
122 if !User.current.logged? 125 if !User.current.logged?
123 # Extract only the basic url parameters on non-GET requests 126 # Extract only the basic url parameters on non-GET requests
124 if request.get? 127 if request.get?
125 url = url_for(params) 128 url = url_for(params)
144 render_403 147 render_403
145 return false 148 return false
146 end 149 end
147 true 150 true
148 end 151 end
149 152
150 def deny_access 153 def deny_access
151 User.current.logged? ? render_403 : require_login 154 User.current.logged? ? render_403 : require_login
152 end 155 end
153 156
154 # Authorize the user for the requested action 157 # Authorize the user for the requested action
195 end 198 end
196 199
197 # Finds and sets @project based on @object.project 200 # Finds and sets @project based on @object.project
198 def find_project_from_association 201 def find_project_from_association
199 render_404 unless @object.present? 202 render_404 unless @object.present?
200 203
201 @project = @object.project 204 @project = @object.project
202 rescue ActiveRecord::RecordNotFound 205 rescue ActiveRecord::RecordNotFound
203 render_404 206 render_404
204 end 207 end
205 208
219 222
220 # Filter for bulk issue operations 223 # Filter for bulk issue operations
221 def find_issues 224 def find_issues
222 @issues = Issue.find_all_by_id(params[:id] || params[:ids]) 225 @issues = Issue.find_all_by_id(params[:id] || params[:ids])
223 raise ActiveRecord::RecordNotFound if @issues.empty? 226 raise ActiveRecord::RecordNotFound if @issues.empty?
227 if @issues.detect {|issue| !issue.visible?}
228 deny_access
229 return
230 end
224 @projects = @issues.collect(&:project).compact.uniq 231 @projects = @issues.collect(&:project).compact.uniq
225 @project = @projects.first if @projects.size == 1 232 @project = @projects.first if @projects.size == 1
226 rescue ActiveRecord::RecordNotFound 233 rescue ActiveRecord::RecordNotFound
227 render_404 234 render_404
228 end 235 end
229 236
230 # Check if project is unique before bulk operations 237 # Check if project is unique before bulk operations
231 def check_project_uniqueness 238 def check_project_uniqueness
232 unless @project 239 unless @project
233 # TODO: let users bulk edit/move/destroy issues from different projects 240 # TODO: let users bulk edit/move/destroy issues from different projects
234 render_error 'Can not bulk edit/move/destroy issues from different projects' 241 render_error 'Can not bulk edit/move/destroy issues from different projects'
235 return false 242 return false
236 end 243 end
237 end 244 end
238 245
239 # make sure that the user is a member of the project (or admin) if project is private 246 # make sure that the user is a member of the project (or admin) if project is private
240 # used as a before_filter for actions that do not require any particular permission on the project 247 # used as a before_filter for actions that do not require any particular permission on the project
241 def check_project_privacy 248 def check_project_privacy
242 if @project && @project.active? 249 if @project && @project.active?
243 if @project.is_public? || User.current.member_of?(@project) || User.current.admin? 250 if @project.is_public? || User.current.member_of?(@project) || User.current.admin?
264 # do not redirect user to another host or to the login or register page 271 # do not redirect user to another host or to the login or register page
265 if (uri.relative? || (uri.host == request.host)) && !uri.path.match(%r{/(login|account/register)}) 272 if (uri.relative? || (uri.host == request.host)) && !uri.path.match(%r{/(login|account/register)})
266 # soundsoftware: if back_url is the home page, 273 # soundsoftware: if back_url is the home page,
267 # change it to My Page (#125) 274 # change it to My Page (#125)
268 if (uri.path == home_path) 275 if (uri.path == home_path)
269 uri.path = uri.path + "/my" 276 if (uri.path =~ /\/$/)
277 uri.path = uri.path + "my"
278 else
279 uri.path = uri.path + "/my"
280 end
270 end 281 end
271 # soundsoftware: if login page is https but back_url http, 282 # soundsoftware: if login page is https but back_url http,
272 # switch back_url to https to ensure cookie validity (#83) 283 # switch back_url to https to ensure cookie validity (#83)
273 if (uri.scheme == "http") && (URI.parse(request.url).scheme == "https") 284 if (uri.scheme == "http") && (URI.parse(request.url).scheme == "https")
274 uri.scheme = "https" 285 uri.scheme = "https"
280 rescue URI::InvalidURIError 291 rescue URI::InvalidURIError
281 # redirect to default 292 # redirect to default
282 end 293 end
283 end 294 end
284 redirect_to default 295 redirect_to default
285 end 296 false
286 297 end
298
287 def render_403(options={}) 299 def render_403(options={})
288 @project = nil 300 @project = nil
289 render_error({:message => :notice_not_authorized, :status => 403}.merge(options)) 301 render_error({:message => :notice_not_authorized, :status => 403}.merge(options))
290 return false 302 return false
291 end 303 end
292 304
293 def render_404(options={}) 305 def render_404(options={})
294 render_error({:message => :notice_file_not_found, :status => 404}.merge(options)) 306 render_error({:message => :notice_file_not_found, :status => 404}.merge(options))
295 return false 307 return false
296 end 308 end
297 309
298 # Renders an error response 310 # Renders an error response
299 def render_error(arg) 311 def render_error(arg)
300 arg = {:message => arg} unless arg.is_a?(Hash) 312 arg = {:message => arg} unless arg.is_a?(Hash)
301 313
302 @message = arg[:message] 314 @message = arg[:message]
303 @message = l(@message) if @message.is_a?(Symbol) 315 @message = l(@message) if @message.is_a?(Symbol)
304 @status = arg[:status] || 500 316 @status = arg[:status] || 500
305 317
306 respond_to do |format| 318 respond_to do |format|
307 format.html { 319 format.html {
308 render :template => 'common/error', :layout => use_layout, :status => @status 320 render :template => 'common/error', :layout => use_layout, :status => @status
309 } 321 }
310 format.atom { head @status } 322 format.atom { head @status }
318 # 330 #
319 # @return [boolean, string] name of the layout to use or false for no layout 331 # @return [boolean, string] name of the layout to use or false for no layout
320 def use_layout 332 def use_layout
321 request.xhr? ? false : 'base' 333 request.xhr? ? false : 'base'
322 end 334 end
323 335
324 def invalid_authenticity_token 336 def invalid_authenticity_token
325 if api_request? 337 if api_request?
326 logger.error "Form authenticity token is missing or is invalid. API calls must include a proper Content-type header (text/xml or text/json)." 338 logger.error "Form authenticity token is missing or is invalid. API calls must include a proper Content-type header (text/xml or text/json)."
327 end 339 end
328 render_error "Invalid form authenticity token. Perhaps your session has timed out; try reloading the form and entering your details again." 340 render_error "Invalid form authenticity token. Perhaps your session has timed out; try reloading the form and entering your details again."
329 end 341 end
330 342
331 def render_feed(items, options={}) 343 def render_feed(items, options={})
332 @items = items || [] 344 @items = items || []
333 @items.sort! {|x,y| y.event_datetime <=> x.event_datetime } 345 @items.sort! {|x,y| y.event_datetime <=> x.event_datetime }
334 @items = @items.slice(0, Setting.feeds_limit.to_i) 346 @items = @items.slice(0, Setting.feeds_limit.to_i)
335 @title = options[:title] || Setting.app_title 347 @title = options[:title] || Setting.app_title
336 render :template => "common/feed.atom.rxml", :layout => false, :content_type => 'application/atom+xml' 348 render :template => "common/feed.atom.rxml", :layout => false, :content_type => 'application/atom+xml'
337 end 349 end
338 350
351 # TODO: remove in Redmine 1.4
339 def self.accept_key_auth(*actions) 352 def self.accept_key_auth(*actions)
340 actions = actions.flatten.map(&:to_s) 353 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."
341 write_inheritable_attribute('accept_key_auth_actions', actions) 354 accept_rss_auth(*actions)
355 end
356
357 # TODO: remove in Redmine 1.4
358 def accept_key_auth_actions
359 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."
360 self.class.accept_rss_auth
342 end 361 end
343 362
344 def accept_key_auth_actions 363 def self.accept_rss_auth(*actions)
345 self.class.read_inheritable_attribute('accept_key_auth_actions') || [] 364 if actions.any?
365 write_inheritable_attribute('accept_rss_auth_actions', actions)
366 else
367 read_inheritable_attribute('accept_rss_auth_actions') || []
368 end
346 end 369 end
347 370
371 def accept_rss_auth?(action=action_name)
372 self.class.accept_rss_auth.include?(action.to_sym)
373 end
374
375 def self.accept_api_auth(*actions)
376 if actions.any?
377 write_inheritable_attribute('accept_api_auth_actions', actions)
378 else
379 read_inheritable_attribute('accept_api_auth_actions') || []
380 end
381 end
382
383 def accept_api_auth?(action=action_name)
384 self.class.accept_api_auth.include?(action.to_sym)
385 end
386
348 # Returns the number of objects that should be displayed 387 # Returns the number of objects that should be displayed
349 # on the paginated list 388 # on the paginated list
350 def per_page_option 389 def per_page_option
351 per_page = nil 390 per_page = nil
352 if params[:per_page] && Setting.per_page_options_array.include?(params[:per_page].to_s.to_i) 391 if params[:per_page] && Setting.per_page_options_array.include?(params[:per_page].to_s.to_i)
356 per_page = session[:per_page] 395 per_page = session[:per_page]
357 else 396 else
358 per_page = Setting.per_page_options_array.first || 25 397 per_page = Setting.per_page_options_array.first || 25
359 end 398 end
360 per_page 399 per_page
400 end
401
402 # Returns offset and limit used to retrieve objects
403 # for an API response based on offset, limit and page parameters
404 def api_offset_and_limit(options=params)
405 if options[:offset].present?
406 offset = options[:offset].to_i
407 if offset < 0
408 offset = 0
409 end
410 end
411 limit = options[:limit].to_i
412 if limit < 1
413 limit = 25
414 elsif limit > 100
415 limit = 100
416 end
417 if offset.nil? && options[:page].present?
418 offset = (options[:page].to_i - 1) * limit
419 offset = 0 if offset < 0
420 end
421 offset ||= 0
422
423 [offset, limit]
361 end 424 end
362 425
363 # qvalues http header parser 426 # qvalues http header parser
364 # code taken from webrick 427 # code taken from webrick
365 def parse_qvalues(value) 428 def parse_qvalues(value)
378 end 441 end
379 return tmp 442 return tmp
380 rescue 443 rescue
381 nil 444 nil
382 end 445 end
383 446
384 # Returns a string that can be used as filename value in Content-Disposition header 447 # Returns a string that can be used as filename value in Content-Disposition header
385 def filename_for_content_disposition(name) 448 def filename_for_content_disposition(name)
386 request.env['HTTP_USER_AGENT'] =~ %r{MSIE} ? ERB::Util.url_encode(name) : name 449 request.env['HTTP_USER_AGENT'] =~ %r{MSIE} ? ERB::Util.url_encode(name) : name
387 end 450 end
388 451
389 def api_request? 452 def api_request?
390 %w(xml json).include? params[:format] 453 %w(xml json).include? params[:format]
454 end
455
456 # Returns the API key present in the request
457 def api_key_from_request
458 if params[:key].present?
459 params[:key]
460 elsif request.headers["X-Redmine-API-Key"].present?
461 request.headers["X-Redmine-API-Key"]
462 end
391 end 463 end
392 464
393 # Renders a warning flash if obj has unsaved attachments 465 # Renders a warning flash if obj has unsaved attachments
394 def render_attachment_warning_if_needed(obj) 466 def render_attachment_warning_if_needed(obj)
395 flash[:warning] = l(:warning_attachments_not_saved, obj.unsaved_attachments.size) if obj.unsaved_attachments.present? 467 flash[:warning] = l(:warning_attachments_not_saved, obj.unsaved_attachments.size) if obj.unsaved_attachments.present?
422 def object_errors_to_json(object) 494 def object_errors_to_json(object)
423 object.errors.collect do |attribute, error| 495 object.errors.collect do |attribute, error|
424 { attribute => error } 496 { attribute => error }
425 end.to_json 497 end.to_json
426 end 498 end
427 499
500 # Renders API response on validation failure
501 def render_validation_errors(object)
502 options = { :status => :unprocessable_entity, :layout => false }
503 options.merge!(case params[:format]
504 when 'xml'; { :xml => object.errors }
505 when 'json'; { :json => {'errors' => object.errors} } # ActiveResource client compliance
506 else
507 raise "Unknown format #{params[:format]} in #render_validation_errors"
508 end
509 )
510 render options
511 end
512
513 # Overrides #default_template so that the api template
514 # is used automatically if it exists
515 def default_template(action_name = self.action_name)
516 if api_request?
517 begin
518 return self.view_paths.find_template(default_template_name(action_name), 'api')
519 rescue ::ActionView::MissingTemplate
520 # the api template was not found
521 # fallback to the default behaviour
522 end
523 end
524 super
525 end
526
527 # Overrides #pick_layout so that #render with no arguments
528 # doesn't use the layout for api requests
529 def pick_layout(*args)
530 api_request? ? nil : super
531 end
428 end 532 end