application_controller.rb
677 lines
| 19.7 KiB
| text/x-ruby
|
RubyLexer
|
r5296 | # Redmine - project management software | ||
|
r14856 | # Copyright (C) 2006-2016 Jean-Philippe Lang | ||
|
r330 | # | ||
# 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. | ||||
|
r5629 | # | ||
|
r330 | # 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. | ||||
|
r5629 | # | ||
|
r330 | # 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. | ||||
|
r1686 | require 'uri' | ||
|
r1891 | require 'cgi' | ||
|
r1686 | |||
|
r6043 | class Unauthorized < Exception; end | ||
|
r330 | class ApplicationController < ActionController::Base | ||
|
r2430 | include Redmine::I18n | ||
|
r10797 | include Redmine::Pagination | ||
|
r14311 | include Redmine::Hook::Helper | ||
|
r10845 | include RoutesHelper | ||
helper :routes | ||||
|
r10355 | |||
|
r9346 | class_attribute :accept_api_auth_actions | ||
class_attribute :accept_rss_auth_actions | ||||
class_attribute :model_object | ||||
|
r2773 | |||
|
r1726 | layout 'base' | ||
|
r6405 | |||
|
r6195 | protect_from_forgery | ||
|
r12080 | |||
def verify_authenticity_token | ||||
unless api_request? | ||||
super | ||||
end | ||||
end | ||||
|
r6196 | def handle_unverified_request | ||
|
r12080 | unless api_request? | ||
super | ||||
cookies.delete(autologin_cookie_name) | ||||
|
r12766 | self.logged_user = nil | ||
|
r13288 | set_localization | ||
|
r12080 | render_error :status => 422, :message => "Invalid form authenticity token." | ||
|
r12037 | end | ||
|
r6196 | end | ||
|
r5629 | |||
|
r15393 | before_action :session_expiration, :user_setup, :check_if_login_required, :set_localization, :check_password_change | ||
|
r5629 | |||
|
r6043 | rescue_from ::Unauthorized, :with => :deny_access | ||
|
r10021 | rescue_from ::ActionView::MissingTemplate, :with => :missing_template | ||
|
r5629 | |||
|
r2829 | include Redmine::Search::Controller | ||
|
r1062 | include Redmine::MenuManager::MenuController | ||
helper Redmine::MenuManager::MenuHelper | ||||
|
r5629 | |||
|
r13951 | include Redmine::SudoMode::Controller | ||
|
r9614 | def session_expiration | ||
|
r14353 | if session[:user_id] && Rails.application.config.redmine_verify_sessions != false | ||
|
r9614 | if session_expired? && !try_to_autologin | ||
|
r13028 | set_localization(User.active.find_by_id(session[:user_id])) | ||
|
r13863 | self.logged_user = nil | ||
|
r9614 | flash[:error] = l(:error_session_expired) | ||
|
r13863 | require_login | ||
|
r9614 | end | ||
end | ||||
end | ||||
def session_expired? | ||||
|
r14353 | ! User.verify_session_token(session[:user_id], session[:tk]) | ||
|
r9614 | end | ||
def start_user_session(user) | ||||
session[:user_id] = user.id | ||||
|
r14353 | session[:tk] = user.generate_session_token | ||
|
r11851 | if user.must_change_password? | ||
session[:pwd] = '1' | ||||
end | ||||
|
r9614 | end | ||
|
r663 | def user_setup | ||
|
r1016 | # Check the settings cache for each request | ||
|
r674 | Setting.check_cache | ||
|
r1016 | # Find the current user | ||
|
r2679 | User.current = find_current_user | ||
|
r10156 | logger.info(" Current user: " + (User.current.logged? ? "#{User.current.login} (id=#{User.current.id})" : "anonymous")) if logger | ||
|
r1016 | end | ||
|
r5629 | |||
|
r1016 | # Returns the current user or nil if no user is logged in | ||
|
r2679 | # and starts a session if needed | ||
|
r1016 | def find_current_user | ||
|
r9902 | user = nil | ||
unless api_request? | ||||
|
r10355 | if session[:user_id] | ||
|
r9902 | # existing session | ||
user = (User.active.find(session[:user_id]) rescue nil) | ||||
elsif autologin_user = try_to_autologin | ||||
user = autologin_user | ||||
elsif params[:format] == 'atom' && params[:key] && request.get? && accept_rss_auth? | ||||
# RSS key authentication does not start a session | ||||
user = User.find_by_rss_key(params[:key]) | ||||
end | ||||
end | ||||
if user.nil? && Setting.rest_api_enabled? && accept_api_auth? | ||||
|
r6077 | if (key = api_key_from_request) | ||
|
r3105 | # Use API key | ||
|
r9902 | user = User.find_by_api_key(key) | ||
|
r12640 | elsif request.authorization.to_s =~ /\ABasic /i | ||
|
r3105 | # HTTP Basic, either username/password or API key/random | ||
authenticate_with_http_basic do |username, password| | ||||
|
r9902 | user = User.try_to_login(username, password) || User.find_by_api_key(username) | ||
|
r3105 | end | ||
|
r11851 | if user && user.must_change_password? | ||
render_error :message => 'You must change your password', :status => 403 | ||||
return | ||||
end | ||||
|
r3105 | end | ||
|
r10397 | # Switch user if requested by an admin user | ||
if user && user.admin? && (username = api_switch_user_from_request) | ||||
su = User.find_by_login(username) | ||||
if su && su.active? | ||||
logger.info(" User switched by: #{user.login} (id=#{user.id})") if logger | ||||
user = su | ||||
else | ||||
render_error :message => 'Invalid X-Redmine-Switch-User header', :status => 412 | ||||
end | ||||
end | ||||
|
r330 | end | ||
|
r14763 | # store current ip address in user object ephemerally | ||
user.remote_ip = request.remote_ip if user | ||||
|
r9902 | user | ||
|
r330 | end | ||
|
r3105 | |||
|
r11289 | def autologin_cookie_name | ||
Redmine::Configuration['autologin_cookie_name'].presence || 'autologin' | ||||
end | ||||
|
r9614 | def try_to_autologin | ||
|
r11289 | if cookies[autologin_cookie_name] && Setting.autologin? | ||
|
r9614 | # auto-login feature starts a new session | ||
|
r11289 | user = User.try_to_autologin(cookies[autologin_cookie_name]) | ||
|
r9614 | if user | ||
reset_session | ||||
start_user_session(user) | ||||
end | ||||
user | ||||
end | ||||
end | ||||
|
r2460 | # Sets the logged in user | ||
def logged_user=(user) | ||||
|
r2966 | reset_session | ||
|
r2460 | if user && user.is_a?(User) | ||
User.current = user | ||||
|
r9614 | start_user_session(user) | ||
|
r2460 | else | ||
User.current = User.anonymous | ||||
end | ||||
end | ||||
|
r5629 | |||
|
r9283 | # Logs out current user | ||
def logout_user | ||||
if User.current.logged? | ||||
|
r11290 | cookies.delete(autologin_cookie_name) | ||
|
r15293 | Token.where(["user_id = ? AND action = ?", User.current.id, 'autologin']).delete_all | ||
Token.where(["user_id = ? AND action = ? AND value = ?", User.current.id, 'session', session[:tk]]).delete_all | ||||
|
r9283 | self.logged_user = nil | ||
end | ||||
end | ||||
|
r330 | # check if login is globally required to access the application | ||
def check_if_login_required | ||||
|
r511 | # no check needed if user is already logged in | ||
|
r663 | return true if User.current.logged? | ||
|
r330 | require_login if Setting.login_required? | ||
|
r5629 | end | ||
|
r11851 | def check_password_change | ||
if session[:pwd] | ||||
if User.current.must_change_password? | ||||
|
r13882 | flash[:error] = l(:error_password_expired) | ||
|
r11851 | redirect_to my_password_path | ||
else | ||||
session.delete(:pwd) | ||||
end | ||||
end | ||||
end | ||||
|
r13028 | def set_localization(user=User.current) | ||
|
r2430 | lang = nil | ||
|
r13028 | if user && user.logged? | ||
lang = find_language(user.language) | ||||
|
r2430 | end | ||
|
r12416 | if lang.nil? && !Setting.force_default_language_for_anonymous? && request.env['HTTP_ACCEPT_LANGUAGE'] | ||
|
r3588 | accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first | ||
|
r2430 | if !accept_lang.blank? | ||
|
r3588 | accept_lang = accept_lang.downcase | ||
|
r2430 | lang = find_language(accept_lang) || find_language(accept_lang.split('-').first) | ||
|
r330 | end | ||
|
r2430 | end | ||
lang ||= Setting.default_language | ||||
set_language_if_valid(lang) | ||||
|
r330 | end | ||
|
r5629 | |||
|
r330 | def require_login | ||
|
r663 | if !User.current.logged? | ||
|
r2936 | # Extract only the basic url parameters on non-GET requests | ||
if request.get? | ||||
|
r15277 | url = request.original_url | ||
|
r2936 | else | ||
url = url_for(:controller => params[:controller], :action => params[:action], :id => params[:id], :project_id => params[:project_id]) | ||||
end | ||||
|
r3104 | respond_to do |format| | ||
|
r11732 | format.html { | ||
if request.xhr? | ||||
head :unauthorized | ||||
else | ||||
|
r13688 | redirect_to signin_path(:back_url => url) | ||
|
r11732 | end | ||
} | ||||
|
r13687 | format.any(:atom, :pdf, :csv) { | ||
|
r13688 | redirect_to signin_path(:back_url => url) | ||
|
r13687 | } | ||
|
r3565 | format.xml { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' } | ||
|
r3713 | format.js { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' } | ||
|
r3565 | format.json { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' } | ||
|
r13687 | format.any { head :unauthorized } | ||
|
r3104 | end | ||
|
r330 | return false | ||
end | ||||
true | ||||
end | ||||
def require_admin | ||||
return unless require_login | ||||
|
r663 | if !User.current.admin? | ||
|
r492 | render_403 | ||
|
r330 | return false | ||
end | ||||
true | ||||
end | ||||
|
r5629 | |||
|
r1777 | def deny_access | ||
User.current.logged? ? render_403 : require_login | ||||
end | ||||
|
r330 | |||
|
r663 | # Authorize the user for the requested action | ||
|
r2651 | def authorize(ctrl = params[:controller], action = params[:action], global = false) | ||
|
r4122 | allowed = User.current.allowed_to?({:controller => ctrl, :action => action}, @project || @projects, :global => global) | ||
|
r4171 | if allowed | ||
true | ||||
else | ||||
if @project && @project.archived? | ||||
render_403 :message => :notice_not_authorized_archived_project | ||||
else | ||||
deny_access | ||||
end | ||||
end | ||||
|
r330 | end | ||
|
r2651 | |||
# 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 | ||||
|
r3256 | |||
# Find project of id params[:id] | ||||
def find_project | ||||
@project = Project.find(params[:id]) | ||||
rescue ActiveRecord::RecordNotFound | ||||
render_404 | ||||
end | ||||
|
r3477 | |||
|
r3961 | # 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 | ||||
|
r3602 | # 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 | ||||
|
r3477 | # Finds and sets @project based on @object.project | ||
def find_project_from_association | ||||
render_404 unless @object.present? | ||||
|
r5629 | |||
|
r3477 | @project = @object.project | ||
end | ||||
|
r3483 | def find_model_object | ||
|
r9346 | model = self.class.model_object | ||
|
r3483 | 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) | ||||
|
r9346 | self.model_object = model | ||
|
r3483 | end | ||
|
r3824 | |||
|
r10677 | # Find the issue whose id is the :id parameter | ||
# Raises a Unauthorized exception if the issue is not visible | ||||
def find_issue | ||||
# Issue.visible.find(...) can not be used to redirect user to the login form | ||||
# if the issue actually exists but requires authentication | ||||
@issue = Issue.find(params[:id]) | ||||
raise Unauthorized unless @issue.visible? | ||||
@project = @issue.project | ||||
rescue ActiveRecord::RecordNotFound | ||||
render_404 | ||||
end | ||||
# Find issues with a single :id param or :ids array param | ||||
# Raises a Unauthorized exception if one of the issues is not visible | ||||
|
r3824 | def find_issues | ||
|
r14348 | @issues = Issue. | ||
where(:id => (params[:id] || params[:ids])). | ||||
preload(:project, :status, :tracker, :priority, :author, :assigned_to, :relations_to, {:custom_values => :custom_field}). | ||||
to_a | ||||
|
r3824 | raise ActiveRecord::RecordNotFound if @issues.empty? | ||
|
r10678 | raise Unauthorized unless @issues.all?(&:visible?) | ||
|
r4114 | @projects = @issues.collect(&:project).compact.uniq | ||
@project = @projects.first if @projects.size == 1 | ||||
rescue ActiveRecord::RecordNotFound | ||||
render_404 | ||||
end | ||||
|
r5629 | |||
|
r10748 | def find_attachments | ||
if (attachments = params[:attachments]).present? | ||||
att = attachments.values.collect do |attachment| | ||||
Attachment.find_by_token( attachment[:token] ) if attachment[:token].present? | ||||
end | ||||
att.compact! | ||||
end | ||||
@attachments = att || [] | ||||
end | ||||
|
r15152 | def parse_params_for_bulk_update(params) | ||
attributes = (params || {}).reject {|k,v| v.blank?} | ||||
attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'} | ||||
if custom = attributes[:custom_field_values] | ||||
custom.reject! {|k,v| v.blank?} | ||||
custom.keys.each do |k| | ||||
if custom[k].is_a?(Array) | ||||
custom[k] << '' if custom[k].delete('__none__') | ||||
else | ||||
custom[k] = '' if custom[k] == '__none__' | ||||
end | ||||
end | ||||
end | ||||
attributes | ||||
end | ||||
|
r330 | # make sure that the user is a member of the project (or admin) if project is private | ||
|
r15273 | # used as a before_action for actions that do not require any particular permission on the project | ||
|
r330 | def check_project_privacy | ||
|
r9700 | if @project && !@project.archived? | ||
|
r8833 | if @project.visible? | ||
|
r1223 | true | ||
else | ||||
|
r7859 | deny_access | ||
|
r1223 | end | ||
else | ||||
|
r546 | @project = nil | ||
render_404 | ||||
|
r1223 | false | ||
|
r546 | end | ||
|
r330 | end | ||
|
r3798 | def back_url | ||
|
r10056 | url = params[:back_url] | ||
if url.nil? && referer = request.env['HTTP_REFERER'] | ||||
url = CGI.unescape(referer.to_s) | ||||
end | ||||
url | ||||
|
r3798 | end | ||
|
r12430 | def redirect_back_or_default(default, options={}) | ||
|
r10056 | back_url = params[:back_url].to_s | ||
|
r14178 | if back_url.present? && valid_url = validate_back_url(back_url) | ||
redirect_to(valid_url) | ||||
|
r12938 | return | ||
|
r12430 | elsif options[:referer] | ||
redirect_to_referer_or default | ||||
return | ||||
|
r5 | end | ||
|
r1686 | redirect_to default | ||
|
r5491 | false | ||
|
r5 | end | ||
|
r5629 | |||
|
r14178 | # Returns a validated URL string if back_url is a valid url for redirection, | ||
# otherwise false | ||||
def validate_back_url(back_url) | ||||
|
r12938 | if CGI.unescape(back_url).include?('..') | ||
return false | ||||
end | ||||
begin | ||||
uri = URI.parse(back_url) | ||||
rescue URI::InvalidURIError | ||||
return false | ||||
end | ||||
|
r14178 | [:scheme, :host, :port].each do |component| | ||
if uri.send(component).present? && uri.send(component) != request.send(component) | ||||
return false | ||||
end | ||||
uri.send(:"#{component}=", nil) | ||||
end | ||||
# Always ignore basic user:password in the URL | ||||
uri.userinfo = nil | ||||
path = uri.to_s | ||||
# Ensure that the remaining URL starts with a slash, followed by a | ||||
# non-slash character or the end | ||||
if path !~ %r{\A/([^/]|\z)} | ||||
|
r12938 | return false | ||
end | ||||
|
r15769 | if path.match(%r{/(login|account/register|account/lost_password)}) | ||
|
r12938 | return false | ||
end | ||||
|
r14178 | if relative_url_root.present? && !path.starts_with?(relative_url_root) | ||
|
r12938 | return false | ||
end | ||||
|
r14178 | return path | ||
end | ||||
private :validate_back_url | ||||
def valid_back_url?(back_url) | ||||
!!validate_back_url(back_url) | ||||
|
r12938 | end | ||
private :valid_back_url? | ||||
|
r9229 | # Redirects to the request referer if present, redirects to args or call block otherwise. | ||
def redirect_to_referer_or(*args, &block) | ||||
redirect_to :back | ||||
rescue ::ActionController::RedirectBackError | ||||
if args.any? | ||||
redirect_to *args | ||||
elsif block_given? | ||||
block.call | ||||
else | ||||
raise "#redirect_to_referer_or takes arguments or a block" | ||||
end | ||||
end | ||||
|
r4171 | def render_403(options={}) | ||
|
r492 | @project = nil | ||
|
r4172 | render_error({:message => :notice_not_authorized, :status => 403}.merge(options)) | ||
|
r492 | return false | ||
end | ||||
|
r5629 | |||
|
r4172 | def render_404(options={}) | ||
render_error({:message => :notice_file_not_found, :status => 404}.merge(options)) | ||||
|
r330 | return false | ||
end | ||||
|
r5629 | |||
|
r4172 | # Renders an error response | ||
def render_error(arg) | ||||
arg = {:message => arg} unless arg.is_a?(Hash) | ||||
|
r5629 | |||
|
r4172 | @message = arg[:message] | ||
@message = l(@message) if @message.is_a?(Symbol) | ||||
@status = arg[:status] || 500 | ||||
|
r5629 | |||
|
r3196 | respond_to do |format| | ||
|
r4172 | format.html { | ||
render :template => 'common/error', :layout => use_layout, :status => @status | ||||
|
r3196 | } | ||
|
r10021 | format.any { head @status } | ||
|
r3196 | end | ||
|
r1080 | end | ||
|
r10021 | |||
# Handler for ActionView::MissingTemplate exception | ||||
def missing_template | ||||
logger.warn "Missing template, responding with 404" | ||||
@project = nil | ||||
render_404 | ||||
end | ||||
|
r7757 | # Filter for actions that provide an API response | ||
# but have no HTML representation for non admin users | ||||
def require_admin_or_api_request | ||||
return true if api_request? | ||||
if User.current.admin? | ||||
true | ||||
elsif User.current.logged? | ||||
render_error(:status => 406) | ||||
else | ||||
deny_access | ||||
end | ||||
end | ||||
|
r3835 | |||
# 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 | ||||
|
r5629 | |||
def render_feed(items, options={}) | ||||
|
r13100 | @items = (items || []).to_a | ||
|
r675 | @items.sort! {|x,y| y.event_datetime <=> x.event_datetime } | ||
|
r1295 | @items = @items.slice(0, Setting.feeds_limit.to_i) | ||
|
r663 | @title = options[:title] || Setting.app_title | ||
|
r10350 | render :template => "common/feed", :formats => [:atom], :layout => false, | ||
|
r7453 | :content_type => 'application/atom+xml' | ||
|
r663 | end | ||
|
r6405 | |||
|
r6077 | def self.accept_rss_auth(*actions) | ||
if actions.any? | ||||
|
r9346 | self.accept_rss_auth_actions = actions | ||
|
r6077 | else | ||
|
r9346 | self.accept_rss_auth_actions || [] | ||
|
r6077 | end | ||
end | ||||
|
r6405 | |||
|
r6077 | def accept_rss_auth?(action=action_name) | ||
self.class.accept_rss_auth.include?(action.to_sym) | ||||
end | ||||
|
r6405 | |||
|
r6077 | def self.accept_api_auth(*actions) | ||
if actions.any? | ||||
|
r9346 | self.accept_api_auth_actions = actions | ||
|
r6077 | else | ||
|
r9346 | self.accept_api_auth_actions || [] | ||
|
r6077 | end | ||
end | ||||
|
r6405 | |||
|
r6077 | def accept_api_auth?(action=action_name) | ||
self.class.accept_api_auth.include?(action.to_sym) | ||||
|
r663 | end | ||
|
r5629 | |||
|
r1013 | # 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 | ||||
|
r4457 | # 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 | ||||
|
r4375 | if offset < 0 | ||
offset = 0 | ||||
end | ||||
end | ||||
|
r4457 | limit = options[:limit].to_i | ||
|
r4375 | if limit < 1 | ||
limit = 25 | ||||
elsif limit > 100 | ||||
limit = 100 | ||||
end | ||||
|
r4457 | if offset.nil? && options[:page].present? | ||
offset = (options[:page].to_i - 1) * limit | ||||
offset = 0 if offset < 0 | ||||
end | ||||
offset ||= 0 | ||||
|
r5629 | |||
|
r4375 | [offset, limit] | ||
end | ||||
|
r5629 | |||
|
r330 | # 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 | ||||
|
r2430 | rescue | ||
nil | ||||
|
r330 | end | ||
|
r5629 | |||
|
r1039 | # Returns a string that can be used as filename value in Content-Disposition header | ||
def filename_for_content_disposition(name) | ||||
|
r14631 | request.env['HTTP_USER_AGENT'] =~ %r{(MSIE|Trident|Edge)} ? ERB::Util.url_encode(name) : name | ||
|
r1039 | end | ||
|
r5629 | |||
|
r3218 | def api_request? | ||
%w(xml json).include? params[:format] | ||||
end | ||||
|
r5629 | |||
|
r4459 | # Returns the API key present in the request | ||
def api_key_from_request | ||||
if params[:key].present? | ||||
|
r9615 | params[:key].to_s | ||
|
r4459 | elsif request.headers["X-Redmine-API-Key"].present? | ||
|
r9615 | request.headers["X-Redmine-API-Key"].to_s | ||
|
r4459 | end | ||
end | ||||
|
r3414 | |||
|
r10397 | # Returns the API 'switch user' value if present | ||
def api_switch_user_from_request | ||||
request.headers["X-Redmine-Switch-User"].to_s.presence | ||||
end | ||||
|
r3414 | # 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 | ||||
|
r3582 | |||
# 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 | ||||
|
r9792 | # Renders a 200 response for successfull updates or deletions via the API | ||
def render_api_ok | ||||
|
r10505 | render_api_head :ok | ||
end | ||||
# Renders a head API response | ||||
def render_api_head(status) | ||||
|
r15349 | head :status => status | ||
|
r9792 | end | ||
|
r4341 | # Renders API response on validation failure | ||
|
r13403 | # for an object or an array of objects | ||
|
r8975 | def render_validation_errors(objects) | ||
|
r13403 | messages = Array.wrap(objects).map {|object| object.errors.full_messages}.flatten | ||
render_api_errors(messages) | ||||
end | ||||
def render_api_errors(*messages) | ||||
@error_messages = messages.flatten | ||||
|
r9346 | render :template => 'common/error_messages.api', :status => :unprocessable_entity, :layout => nil | ||
|
r4352 | end | ||
|
r5629 | |||
|
r9346 | # Overrides #_include_layout? so that #render with no arguments | ||
|
r4352 | # doesn't use the layout for api requests | ||
|
r9346 | def _include_layout?(*args) | ||
api_request? ? false : super | ||||
|
r4352 | end | ||
|
r663 | end | ||