annotate app/controllers/application_controller.rb @ 8:0c83d98252d9 yuya

* Add custom repo prefix and proper auth realm, remove auth cache (seems like an unwise feature), pass DB handle around, various other bits of tidying
author Chris Cannam
date Thu, 12 Aug 2010 15:31:37 +0100
parents 513646585e45
children 1d32c0a0efbf
rev   line source
Chris@0 1 # redMine - project management software
Chris@0 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
Chris@0 3 #
Chris@0 4 # This program is free software; you can redistribute it and/or
Chris@0 5 # modify it under the terms of the GNU General Public License
Chris@0 6 # as published by the Free Software Foundation; either version 2
Chris@0 7 # of the License, or (at your option) any later version.
Chris@0 8 #
Chris@0 9 # This program is distributed in the hope that it will be useful,
Chris@0 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
Chris@0 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Chris@0 12 # GNU General Public License for more details.
Chris@0 13 #
Chris@0 14 # You should have received a copy of the GNU General Public License
Chris@0 15 # along with this program; if not, write to the Free Software
Chris@0 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Chris@0 17
Chris@0 18 require 'uri'
Chris@0 19 require 'cgi'
Chris@0 20
Chris@0 21 class ApplicationController < ActionController::Base
Chris@0 22 include Redmine::I18n
Chris@0 23
Chris@0 24 layout 'base'
Chris@0 25 exempt_from_layout 'builder'
Chris@0 26
Chris@0 27 # Remove broken cookie after upgrade from 0.8.x (#4292)
Chris@0 28 # See https://rails.lighthouseapp.com/projects/8994/tickets/3360
Chris@0 29 # TODO: remove it when Rails is fixed
Chris@0 30 before_filter :delete_broken_cookies
Chris@0 31 def delete_broken_cookies
Chris@0 32 if cookies['_redmine_session'] && cookies['_redmine_session'] !~ /--/
Chris@0 33 cookies.delete '_redmine_session'
Chris@0 34 redirect_to home_path
Chris@0 35 return false
Chris@0 36 end
Chris@0 37 end
Chris@0 38
Chris@0 39 before_filter :user_setup, :check_if_login_required, :set_localization
Chris@0 40 filter_parameter_logging :password
Chris@0 41 protect_from_forgery
Chris@0 42
Chris@0 43 rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
Chris@0 44
Chris@0 45 include Redmine::Search::Controller
Chris@0 46 include Redmine::MenuManager::MenuController
Chris@0 47 helper Redmine::MenuManager::MenuHelper
Chris@0 48
Chris@0 49 Redmine::Scm::Base.all.each do |scm|
Chris@0 50 require_dependency "repository/#{scm.underscore}"
Chris@0 51 end
Chris@0 52
Chris@0 53 def user_setup
Chris@0 54 # Check the settings cache for each request
Chris@0 55 Setting.check_cache
Chris@0 56 # Find the current user
Chris@0 57 User.current = find_current_user
Chris@0 58 end
Chris@0 59
Chris@0 60 # Returns the current user or nil if no user is logged in
Chris@0 61 # and starts a session if needed
Chris@0 62 def find_current_user
Chris@0 63 if session[:user_id]
Chris@0 64 # existing session
Chris@0 65 (User.active.find(session[:user_id]) rescue nil)
Chris@0 66 elsif cookies[:autologin] && Setting.autologin?
Chris@0 67 # auto-login feature starts a new session
Chris@0 68 user = User.try_to_autologin(cookies[:autologin])
Chris@0 69 session[:user_id] = user.id if user
Chris@0 70 user
Chris@0 71 elsif params[:format] == 'atom' && params[:key] && accept_key_auth_actions.include?(params[:action])
Chris@0 72 # RSS key authentication does not start a session
Chris@0 73 User.find_by_rss_key(params[:key])
Chris@0 74 elsif Setting.rest_api_enabled? && ['xml', 'json'].include?(params[:format])
Chris@0 75 if params[:key].present? && accept_key_auth_actions.include?(params[:action])
Chris@0 76 # Use API key
Chris@0 77 User.find_by_api_key(params[:key])
Chris@0 78 else
Chris@0 79 # HTTP Basic, either username/password or API key/random
Chris@0 80 authenticate_with_http_basic do |username, password|
Chris@0 81 User.try_to_login(username, password) || User.find_by_api_key(username)
Chris@0 82 end
Chris@0 83 end
Chris@0 84 end
Chris@0 85 end
Chris@0 86
Chris@0 87 # Sets the logged in user
Chris@0 88 def logged_user=(user)
Chris@0 89 reset_session
Chris@0 90 if user && user.is_a?(User)
Chris@0 91 User.current = user
Chris@0 92 session[:user_id] = user.id
Chris@0 93 else
Chris@0 94 User.current = User.anonymous
Chris@0 95 end
Chris@0 96 end
Chris@0 97
Chris@0 98 # check if login is globally required to access the application
Chris@0 99 def check_if_login_required
Chris@0 100 # no check needed if user is already logged in
Chris@0 101 return true if User.current.logged?
Chris@0 102 require_login if Setting.login_required?
Chris@0 103 end
Chris@0 104
Chris@0 105 def set_localization
Chris@0 106 lang = nil
Chris@0 107 if User.current.logged?
Chris@0 108 lang = find_language(User.current.language)
Chris@0 109 end
Chris@0 110 if lang.nil? && request.env['HTTP_ACCEPT_LANGUAGE']
Chris@0 111 accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first
Chris@0 112 if !accept_lang.blank?
Chris@0 113 accept_lang = accept_lang.downcase
Chris@0 114 lang = find_language(accept_lang) || find_language(accept_lang.split('-').first)
Chris@0 115 end
Chris@0 116 end
Chris@0 117 lang ||= Setting.default_language
Chris@0 118 set_language_if_valid(lang)
Chris@0 119 end
Chris@0 120
Chris@0 121 def require_login
Chris@0 122 if !User.current.logged?
Chris@0 123 # Extract only the basic url parameters on non-GET requests
Chris@0 124 if request.get?
Chris@0 125 url = url_for(params)
Chris@0 126 else
Chris@0 127 url = url_for(:controller => params[:controller], :action => params[:action], :id => params[:id], :project_id => params[:project_id])
Chris@0 128 end
Chris@0 129 respond_to do |format|
Chris@0 130 format.html { redirect_to :controller => "account", :action => "login", :back_url => url }
Chris@0 131 format.atom { redirect_to :controller => "account", :action => "login", :back_url => url }
Chris@0 132 format.xml { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
Chris@0 133 format.js { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
Chris@0 134 format.json { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
Chris@0 135 end
Chris@0 136 return false
Chris@0 137 end
Chris@0 138 true
Chris@0 139 end
Chris@0 140
Chris@0 141 def require_admin
Chris@0 142 return unless require_login
Chris@0 143 if !User.current.admin?
Chris@0 144 render_403
Chris@0 145 return false
Chris@0 146 end
Chris@0 147 true
Chris@0 148 end
Chris@0 149
Chris@0 150 def deny_access
Chris@0 151 User.current.logged? ? render_403 : require_login
Chris@0 152 end
Chris@0 153
Chris@0 154 # Authorize the user for the requested action
Chris@0 155 def authorize(ctrl = params[:controller], action = params[:action], global = false)
Chris@0 156 allowed = User.current.allowed_to?({:controller => ctrl, :action => action}, @project, :global => global)
Chris@0 157 allowed ? true : deny_access
Chris@0 158 end
Chris@0 159
Chris@0 160 # Authorize the user for the requested action outside a project
Chris@0 161 def authorize_global(ctrl = params[:controller], action = params[:action], global = true)
Chris@0 162 authorize(ctrl, action, global)
Chris@0 163 end
Chris@0 164
Chris@0 165 # Find project of id params[:id]
Chris@0 166 def find_project
Chris@0 167 @project = Project.find(params[:id])
Chris@0 168 rescue ActiveRecord::RecordNotFound
Chris@0 169 render_404
Chris@0 170 end
Chris@0 171
Chris@0 172 # Find a project based on params[:project_id]
Chris@0 173 # TODO: some subclasses override this, see about merging their logic
Chris@0 174 def find_optional_project
Chris@0 175 @project = Project.find(params[:project_id]) unless params[:project_id].blank?
Chris@0 176 allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
Chris@0 177 allowed ? true : deny_access
Chris@0 178 rescue ActiveRecord::RecordNotFound
Chris@0 179 render_404
Chris@0 180 end
Chris@0 181
Chris@0 182 # Finds and sets @project based on @object.project
Chris@0 183 def find_project_from_association
Chris@0 184 render_404 unless @object.present?
Chris@0 185
Chris@0 186 @project = @object.project
Chris@0 187 rescue ActiveRecord::RecordNotFound
Chris@0 188 render_404
Chris@0 189 end
Chris@0 190
Chris@0 191 def find_model_object
Chris@0 192 model = self.class.read_inheritable_attribute('model_object')
Chris@0 193 if model
Chris@0 194 @object = model.find(params[:id])
Chris@0 195 self.instance_variable_set('@' + controller_name.singularize, @object) if @object
Chris@0 196 end
Chris@0 197 rescue ActiveRecord::RecordNotFound
Chris@0 198 render_404
Chris@0 199 end
Chris@0 200
Chris@0 201 def self.model_object(model)
Chris@0 202 write_inheritable_attribute('model_object', model)
Chris@0 203 end
Chris@0 204
Chris@0 205 # make sure that the user is a member of the project (or admin) if project is private
Chris@0 206 # used as a before_filter for actions that do not require any particular permission on the project
Chris@0 207 def check_project_privacy
Chris@0 208 if @project && @project.active?
Chris@0 209 if @project.is_public? || User.current.member_of?(@project) || User.current.admin?
Chris@0 210 true
Chris@0 211 else
Chris@0 212 User.current.logged? ? render_403 : require_login
Chris@0 213 end
Chris@0 214 else
Chris@0 215 @project = nil
Chris@0 216 render_404
Chris@0 217 false
Chris@0 218 end
Chris@0 219 end
Chris@0 220
Chris@0 221 def redirect_back_or_default(default)
Chris@0 222 back_url = CGI.unescape(params[:back_url].to_s)
Chris@0 223 if !back_url.blank?
Chris@0 224 begin
Chris@0 225 uri = URI.parse(back_url)
Chris@0 226 # do not redirect user to another host or to the login or register page
Chris@0 227 if (uri.relative? || (uri.host == request.host)) && !uri.path.match(%r{/(login|account/register)})
Chris@0 228 redirect_to(back_url)
Chris@0 229 return
Chris@0 230 end
Chris@0 231 rescue URI::InvalidURIError
Chris@0 232 # redirect to default
Chris@0 233 end
Chris@0 234 end
Chris@0 235 redirect_to default
Chris@0 236 end
Chris@0 237
Chris@0 238 def render_403
Chris@0 239 @project = nil
Chris@0 240 respond_to do |format|
Chris@0 241 format.html { render :template => "common/403", :layout => (request.xhr? ? false : 'base'), :status => 403 }
Chris@0 242 format.atom { head 403 }
Chris@0 243 format.xml { head 403 }
Chris@0 244 format.js { head 403 }
Chris@0 245 format.json { head 403 }
Chris@0 246 end
Chris@0 247 return false
Chris@0 248 end
Chris@0 249
Chris@0 250 def render_404
Chris@0 251 respond_to do |format|
Chris@0 252 format.html { render :template => "common/404", :layout => !request.xhr?, :status => 404 }
Chris@0 253 format.atom { head 404 }
Chris@0 254 format.xml { head 404 }
Chris@0 255 format.js { head 404 }
Chris@0 256 format.json { head 404 }
Chris@0 257 end
Chris@0 258 return false
Chris@0 259 end
Chris@0 260
Chris@0 261 def render_error(msg)
Chris@0 262 respond_to do |format|
Chris@0 263 format.html {
Chris@0 264 flash.now[:error] = msg
Chris@0 265 render :text => '', :layout => !request.xhr?, :status => 500
Chris@0 266 }
Chris@0 267 format.atom { head 500 }
Chris@0 268 format.xml { head 500 }
Chris@0 269 format.js { head 500 }
Chris@0 270 format.json { head 500 }
Chris@0 271 end
Chris@0 272 end
Chris@0 273
Chris@0 274 def invalid_authenticity_token
Chris@0 275 if api_request?
Chris@0 276 logger.error "Form authenticity token is missing or is invalid. API calls must include a proper Content-type header (text/xml or text/json)."
Chris@0 277 end
Chris@0 278 render_error "Invalid form authenticity token."
Chris@0 279 end
Chris@0 280
Chris@0 281 def render_feed(items, options={})
Chris@0 282 @items = items || []
Chris@0 283 @items.sort! {|x,y| y.event_datetime <=> x.event_datetime }
Chris@0 284 @items = @items.slice(0, Setting.feeds_limit.to_i)
Chris@0 285 @title = options[:title] || Setting.app_title
Chris@0 286 render :template => "common/feed.atom.rxml", :layout => false, :content_type => 'application/atom+xml'
Chris@0 287 end
Chris@0 288
Chris@0 289 def self.accept_key_auth(*actions)
Chris@0 290 actions = actions.flatten.map(&:to_s)
Chris@0 291 write_inheritable_attribute('accept_key_auth_actions', actions)
Chris@0 292 end
Chris@0 293
Chris@0 294 def accept_key_auth_actions
Chris@0 295 self.class.read_inheritable_attribute('accept_key_auth_actions') || []
Chris@0 296 end
Chris@0 297
Chris@0 298 # Returns the number of objects that should be displayed
Chris@0 299 # on the paginated list
Chris@0 300 def per_page_option
Chris@0 301 per_page = nil
Chris@0 302 if params[:per_page] && Setting.per_page_options_array.include?(params[:per_page].to_s.to_i)
Chris@0 303 per_page = params[:per_page].to_s.to_i
Chris@0 304 session[:per_page] = per_page
Chris@0 305 elsif session[:per_page]
Chris@0 306 per_page = session[:per_page]
Chris@0 307 else
Chris@0 308 per_page = Setting.per_page_options_array.first || 25
Chris@0 309 end
Chris@0 310 per_page
Chris@0 311 end
Chris@0 312
Chris@0 313 # qvalues http header parser
Chris@0 314 # code taken from webrick
Chris@0 315 def parse_qvalues(value)
Chris@0 316 tmp = []
Chris@0 317 if value
Chris@0 318 parts = value.split(/,\s*/)
Chris@0 319 parts.each {|part|
Chris@0 320 if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
Chris@0 321 val = m[1]
Chris@0 322 q = (m[2] or 1).to_f
Chris@0 323 tmp.push([val, q])
Chris@0 324 end
Chris@0 325 }
Chris@0 326 tmp = tmp.sort_by{|val, q| -q}
Chris@0 327 tmp.collect!{|val, q| val}
Chris@0 328 end
Chris@0 329 return tmp
Chris@0 330 rescue
Chris@0 331 nil
Chris@0 332 end
Chris@0 333
Chris@0 334 # Returns a string that can be used as filename value in Content-Disposition header
Chris@0 335 def filename_for_content_disposition(name)
Chris@0 336 request.env['HTTP_USER_AGENT'] =~ %r{MSIE} ? ERB::Util.url_encode(name) : name
Chris@0 337 end
Chris@0 338
Chris@0 339 def api_request?
Chris@0 340 %w(xml json).include? params[:format]
Chris@0 341 end
Chris@0 342
Chris@0 343 # Renders a warning flash if obj has unsaved attachments
Chris@0 344 def render_attachment_warning_if_needed(obj)
Chris@0 345 flash[:warning] = l(:warning_attachments_not_saved, obj.unsaved_attachments.size) if obj.unsaved_attachments.present?
Chris@0 346 end
Chris@0 347
Chris@0 348 # Rescues an invalid query statement. Just in case...
Chris@0 349 def query_statement_invalid(exception)
Chris@0 350 logger.error "Query::StatementInvalid: #{exception.message}" if logger
Chris@0 351 session.delete(:query)
Chris@0 352 sort_clear if respond_to?(:sort_clear)
Chris@0 353 render_error "An error occurred while executing the query and has been logged. Please report this error to your Redmine administrator."
Chris@0 354 end
Chris@0 355
Chris@0 356 # Converts the errors on an ActiveRecord object into a common JSON format
Chris@0 357 def object_errors_to_json(object)
Chris@0 358 object.errors.collect do |attribute, error|
Chris@0 359 { attribute => error }
Chris@0 360 end.to_json
Chris@0 361 end
Chris@0 362
Chris@0 363 end