comparison app/controllers/.svn/text-base/application_controller.rb.svn-base @ 511:107d36338b70 live

Merge from branch "cannam"
author Chris Cannam
date Thu, 14 Jul 2011 10:43:07 +0100
parents 0c939c159af4
children
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?
269 rescue URI::InvalidURIError 276 rescue URI::InvalidURIError
270 # redirect to default 277 # redirect to default
271 end 278 end
272 end 279 end
273 redirect_to default 280 redirect_to default
274 end 281 false
275 282 end
283
276 def render_403(options={}) 284 def render_403(options={})
277 @project = nil 285 @project = nil
278 render_error({:message => :notice_not_authorized, :status => 403}.merge(options)) 286 render_error({:message => :notice_not_authorized, :status => 403}.merge(options))
279 return false 287 return false
280 end 288 end
281 289
282 def render_404(options={}) 290 def render_404(options={})
283 render_error({:message => :notice_file_not_found, :status => 404}.merge(options)) 291 render_error({:message => :notice_file_not_found, :status => 404}.merge(options))
284 return false 292 return false
285 end 293 end
286 294
287 # Renders an error response 295 # Renders an error response
288 def render_error(arg) 296 def render_error(arg)
289 arg = {:message => arg} unless arg.is_a?(Hash) 297 arg = {:message => arg} unless arg.is_a?(Hash)
290 298
291 @message = arg[:message] 299 @message = arg[:message]
292 @message = l(@message) if @message.is_a?(Symbol) 300 @message = l(@message) if @message.is_a?(Symbol)
293 @status = arg[:status] || 500 301 @status = arg[:status] || 500
294 302
295 respond_to do |format| 303 respond_to do |format|
296 format.html { 304 format.html {
297 render :template => 'common/error', :layout => use_layout, :status => @status 305 render :template => 'common/error', :layout => use_layout, :status => @status
298 } 306 }
299 format.atom { head @status } 307 format.atom { head @status }
307 # 315 #
308 # @return [boolean, string] name of the layout to use or false for no layout 316 # @return [boolean, string] name of the layout to use or false for no layout
309 def use_layout 317 def use_layout
310 request.xhr? ? false : 'base' 318 request.xhr? ? false : 'base'
311 end 319 end
312 320
313 def invalid_authenticity_token 321 def invalid_authenticity_token
314 if api_request? 322 if api_request?
315 logger.error "Form authenticity token is missing or is invalid. API calls must include a proper Content-type header (text/xml or text/json)." 323 logger.error "Form authenticity token is missing or is invalid. API calls must include a proper Content-type header (text/xml or text/json)."
316 end 324 end
317 render_error "Invalid form authenticity token." 325 render_error "Invalid form authenticity token."
318 end 326 end
319 327
320 def render_feed(items, options={}) 328 def render_feed(items, options={})
321 @items = items || [] 329 @items = items || []
322 @items.sort! {|x,y| y.event_datetime <=> x.event_datetime } 330 @items.sort! {|x,y| y.event_datetime <=> x.event_datetime }
323 @items = @items.slice(0, Setting.feeds_limit.to_i) 331 @items = @items.slice(0, Setting.feeds_limit.to_i)
324 @title = options[:title] || Setting.app_title 332 @title = options[:title] || Setting.app_title
325 render :template => "common/feed.atom.rxml", :layout => false, :content_type => 'application/atom+xml' 333 render :template => "common/feed.atom.rxml", :layout => false, :content_type => 'application/atom+xml'
326 end 334 end
327 335
336 # TODO: remove in Redmine 1.4
328 def self.accept_key_auth(*actions) 337 def self.accept_key_auth(*actions)
329 actions = actions.flatten.map(&:to_s) 338 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."
330 write_inheritable_attribute('accept_key_auth_actions', actions) 339 accept_rss_auth(*actions)
340 end
341
342 # TODO: remove in Redmine 1.4
343 def accept_key_auth_actions
344 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."
345 self.class.accept_rss_auth
331 end 346 end
332 347
333 def accept_key_auth_actions 348 def self.accept_rss_auth(*actions)
334 self.class.read_inheritable_attribute('accept_key_auth_actions') || [] 349 if actions.any?
350 write_inheritable_attribute('accept_rss_auth_actions', actions)
351 else
352 read_inheritable_attribute('accept_rss_auth_actions') || []
353 end
335 end 354 end
336 355
356 def accept_rss_auth?(action=action_name)
357 self.class.accept_rss_auth.include?(action.to_sym)
358 end
359
360 def self.accept_api_auth(*actions)
361 if actions.any?
362 write_inheritable_attribute('accept_api_auth_actions', actions)
363 else
364 read_inheritable_attribute('accept_api_auth_actions') || []
365 end
366 end
367
368 def accept_api_auth?(action=action_name)
369 self.class.accept_api_auth.include?(action.to_sym)
370 end
371
337 # Returns the number of objects that should be displayed 372 # Returns the number of objects that should be displayed
338 # on the paginated list 373 # on the paginated list
339 def per_page_option 374 def per_page_option
340 per_page = nil 375 per_page = nil
341 if params[:per_page] && Setting.per_page_options_array.include?(params[:per_page].to_s.to_i) 376 if params[:per_page] && Setting.per_page_options_array.include?(params[:per_page].to_s.to_i)
345 per_page = session[:per_page] 380 per_page = session[:per_page]
346 else 381 else
347 per_page = Setting.per_page_options_array.first || 25 382 per_page = Setting.per_page_options_array.first || 25
348 end 383 end
349 per_page 384 per_page
385 end
386
387 # Returns offset and limit used to retrieve objects
388 # for an API response based on offset, limit and page parameters
389 def api_offset_and_limit(options=params)
390 if options[:offset].present?
391 offset = options[:offset].to_i
392 if offset < 0
393 offset = 0
394 end
395 end
396 limit = options[:limit].to_i
397 if limit < 1
398 limit = 25
399 elsif limit > 100
400 limit = 100
401 end
402 if offset.nil? && options[:page].present?
403 offset = (options[:page].to_i - 1) * limit
404 offset = 0 if offset < 0
405 end
406 offset ||= 0
407
408 [offset, limit]
350 end 409 end
351 410
352 # qvalues http header parser 411 # qvalues http header parser
353 # code taken from webrick 412 # code taken from webrick
354 def parse_qvalues(value) 413 def parse_qvalues(value)
367 end 426 end
368 return tmp 427 return tmp
369 rescue 428 rescue
370 nil 429 nil
371 end 430 end
372 431
373 # Returns a string that can be used as filename value in Content-Disposition header 432 # Returns a string that can be used as filename value in Content-Disposition header
374 def filename_for_content_disposition(name) 433 def filename_for_content_disposition(name)
375 request.env['HTTP_USER_AGENT'] =~ %r{MSIE} ? ERB::Util.url_encode(name) : name 434 request.env['HTTP_USER_AGENT'] =~ %r{MSIE} ? ERB::Util.url_encode(name) : name
376 end 435 end
377 436
378 def api_request? 437 def api_request?
379 %w(xml json).include? params[:format] 438 %w(xml json).include? params[:format]
439 end
440
441 # Returns the API key present in the request
442 def api_key_from_request
443 if params[:key].present?
444 params[:key]
445 elsif request.headers["X-Redmine-API-Key"].present?
446 request.headers["X-Redmine-API-Key"]
447 end
380 end 448 end
381 449
382 # Renders a warning flash if obj has unsaved attachments 450 # Renders a warning flash if obj has unsaved attachments
383 def render_attachment_warning_if_needed(obj) 451 def render_attachment_warning_if_needed(obj)
384 flash[:warning] = l(:warning_attachments_not_saved, obj.unsaved_attachments.size) if obj.unsaved_attachments.present? 452 flash[:warning] = l(:warning_attachments_not_saved, obj.unsaved_attachments.size) if obj.unsaved_attachments.present?
411 def object_errors_to_json(object) 479 def object_errors_to_json(object)
412 object.errors.collect do |attribute, error| 480 object.errors.collect do |attribute, error|
413 { attribute => error } 481 { attribute => error }
414 end.to_json 482 end.to_json
415 end 483 end
416 484
485 # Renders API response on validation failure
486 def render_validation_errors(object)
487 options = { :status => :unprocessable_entity, :layout => false }
488 options.merge!(case params[:format]
489 when 'xml'; { :xml => object.errors }
490 when 'json'; { :json => {'errors' => object.errors} } # ActiveResource client compliance
491 else
492 raise "Unknown format #{params[:format]} in #render_validation_errors"
493 end
494 )
495 render options
496 end
497
498 # Overrides #default_template so that the api template
499 # is used automatically if it exists
500 def default_template(action_name = self.action_name)
501 if api_request?
502 begin
503 return self.view_paths.find_template(default_template_name(action_name), 'api')
504 rescue ::ActionView::MissingTemplate
505 # the api template was not found
506 # fallback to the default behaviour
507 end
508 end
509 super
510 end
511
512 # Overrides #pick_layout so that #render with no arguments
513 # doesn't use the layout for api requests
514 def pick_layout(*args)
515 api_request? ? nil : super
516 end
417 end 517 end