application_controller.rb
367 lines
| 11.7 KiB
| text/x-ruby
|
RubyLexer
|
r330 | # redMine - project management software | ||
# Copyright (C) 2006-2007 Jean-Philippe Lang | ||||
# | ||||
# 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. | ||||
# | ||||
# 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. | ||||
# | ||||
# 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 | |||
|
r330 | class ApplicationController < ActionController::Base | ||
|
r2430 | include Redmine::I18n | ||
|
r2773 | |||
|
r1726 | layout 'base' | ||
|
r3199 | exempt_from_layout 'builder' | ||
|
r1726 | |||
|
r2979 | # 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'] !~ /--/ | ||||
cookies.delete '_redmine_session' | ||||
|
r3071 | redirect_to home_path | ||
return false | ||||
|
r2979 | end | ||
end | ||||
|
r663 | before_filter :user_setup, :check_if_login_required, :set_localization | ||
|
r330 | filter_parameter_logging :password | ||
|
r2937 | protect_from_forgery | ||
|
r330 | |||
|
r2980 | rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token | ||
|
r2829 | include Redmine::Search::Controller | ||
|
r1062 | include Redmine::MenuManager::MenuController | ||
helper Redmine::MenuManager::MenuHelper | ||||
|
r3326 | Redmine::Scm::Base.all.each do |scm| | ||
|
r558 | require_dependency "repository/#{scm.underscore}" | ||
end | ||||
|
r2979 | |||
|
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 | ||
|
r1016 | end | ||
# Returns the current user or nil if no user is logged in | ||||
|
r2679 | # and starts a session if needed | ||
|
r1016 | def find_current_user | ||
|
r330 | if session[:user_id] | ||
|
r663 | # existing session | ||
|
r2077 | (User.active.find(session[:user_id]) rescue nil) | ||
|
r663 | elsif cookies[:autologin] && Setting.autologin? | ||
|
r2679 | # auto-login feature starts a new session | ||
user = User.try_to_autologin(cookies[:autologin]) | ||||
session[:user_id] = user.id if user | ||||
user | ||||
elsif params[:format] == 'atom' && params[:key] && accept_key_auth_actions.include?(params[:action]) | ||||
# RSS key authentication does not start a session | ||||
|
r1016 | User.find_by_rss_key(params[:key]) | ||
|
r3196 | elsif Setting.rest_api_enabled? && ['xml', 'json'].include?(params[:format]) | ||
if params[:key].present? && accept_key_auth_actions.include?(params[:action]) | ||||
|
r3105 | # Use API key | ||
User.find_by_api_key(params[:key]) | ||||
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 | ||||
|
r330 | end | ||
end | ||||
|
r3105 | |||
|
r2460 | # Sets the logged in user | ||
def logged_user=(user) | ||||
|
r2966 | reset_session | ||
|
r2460 | if user && user.is_a?(User) | ||
User.current = user | ||||
session[:user_id] = user.id | ||||
else | ||||
User.current = User.anonymous | ||||
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? | ||
end | ||||
def set_localization | ||||
|
r2430 | lang = nil | ||
if User.current.logged? | ||||
lang = find_language(User.current.language) | ||||
end | ||||
if lang.nil? && 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 | ||
def require_login | ||||
|
r663 | if !User.current.logged? | ||
|
r2936 | # 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 | ||||
|
r3104 | respond_to do |format| | ||
format.html { redirect_to :controller => "account", :action => "login", :back_url => url } | ||||
|
r3105 | format.atom { redirect_to :controller => "account", :action => "login", :back_url => url } | ||
|
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"' } | ||
|
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 | ||||
|
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) | ||
allowed = User.current.allowed_to?({:controller => ctrl, :action => action}, @project, :global => global) | ||||
|
r1777 | allowed ? true : deny_access | ||
|
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 | |||
|
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? | ||||
@project = @object.project | ||||
rescue ActiveRecord::RecordNotFound | ||||
render_404 | ||||
end | ||||
|
r3483 | 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 | ||||
|
r330 | # 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 | ||||
|
r1223 | 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 | ||||
|
r546 | @project = nil | ||
render_404 | ||||
|
r1223 | false | ||
|
r546 | end | ||
|
r330 | end | ||
|
r3798 | def back_url | ||
params[:back_url] || request.env['HTTP_REFERER'] | ||||
end | ||||
|
r5 | def redirect_back_or_default(default) | ||
|
r1891 | back_url = CGI.unescape(params[:back_url].to_s) | ||
|
r1686 | if !back_url.blank? | ||
|
r2124 | 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)}) | ||||
|
r3071 | redirect_to(back_url) | ||
return | ||||
|
r2124 | end | ||
rescue URI::InvalidURIError | ||||
# redirect to default | ||||
|
r1686 | end | ||
|
r5 | end | ||
|
r1686 | redirect_to default | ||
|
r5 | end | ||
|
r330 | |||
|
r492 | def render_403 | ||
@project = nil | ||||
|
r3196 | respond_to do |format| | ||
format.html { render :template => "common/403", :layout => (request.xhr? ? false : 'base'), :status => 403 } | ||||
format.atom { head 403 } | ||||
format.xml { head 403 } | ||||
|
r3713 | format.js { head 403 } | ||
|
r3196 | format.json { head 403 } | ||
end | ||||
|
r492 | return false | ||
end | ||||
|
r330 | def render_404 | ||
|
r3196 | respond_to do |format| | ||
format.html { render :template => "common/404", :layout => !request.xhr?, :status => 404 } | ||||
format.atom { head 404 } | ||||
format.xml { head 404 } | ||||
|
r3713 | format.js { head 404 } | ||
|
r3196 | format.json { head 404 } | ||
end | ||||
|
r330 | return false | ||
end | ||||
|
r663 | |||
|
r1080 | def render_error(msg) | ||
|
r3196 | respond_to do |format| | ||
format.html { | ||||
flash.now[:error] = msg | ||||
render :text => '', :layout => !request.xhr?, :status => 500 | ||||
} | ||||
format.atom { head 500 } | ||||
format.xml { head 500 } | ||||
|
r3713 | format.js { head 500 } | ||
|
r3196 | format.json { head 500 } | ||
end | ||||
|
r1080 | end | ||
|
r2980 | def invalid_authenticity_token | ||
|
r3218 | 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 | ||||
|
r2980 | render_error "Invalid form authenticity token." | ||
end | ||||
|
r675 | def render_feed(items, options={}) | ||
@items = items || [] | ||||
@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 | ||
render :template => "common/feed.atom.rxml", :layout => false, :content_type => 'application/atom+xml' | ||||
end | ||||
def self.accept_key_auth(*actions) | ||||
actions = actions.flatten.map(&:to_s) | ||||
write_inheritable_attribute('accept_key_auth_actions', actions) | ||||
end | ||||
def accept_key_auth_actions | ||||
self.class.read_inheritable_attribute('accept_key_auth_actions') || [] | ||||
end | ||||
|
r977 | |||
|
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 | ||||
|
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 | ||
|
r1039 | |||
# 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 | ||||
|
r3218 | |||
def api_request? | ||||
%w(xml json).include? params[:format] | ||||
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 | ||||
|
r3652 | # 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 | ||||
|
r663 | end | ||