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