2011-04-11 21:53:15 +04:00
# Redmine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang
2007-03-12 20:59:02 +03:00
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
2011-05-12 08:25:47 +04:00
#
2007-03-12 20:59:02 +03:00
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
2011-05-12 08:25:47 +04:00
#
2007-03-12 20:59:02 +03:00
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
2008-07-26 12:46:33 +04:00
require 'uri'
2008-09-21 16:45:22 +04:00
require 'cgi'
2008-07-26 12:46:33 +04:00
2011-07-03 19:58:02 +04:00
class Unauthorized < Exception ; end
2007-03-12 20:59:02 +03:00
class ApplicationController < ActionController :: Base
2009-02-21 14:04:50 +03:00
include Redmine :: I18n
2009-09-13 21:14:35 +04:00
2008-08-10 19:22:54 +04:00
layout 'base'
2010-12-04 20:43:39 +03:00
exempt_from_layout 'builder' , 'rsb'
2011-05-12 08:25:47 +04:00
2009-11-25 23:28:56 +03:00
# Remove broken cookie after upgrade from 0.8.x (#4292)
# See https://rails.lighthouseapp.com/projects/8994/tickets/3360
# TODO: remove it when Rails is fixed
before_filter :delete_broken_cookies
def delete_broken_cookies
if cookies [ '_redmine_session' ] && cookies [ '_redmine_session' ] !~ / -- /
2011-05-12 08:25:47 +04:00
cookies . delete '_redmine_session'
2009-12-18 17:22:18 +03:00
redirect_to home_path
return false
2009-11-25 23:28:56 +03:00
end
end
2011-05-12 08:25:47 +04:00
2007-08-29 20:52:35 +04:00
before_filter :user_setup , :check_if_login_required , :set_localization
2007-03-12 20:59:02 +03:00
filter_parameter_logging :password
2009-11-14 22:41:07 +03:00
protect_from_forgery
2011-05-12 08:25:47 +04:00
2009-11-25 23:45:16 +03:00
rescue_from ActionController :: InvalidAuthenticityToken , :with = > :invalid_authenticity_token
2011-07-03 19:58:02 +04:00
rescue_from :: Unauthorized , :with = > :deny_access
2011-05-12 08:25:47 +04:00
2009-10-21 21:07:18 +04:00
include Redmine :: Search :: Controller
2008-01-19 14:53:43 +03:00
include Redmine :: MenuManager :: MenuController
helper Redmine :: MenuManager :: MenuHelper
2011-05-12 08:25:47 +04:00
2010-02-17 01:41:59 +03:00
Redmine :: Scm :: Base . all . each do | scm |
2007-06-13 03:03:38 +04:00
require_dependency " repository/ #{ scm . underscore } "
end
2009-11-25 23:28:56 +03:00
2007-08-29 20:52:35 +04:00
def user_setup
2007-12-30 13:51:34 +03:00
# Check the settings cache for each request
2007-08-31 21:45:32 +04:00
Setting . check_cache
2007-12-30 13:51:34 +03:00
# Find the current user
2009-06-06 14:20:27 +04:00
User . current = find_current_user
2007-12-30 13:51:34 +03:00
end
2011-05-12 08:25:47 +04:00
2007-12-30 13:51:34 +03:00
# Returns the current user or nil if no user is logged in
2009-06-06 14:20:27 +04:00
# and starts a session if needed
2007-12-30 13:51:34 +03:00
def find_current_user
2007-03-12 20:59:02 +03:00
if session [ :user_id ]
2007-08-29 20:52:35 +04:00
# existing session
2008-11-30 19:57:56 +03:00
( User . active . find ( session [ :user_id ] ) rescue nil )
2007-08-29 20:52:35 +04:00
elsif cookies [ :autologin ] && Setting . autologin?
2009-06-06 14:20:27 +04:00
# auto-login feature starts a new session
user = User . try_to_autologin ( cookies [ :autologin ] )
session [ :user_id ] = user . id if user
user
2011-07-09 13:11:13 +04:00
elsif params [ :format ] == 'atom' && params [ :key ] && request . get? && accept_rss_auth?
2009-06-06 14:20:27 +04:00
# RSS key authentication does not start a session
2007-12-30 13:51:34 +03:00
User . find_by_rss_key ( params [ :key ] )
2011-07-09 13:11:13 +04:00
elsif Setting . rest_api_enabled? && accept_api_auth?
if ( key = api_key_from_request )
2009-12-23 09:27:38 +03:00
# Use API key
2010-12-23 17:49:14 +03:00
User . find_by_api_key ( key )
2009-12-23 09:27:38 +03:00
else
# HTTP Basic, either username/password or API key/random
authenticate_with_http_basic do | username , password |
User . try_to_login ( username , password ) || User . find_by_api_key ( username )
end
end
2007-03-12 20:59:02 +03:00
end
end
2009-12-23 09:27:38 +03:00
2009-02-25 17:59:33 +03:00
# Sets the logged in user
def logged_user = ( user )
2009-11-21 13:02:39 +03:00
reset_session
2009-02-25 17:59:33 +03:00
if user && user . is_a? ( User )
User . current = user
session [ :user_id ] = user . id
else
User . current = User . anonymous
end
end
2011-05-12 08:25:47 +04:00
2007-03-12 20:59:02 +03:00
# check if login is globally required to access the application
def check_if_login_required
2007-05-06 16:49:32 +04:00
# no check needed if user is already logged in
2007-08-29 20:52:35 +04:00
return true if User . current . logged?
2007-03-12 20:59:02 +03:00
require_login if Setting . login_required?
2011-05-12 08:25:47 +04:00
end
2007-03-12 20:59:02 +03:00
def set_localization
2009-02-21 14:04:50 +03:00
lang = nil
if User . current . logged?
lang = find_language ( User . current . language )
end
if lang . nil? && request . env [ 'HTTP_ACCEPT_LANGUAGE' ]
2010-04-30 14:33:25 +04:00
accept_lang = parse_qvalues ( request . env [ 'HTTP_ACCEPT_LANGUAGE' ] ) . first
2009-02-21 14:04:50 +03:00
if ! accept_lang . blank?
2010-04-30 14:33:25 +04:00
accept_lang = accept_lang . downcase
2009-02-21 14:04:50 +03:00
lang = find_language ( accept_lang ) || find_language ( accept_lang . split ( '-' ) . first )
2007-03-12 20:59:02 +03:00
end
2009-02-21 14:04:50 +03:00
end
lang || = Setting . default_language
set_language_if_valid ( lang )
2007-03-12 20:59:02 +03:00
end
2011-05-12 08:25:47 +04:00
2007-03-12 20:59:02 +03:00
def require_login
2007-08-29 20:52:35 +04:00
if ! User . current . logged?
2009-11-14 22:41:02 +03:00
# Extract only the basic url parameters on non-GET requests
if request . get?
url = url_for ( params )
else
url = url_for ( :controller = > params [ :controller ] , :action = > params [ :action ] , :id = > params [ :id ] , :project_id = > params [ :project_id ] )
end
2009-12-23 09:27:33 +03:00
respond_to do | format |
format . html { redirect_to :controller = > " account " , :action = > " login " , :back_url = > url }
2009-12-23 09:27:38 +03:00
format . atom { redirect_to :controller = > " account " , :action = > " login " , :back_url = > url }
2010-04-17 16:45:23 +04:00
format . xml { head :unauthorized , 'WWW-Authenticate' = > 'Basic realm="Redmine API"' }
2010-07-05 22:00:50 +04:00
format . js { head :unauthorized , 'WWW-Authenticate' = > 'Basic realm="Redmine API"' }
2010-04-17 16:45:23 +04:00
format . json { head :unauthorized , 'WWW-Authenticate' = > 'Basic realm="Redmine API"' }
2009-12-23 09:27:33 +03:00
end
2007-03-12 20:59:02 +03:00
return false
end
true
end
def require_admin
return unless require_login
2007-08-29 20:52:35 +04:00
if ! User . current . admin?
2007-04-30 23:47:28 +04:00
render_403
2007-03-12 20:59:02 +03:00
return false
end
true
end
2011-05-12 08:25:47 +04:00
2008-08-31 20:34:54 +04:00
def deny_access
User . current . logged? ? render_403 : require_login
end
2007-03-12 20:59:02 +03:00
2007-08-29 20:52:35 +04:00
# Authorize the user for the requested action
2009-05-17 16:59:14 +04:00
def authorize ( ctrl = params [ :controller ] , action = params [ :action ] , global = false )
2010-10-07 09:11:28 +04:00
allowed = User . current . allowed_to? ( { :controller = > ctrl , :action = > action } , @project || @projects , :global = > global )
2010-10-23 13:48:01 +04:00
if allowed
true
else
if @project && @project . archived?
render_403 :message = > :notice_not_authorized_archived_project
else
deny_access
end
end
2007-03-12 20:59:02 +03:00
end
2009-05-17 16:59:14 +04:00
# Authorize the user for the requested action outside a project
def authorize_global ( ctrl = params [ :controller ] , action = params [ :action ] , global = true )
authorize ( ctrl , action , global )
end
2010-02-05 19:57:02 +03:00
# Find project of id params[:id]
def find_project
@project = Project . find ( params [ :id ] )
rescue ActiveRecord :: RecordNotFound
render_404
end
2010-03-16 18:17:47 +03:00
2010-09-10 20:00:49 +04:00
# Find project of id params[:project_id]
def find_project_by_project_id
@project = Project . find ( params [ :project_id ] )
rescue ActiveRecord :: RecordNotFound
render_404
end
2010-04-30 21:24:11 +04:00
# Find a project based on params[:project_id]
# TODO: some subclasses override this, see about merging their logic
def find_optional_project
@project = Project . find ( params [ :project_id ] ) unless params [ :project_id ] . blank?
allowed = User . current . allowed_to? ( { :controller = > params [ :controller ] , :action = > params [ :action ] } , @project , :global = > true )
allowed ? true : deny_access
rescue ActiveRecord :: RecordNotFound
render_404
end
2010-03-16 18:17:47 +03:00
# Finds and sets @project based on @object.project
def find_project_from_association
render_404 unless @object . present?
2011-05-12 08:25:47 +04:00
2010-03-16 18:17:47 +03:00
@project = @object . project
rescue ActiveRecord :: RecordNotFound
render_404
end
2010-03-17 18:41:58 +03:00
def find_model_object
model = self . class . read_inheritable_attribute ( 'model_object' )
if model
@object = model . find ( params [ :id ] )
self . instance_variable_set ( '@' + controller_name . singularize , @object ) if @object
end
rescue ActiveRecord :: RecordNotFound
render_404
end
def self . model_object ( model )
write_inheritable_attribute ( 'model_object' , model )
end
2010-08-12 17:57:51 +04:00
# Filter for bulk issue operations
def find_issues
@issues = Issue . find_all_by_id ( params [ :id ] || params [ :ids ] )
raise ActiveRecord :: RecordNotFound if @issues . empty?
2011-04-11 21:53:15 +04:00
if @issues . detect { | issue | ! issue . visible? }
deny_access
return
end
2010-09-29 09:22:53 +04:00
@projects = @issues . collect ( & :project ) . compact . uniq
@project = @projects . first if @projects . size == 1
rescue ActiveRecord :: RecordNotFound
render_404
end
2011-05-12 08:25:47 +04:00
2010-09-29 09:22:53 +04:00
# Check if project is unique before bulk operations
def check_project_uniqueness
unless @project
2010-08-12 17:57:51 +04:00
# TODO: let users bulk edit/move/destroy issues from different projects
render_error 'Can not bulk edit/move/destroy issues from different projects'
return false
end
end
2011-05-12 08:25:47 +04:00
2007-03-12 20:59:02 +03:00
# make sure that the user is a member of the project (or admin) if project is private
# used as a before_filter for actions that do not require any particular permission on the project
def check_project_privacy
2008-03-12 23:50:48 +03:00
if @project && @project . active?
if @project . is_public? || User . current . member_of? ( @project ) || User . current . admin?
true
else
User . current . logged? ? render_403 : require_login
end
else
2007-05-27 21:42:04 +04:00
@project = nil
render_404
2008-03-12 23:50:48 +03:00
false
2007-05-27 21:42:04 +04:00
end
2007-03-12 20:59:02 +03:00
end
2010-08-04 17:37:44 +04:00
def back_url
params [ :back_url ] || request . env [ 'HTTP_REFERER' ]
end
2006-07-09 20:30:01 +04:00
def redirect_back_or_default ( default )
2008-09-21 16:45:22 +04:00
back_url = CGI . unescape ( params [ :back_url ] . to_s )
2008-07-26 12:46:33 +04:00
if ! back_url . blank?
2008-12-12 19:03:57 +03:00
begin
uri = URI . parse ( back_url )
# do not redirect user to another host or to the login or register page
if ( uri . relative? || ( uri . host == request . host ) ) && ! uri . path . match ( %r{ /(login|account/register) } )
2009-12-18 17:22:18 +03:00
redirect_to ( back_url )
return
2008-12-12 19:03:57 +03:00
end
rescue URI :: InvalidURIError
# redirect to default
2008-07-26 12:46:33 +04:00
end
2006-07-09 20:30:01 +04:00
end
2008-07-26 12:46:33 +04:00
redirect_to default
2011-05-02 03:15:03 +04:00
false
2006-07-09 20:30:01 +04:00
end
2011-05-12 08:25:47 +04:00
2010-10-23 13:48:01 +04:00
def render_403 ( options = { } )
2007-04-30 23:47:28 +04:00
@project = nil
2010-10-23 15:07:04 +04:00
render_error ( { :message = > :notice_not_authorized , :status = > 403 } . merge ( options ) )
2007-04-30 23:47:28 +04:00
return false
end
2011-05-12 08:25:47 +04:00
2010-10-23 15:07:04 +04:00
def render_404 ( options = { } )
render_error ( { :message = > :notice_file_not_found , :status = > 404 } . merge ( options ) )
2007-03-12 20:59:02 +03:00
return false
end
2011-05-12 08:25:47 +04:00
2010-10-23 15:07:04 +04:00
# Renders an error response
def render_error ( arg )
arg = { :message = > arg } unless arg . is_a? ( Hash )
2011-05-12 08:25:47 +04:00
2010-10-23 15:07:04 +04:00
@message = arg [ :message ]
@message = l ( @message ) if @message . is_a? ( Symbol )
@status = arg [ :status ] || 500
2011-05-12 08:25:47 +04:00
2010-01-13 22:29:19 +03:00
respond_to do | format |
2010-10-23 15:07:04 +04:00
format . html {
render :template = > 'common/error' , :layout = > use_layout , :status = > @status
2010-01-13 22:29:19 +03:00
}
2010-10-23 15:07:04 +04:00
format . atom { head @status }
format . xml { head @status }
format . js { head @status }
format . json { head @status }
2010-01-13 22:29:19 +03:00
end
2008-01-23 20:25:11 +03:00
end
2010-08-19 05:01:35 +04:00
# Picks which layout to use based on the request
#
# @return [boolean, string] name of the layout to use or false for no layout
def use_layout
request . xhr? ? false : 'base'
end
2011-05-12 08:25:47 +04:00
2009-11-25 23:45:16 +03:00
def invalid_authenticity_token
2010-01-17 23:23:06 +03:00
if api_request?
logger . error " Form authenticity token is missing or is invalid. API calls must include a proper Content-type header (text/xml or text/json). "
end
2009-11-25 23:45:16 +03:00
render_error " Invalid form authenticity token. "
end
2011-05-12 08:25:47 +04:00
def render_feed ( items , options = { } )
2007-09-01 00:22:36 +04:00
@items = items || [ ]
@items . sort! { | x , y | y . event_datetime < = > x . event_datetime }
2008-03-30 12:33:04 +04:00
@items = @items . slice ( 0 , Setting . feeds_limit . to_i )
2007-08-29 20:52:35 +04:00
@title = options [ :title ] || Setting . app_title
render :template = > " common/feed.atom.rxml " , :layout = > false , :content_type = > 'application/atom+xml'
end
2011-07-09 13:11:13 +04:00
# TODO: remove in Redmine 1.4
2007-08-29 20:52:35 +04:00
def self . accept_key_auth ( * actions )
2011-07-09 22:49:13 +04:00
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. "
2011-07-09 13:11:13 +04:00
accept_rss_auth ( * actions )
2007-08-29 20:52:35 +04:00
end
2011-05-12 08:25:47 +04:00
2011-07-09 13:11:13 +04:00
# TODO: remove in Redmine 1.4
2007-08-29 20:52:35 +04:00
def accept_key_auth_actions
2011-07-09 22:49:13 +04:00
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. "
2011-07-09 13:11:13 +04:00
self . class . accept_rss_auth
end
def self . accept_rss_auth ( * actions )
if actions . any?
write_inheritable_attribute ( 'accept_rss_auth_actions' , actions )
else
read_inheritable_attribute ( 'accept_rss_auth_actions' ) || [ ]
end
end
def accept_rss_auth? ( action = action_name )
self . class . accept_rss_auth . include? ( action . to_sym )
end
def self . accept_api_auth ( * actions )
if actions . any?
write_inheritable_attribute ( 'accept_api_auth_actions' , actions )
else
read_inheritable_attribute ( 'accept_api_auth_actions' ) || [ ]
end
end
def accept_api_auth? ( action = action_name )
self . class . accept_api_auth . include? ( action . to_sym )
2007-08-29 20:52:35 +04:00
end
2011-05-12 08:25:47 +04:00
2007-12-29 14:36:30 +03:00
# Returns the number of objects that should be displayed
# on the paginated list
def per_page_option
per_page = nil
if params [ :per_page ] && Setting . per_page_options_array . include? ( params [ :per_page ] . to_s . to_i )
per_page = params [ :per_page ] . to_s . to_i
session [ :per_page ] = per_page
elsif session [ :per_page ]
per_page = session [ :per_page ]
else
per_page = Setting . per_page_options_array . first || 25
end
per_page
end
2010-12-23 16:33:01 +03:00
# Returns offset and limit used to retrieve objects
# for an API response based on offset, limit and page parameters
def api_offset_and_limit ( options = params )
if options [ :offset ] . present?
offset = options [ :offset ] . to_i
2010-12-11 16:13:49 +03:00
if offset < 0
offset = 0
end
end
2010-12-23 16:33:01 +03:00
limit = options [ :limit ] . to_i
2010-12-11 16:13:49 +03:00
if limit < 1
limit = 25
elsif limit > 100
limit = 100
end
2010-12-23 16:33:01 +03:00
if offset . nil? && options [ :page ] . present?
offset = ( options [ :page ] . to_i - 1 ) * limit
offset = 0 if offset < 0
end
offset || = 0
2011-05-12 08:25:47 +04:00
2010-12-11 16:13:49 +03:00
[ offset , limit ]
end
2011-05-12 08:25:47 +04:00
2007-03-12 20:59:02 +03:00
# qvalues http header parser
# code taken from webrick
def parse_qvalues ( value )
tmp = [ ]
if value
parts = value . split ( / , \ s* / )
parts . each { | part |
if m = %r{ ^([^ \ s,]+?)(?:; \ s*q=( \ d+(?: \ . \ d+)?))?$ } . match ( part )
val = m [ 1 ]
q = ( m [ 2 ] or 1 ) . to_f
tmp . push ( [ val , q ] )
end
}
tmp = tmp . sort_by { | val , q | - q }
tmp . collect! { | val , q | val }
end
return tmp
2009-02-21 14:04:50 +03:00
rescue
nil
2007-03-12 20:59:02 +03:00
end
2011-05-12 08:25:47 +04:00
2008-01-11 01:42:41 +03:00
# Returns a string that can be used as filename value in Content-Disposition header
def filename_for_content_disposition ( name )
request . env [ 'HTTP_USER_AGENT' ] =~ %r{ MSIE } ? ERB :: Util . url_encode ( name ) : name
end
2011-05-12 08:25:47 +04:00
2010-01-17 23:23:06 +03:00
def api_request?
%w( xml json ) . include? params [ :format ]
end
2011-05-12 08:25:47 +04:00
2010-12-23 17:49:14 +03:00
# Returns the API key present in the request
def api_key_from_request
if params [ :key ] . present?
params [ :key ]
elsif request . headers [ " X-Redmine-API-Key " ] . present?
request . headers [ " X-Redmine-API-Key " ]
end
end
2010-03-03 20:05:00 +03:00
# Renders a warning flash if obj has unsaved attachments
def render_attachment_warning_if_needed ( obj )
flash [ :warning ] = l ( :warning_attachments_not_saved , obj . unsaved_attachments . size ) if obj . unsaved_attachments . present?
end
2010-04-29 19:19:19 +04:00
2010-08-13 18:59:04 +04:00
# Sets the `flash` notice or error based the number of issues that did not save
#
# @param [Array, Issue] issues all of the saved and unsaved Issues
# @param [Array, Integer] unsaved_issue_ids the issue ids that were not saved
def set_flash_from_bulk_issue_save ( issues , unsaved_issue_ids )
if unsaved_issue_ids . empty?
flash [ :notice ] = l ( :notice_successful_update ) unless issues . empty?
else
flash [ :error ] = l ( :notice_failed_to_save_issues ,
:count = > unsaved_issue_ids . size ,
:total = > issues . size ,
:ids = > '#' + unsaved_issue_ids . join ( ', #' ) )
end
end
2010-04-29 19:19:19 +04:00
# Rescues an invalid query statement. Just in case...
def query_statement_invalid ( exception )
logger . error " Query::StatementInvalid: #{ exception . message } " if logger
session . delete ( :query )
sort_clear if respond_to? ( :sort_clear )
render_error " An error occurred while executing the query and has been logged. Please report this error to your Redmine administrator. "
end
2010-06-05 07:52:59 +04:00
# Converts the errors on an ActiveRecord object into a common JSON format
def object_errors_to_json ( object )
object . errors . collect do | attribute , error |
{ attribute = > error }
end . to_json
end
2010-12-03 14:51:06 +03:00
# Renders API response on validation failure
def render_validation_errors ( object )
options = { :status = > :unprocessable_entity , :layout = > false }
options . merge! ( case params [ :format ]
when 'xml' ; { :xml = > object . errors }
when 'json' ; { :json = > { 'errors' = > object . errors } } # ActiveResource client compliance
else
raise " Unknown format #{ params [ :format ] } in # render_validation_errors "
end
)
render options
end
2011-05-12 08:25:47 +04:00
2010-12-04 20:43:39 +03:00
# Overrides #default_template so that the api template
# is used automatically if it exists
def default_template ( action_name = self . action_name )
if api_request?
begin
return self . view_paths . find_template ( default_template_name ( action_name ) , 'api' )
rescue :: ActionView :: MissingTemplate
# the api template was not found
# fallback to the default behaviour
end
end
super
end
2011-05-12 08:25:47 +04:00
2010-12-04 20:43:39 +03:00
# Overrides #pick_layout so that #render with no arguments
# doesn't use the layout for api requests
def pick_layout ( * args )
api_request? ? nil : super
end
2007-08-29 20:52:35 +04:00
end