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