aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_controller/base
diff options
context:
space:
mode:
authorCarl Lerche & Yehuda Katz <wycats@gmail.com>2009-04-13 15:18:45 -0700
committerCarl Lerche & Yehuda Katz <wycats@gmail.com>2009-04-13 15:18:45 -0700
commit906aebceedb95d8caa6db6314bc90f605bdfaf2b (patch)
tree5abc86bb6709b20df7cb5f4d1750b27c641dca4b /actionpack/lib/action_controller/base
parent2036d3ba75da1a0f3061bf5a33c89e2b2eaff420 (diff)
parentc877857d59554d78dbf45f5f9fcaafb8badec4e2 (diff)
downloadrails-906aebceedb95d8caa6db6314bc90f605bdfaf2b.tar.gz
rails-906aebceedb95d8caa6db6314bc90f605bdfaf2b.tar.bz2
rails-906aebceedb95d8caa6db6314bc90f605bdfaf2b.zip
Bring abstract_controller up to date with rails/master
Resolved all the conflicts since 2.3.0 -> HEAD. Following is a list of commits that could not be applied cleanly or are obviated with the abstract_controller refactor. They all need to be revisited to ensure that fixes made in 2.3 do not reappear in 3.0: 2259ecf368e6a6715966f69216e3ee86bf1a82a7 AR not available * This will be reimplemented with ActionORM or equivalent 06182ea02e92afad579998aa80144588e8865ac3 implicitly rendering a js response should not use the default layout [#1844 state:resolved] * This will be handled generically 893e9eb99504705419ad6edac14d00e71cef5f12 Improve view rendering performance in development mode and reinstate template recompiling in production [#1909 state:resolved] * We will need to reimplement rails-dev-boost on top of the refactor; the changes here are very implementation specific and cannot be cleanly applied. The following commits are implicated: 199e750d46c04970b5e7684998d09405648ecbd4 3942cb406e1d5db0ac00e03153809cc8dc4cc4db f8ea9f85d4f1e3e6f3b5d895bef6b013aa4b0690 e3b166aab37ddc2fbab030b146eb61713b91bf55 ae9f258e03c9fd5088da12c1c6cd216cc89a01f7 44423126c6f6133a1d9cf1d0832b527e8711d40f 0cb020b4d6d838025859bd60fb8151c8e21b8e84 workaround for picking layouts based on wrong view_paths [#1974 state:resolved] * The specifics of this commit no longer apply. Since it is a two-line commit, we will reimplement this change. 8c5cc66a831aadb159f3daaffa4208064c30af0e make action_controller/layouts pick templates from the current instance's view_paths instead of the class view_paths [#1974 state:resolved] * This does not apply at all. It should be trivial to apply the feature to the reimplemented ActionController::Base. 87e8b162463f13bd50d27398f020769460a770e3 fix HTML fallback for explicit templates [#2052 state:resolved] * There were a number of patches related to this that simply compounded each other. Basically none of them apply cleanly, and the underlying issue needs to be revisited. After discussing the underlying problem with Koz, we will defer these fixes for further discussion.
Diffstat (limited to 'actionpack/lib/action_controller/base')
-rw-r--r--actionpack/lib/action_controller/base/base.rb50
-rw-r--r--actionpack/lib/action_controller/base/cookies.rb2
-rw-r--r--actionpack/lib/action_controller/base/http_authentication.rb72
-rw-r--r--actionpack/lib/action_controller/base/redirect.rb2
-rw-r--r--actionpack/lib/action_controller/base/render.rb18
-rw-r--r--actionpack/lib/action_controller/base/responder.rb1
-rw-r--r--actionpack/lib/action_controller/base/streaming.rb16
-rw-r--r--actionpack/lib/action_controller/base/verification.rb2
8 files changed, 123 insertions, 40 deletions
diff --git a/actionpack/lib/action_controller/base/base.rb b/actionpack/lib/action_controller/base/base.rb
index 7a745ea040..aea434ffa0 100644
--- a/actionpack/lib/action_controller/base/base.rb
+++ b/actionpack/lib/action_controller/base/base.rb
@@ -22,7 +22,7 @@ module ActionController #:nodoc:
attr_reader :allowed_methods
def initialize(*allowed_methods)
- super("Only #{allowed_methods.to_sentence} requests are allowed.")
+ super("Only #{allowed_methods.to_sentence(:locale => :en)} requests are allowed.")
@allowed_methods = allowed_methods
end
@@ -394,7 +394,7 @@ module ActionController #:nodoc:
# Return an array containing the names of public methods that have been marked hidden from the action processor.
# By default, all methods defined in ActionController::Base and included modules are hidden.
- # More methods can be hidden using <tt>hide_actions</tt>.
+ # More methods can be hidden using <tt>hide_action</tt>.
def hidden_actions
read_inheritable_attribute(:hidden_actions) || write_inheritable_attribute(:hidden_actions, [])
end
@@ -690,6 +690,11 @@ module ActionController #:nodoc:
# request is considered stale and should be generated from scratch. Otherwise,
# it's fresh and we don't need to generate anything and a reply of "304 Not Modified" is sent.
#
+ # Parameters:
+ # * <tt>:etag</tt>
+ # * <tt>:last_modified</tt>
+ # * <tt>:public</tt> By default the Cache-Control header is private, set this to true if you want your application to be cachable by other devices (proxy caches).
+ #
# Example:
#
# def show
@@ -710,20 +715,34 @@ module ActionController #:nodoc:
# Sets the etag, last_modified, or both on the response and renders a
# "304 Not Modified" response if the request is already fresh.
#
+ # Parameters:
+ # * <tt>:etag</tt>
+ # * <tt>:last_modified</tt>
+ # * <tt>:public</tt> By default the Cache-Control header is private, set this to true if you want your application to be cachable by other devices (proxy caches).
+ #
# Example:
#
# def show
# @article = Article.find(params[:id])
- # fresh_when(:etag => @article, :last_modified => @article.created_at.utc)
+ # fresh_when(:etag => @article, :last_modified => @article.created_at.utc, :public => true)
# end
#
# This will render the show template if the request isn't sending a matching etag or
# If-Modified-Since header and just a "304 Not Modified" response if there's a match.
+ #
def fresh_when(options)
- options.assert_valid_keys(:etag, :last_modified)
+ options.assert_valid_keys(:etag, :last_modified, :public)
response.etag = options[:etag] if options[:etag]
response.last_modified = options[:last_modified] if options[:last_modified]
+
+ if options[:public]
+ cache_control = response.headers["Cache-Control"].split(",").map {|k| k.strip }
+ cache_control.delete("private")
+ cache_control.delete("no-cache")
+ cache_control << "public"
+ response.headers["Cache-Control"] = cache_control.join(', ')
+ end
if request.fresh?(response)
head :not_modified
@@ -735,15 +754,26 @@ module ActionController #:nodoc:
#
# Examples:
# expires_in 20.minutes
- # expires_in 3.hours, :private => false
- # expires in 3.hours, 'max-stale' => 5.hours, :private => nil, :public => true
+ # expires_in 3.hours, :public => true
+ # expires in 3.hours, 'max-stale' => 5.hours, :public => true
#
# This method will overwrite an existing Cache-Control header.
# See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities.
def expires_in(seconds, options = {}) #:doc:
- cache_options = { 'max-age' => seconds, 'private' => true }.symbolize_keys.merge!(options.symbolize_keys)
- cache_options.delete_if { |k,v| v.nil? or v == false }
- cache_control = cache_options.map{ |k,v| v == true ? k.to_s : "#{k.to_s}=#{v.to_s}"}
+ cache_control = response.headers["Cache-Control"].split(",").map {|k| k.strip }
+
+ cache_control << "max-age=#{seconds}"
+ cache_control.delete("no-cache")
+ if options[:public]
+ cache_control.delete("private")
+ cache_control << "public"
+ else
+ cache_control << "private"
+ end
+
+ # This allows for additional headers to be passed through like 'max-stale' => 5.hours
+ cache_control += options.symbolize_keys.reject{|k,v| k == :public || k == :private }.map{ |k,v| v == true ? k.to_s : "#{k.to_s}=#{v.to_s}"}
+
response.headers["Cache-Control"] = cache_control.join(', ')
end
@@ -839,6 +869,7 @@ module ActionController #:nodoc:
end
end
+ # Returns true if a render or redirect has already been performed.
def performed?
@performed_render || @performed_redirect
end
@@ -857,6 +888,7 @@ module ActionController #:nodoc:
@request_origin ||= "#{request.remote_ip} at #{Time.now.to_s(:db)}"
end
+ # Returns the request URI used to get to the current location
def complete_request_uri
"#{request.protocol}#{request.host}#{request.request_uri}"
end
diff --git a/actionpack/lib/action_controller/base/cookies.rb b/actionpack/lib/action_controller/base/cookies.rb
index 840ceb5abd..ca380e98d0 100644
--- a/actionpack/lib/action_controller/base/cookies.rb
+++ b/actionpack/lib/action_controller/base/cookies.rb
@@ -41,7 +41,7 @@ module ActionController #:nodoc:
# * <tt>:expires</tt> - The time at which this cookie expires, as a Time object.
# * <tt>:secure</tt> - Whether this cookie is a only transmitted to HTTPS servers.
# Default is +false+.
- # * <tt>:http_only</tt> - Whether this cookie is accessible via scripting or
+ # * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or
# only HTTP. Defaults to +false+.
module Cookies
def self.included(base)
diff --git a/actionpack/lib/action_controller/base/http_authentication.rb b/actionpack/lib/action_controller/base/http_authentication.rb
index 5d915fda08..b6b5267c66 100644
--- a/actionpack/lib/action_controller/base/http_authentication.rb
+++ b/actionpack/lib/action_controller/base/http_authentication.rb
@@ -68,8 +68,11 @@ module ActionController
#
# Simple Digest example:
#
+ # require 'digest/md5'
# class PostsController < ApplicationController
- # USERS = {"dhh" => "secret"}
+ # REALM = "SuperSecret"
+ # USERS = {"dhh" => "secret", #plain text password
+ # "dap" => Digest:MD5::hexdigest(["dap",REALM,"secret"].join(":")) #ha1 digest password
#
# before_filter :authenticate, :except => [:index]
#
@@ -83,14 +86,18 @@ module ActionController
#
# private
# def authenticate
- # authenticate_or_request_with_http_digest(realm) do |username|
+ # authenticate_or_request_with_http_digest(REALM) do |username|
# USERS[username]
# end
# end
# end
#
- # NOTE: The +authenticate_or_request_with_http_digest+ block must return the user's password so the framework can appropriately
- # hash it to check the user's credentials. Returning +nil+ will cause authentication to fail.
+ # NOTE: The +authenticate_or_request_with_http_digest+ block must return the user's password or the ha1 digest hash so the framework can appropriately
+ # hash to check the user's credentials. Returning +nil+ will cause authentication to fail.
+ # Storing the ha1 hash: MD5(username:realm:password), is better than storing a plain password. If
+ # the password file or database is compromised, the attacker would be able to use the ha1 hash to
+ # authenticate as the user at this +realm+, but would not have the user's password to try using at
+ # other sites.
#
# On shared hosts, Apache sometimes doesn't pass authentication headers to
# FCGI instances. If your environment matches this description and you cannot
@@ -177,26 +184,37 @@ module ActionController
end
# Raises error unless the request credentials response value matches the expected value.
+ # First try the password as a ha1 digest password. If this fails, then try it as a plain
+ # text password.
def validate_digest_response(request, realm, &password_procedure)
credentials = decode_credentials_header(request)
valid_nonce = validate_nonce(request, credentials[:nonce])
- if valid_nonce && realm == credentials[:realm] && opaque(request.session.session_id) == credentials[:opaque]
+ if valid_nonce && realm == credentials[:realm] && opaque == credentials[:opaque]
password = password_procedure.call(credentials[:username])
- expected = expected_response(request.env['REQUEST_METHOD'], request.url, credentials, password)
- expected == credentials[:response]
+
+ [true, false].any? do |password_is_ha1|
+ expected = expected_response(request.env['REQUEST_METHOD'], request.env['REQUEST_URI'], credentials, password, password_is_ha1)
+ expected == credentials[:response]
+ end
end
end
# Returns the expected response for a request of +http_method+ to +uri+ with the decoded +credentials+ and the expected +password+
- def expected_response(http_method, uri, credentials, password)
- ha1 = ::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(':'))
+ # Optional parameter +password_is_ha1+ is set to +true+ by default, since best practice is to store ha1 digest instead
+ # of a plain-text password.
+ def expected_response(http_method, uri, credentials, password, password_is_ha1=true)
+ ha1 = password_is_ha1 ? password : ha1(credentials, password)
ha2 = ::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(':'))
::Digest::MD5.hexdigest([ha1, credentials[:nonce], credentials[:nc], credentials[:cnonce], credentials[:qop], ha2].join(':'))
end
- def encode_credentials(http_method, credentials, password)
- credentials[:response] = expected_response(http_method, credentials[:uri], credentials, password)
+ def ha1(credentials, password)
+ ::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(':'))
+ end
+
+ def encode_credentials(http_method, credentials, password, password_is_ha1)
+ credentials[:response] = expected_response(http_method, credentials[:uri], credentials, password, password_is_ha1)
"Digest " + credentials.sort_by {|x| x[0].to_s }.inject([]) {|a, v| a << "#{v[0]}='#{v[1]}'" }.join(', ')
end
@@ -213,8 +231,7 @@ module ActionController
end
def authentication_header(controller, realm)
- session_id = controller.request.session.session_id
- controller.headers["WWW-Authenticate"] = %(Digest realm="#{realm}", qop="auth", algorithm=MD5, nonce="#{nonce(session_id)}", opaque="#{opaque(session_id)}")
+ controller.headers["WWW-Authenticate"] = %(Digest realm="#{realm}", qop="auth", algorithm=MD5, nonce="#{nonce}", opaque="#{opaque}")
end
def authentication_request(controller, realm, message = nil)
@@ -252,23 +269,36 @@ module ActionController
# POST or PUT requests and a time-stamp for GET requests. For more details on the issues involved see Section 4
# of this document.
#
- # The nonce is opaque to the client.
- def nonce(session_id, time = Time.now)
+ # The nonce is opaque to the client. Composed of Time, and hash of Time with secret
+ # key from the Rails session secret generated upon creation of project. Ensures
+ # the time cannot be modifed by client.
+ def nonce(time = Time.now)
t = time.to_i
- hashed = [t, session_id]
+ hashed = [t, secret_key]
digest = ::Digest::MD5.hexdigest(hashed.join(":"))
Base64.encode64("#{t}:#{digest}").gsub("\n", '')
end
- def validate_nonce(request, value)
+ # Might want a shorter timeout depending on whether the request
+ # is a PUT or POST, and if client is browser or web service.
+ # Can be much shorter if the Stale directive is implemented. This would
+ # allow a user to use new nonce without prompting user again for their
+ # username and password.
+ def validate_nonce(request, value, seconds_to_timeout=5*60)
t = Base64.decode64(value).split(":").first.to_i
- nonce(request.session.session_id, t) == value && (t - Time.now.to_i).abs <= 10 * 60
+ nonce(t) == value && (t - Time.now.to_i).abs <= seconds_to_timeout
end
- # Opaque based on digest of session_id
- def opaque(session_id)
- Base64.encode64(::Digest::MD5::hexdigest(session_id)).gsub("\n", '')
+ # Opaque based on random generation - but changing each request?
+ def opaque()
+ ::Digest::MD5.hexdigest(secret_key)
end
+
+ # Set in /initializers/session_store.rb, and loaded even if sessions are not in use.
+ def secret_key
+ ActionController::Base.session_options[:secret]
+ end
+
end
end
end
diff --git a/actionpack/lib/action_controller/base/redirect.rb b/actionpack/lib/action_controller/base/redirect.rb
index 83af793978..2e92117e7c 100644
--- a/actionpack/lib/action_controller/base/redirect.rb
+++ b/actionpack/lib/action_controller/base/redirect.rb
@@ -49,7 +49,6 @@ module ActionController
end
response.redirected_to = options
- logger.info("Redirected to #{options}") if logger && logger.info?
case options
# The scheme name consist of a letter followed by any combination of
@@ -72,6 +71,7 @@ module ActionController
def redirect_to_full_url(url, status)
raise DoubleRenderError if performed?
+ logger.info("Redirected to #{url}") if logger && logger.info?
response.redirect(url, interpret_status(status))
@performed_redirect = true
end
diff --git a/actionpack/lib/action_controller/base/render.rb b/actionpack/lib/action_controller/base/render.rb
index abba059969..c4a3725079 100644
--- a/actionpack/lib/action_controller/base/render.rb
+++ b/actionpack/lib/action_controller/base/render.rb
@@ -197,7 +197,17 @@ module ActionController
raise DoubleRenderError, "Can only render or redirect once per action" if performed?
options = { :layout => true } if options.nil?
- original, options = options, extra_options unless options.is_a?(Hash)
+
+ # This handles render "string", render :symbol, and render object
+ # render string and symbol are handled by render_for_name
+ # render object becomes render :partial => object
+ unless options.is_a?(Hash)
+ if options.is_a?(String) || options.is_a?(Symbol)
+ original, options = options, extra_options
+ else
+ extra_options[:partial], options = options, extra_options
+ end
+ end
layout_name = options.delete(:layout)
@@ -300,6 +310,7 @@ module ActionController
# of sending it as the response body to the browser.
def render_to_string(options = nil, &block) #:doc:
render(options, &block)
+ response.body
ensure
response.content_type = nil
erase_render_results
@@ -308,7 +319,7 @@ module ActionController
# Clears the rendered results, allowing for another render to be performed.
def erase_render_results #:nodoc:
- response.body = nil
+ response.body = []
@performed_render = false
end
@@ -360,8 +371,9 @@ module ActionController
def render_for_parts(parts, layout, options = {})
tmp = view_paths.find_by_parts(*parts)
- layout = _pick_layout(*layout) unless tmp.exempt_from_layout?
+ layout = _pick_layout(*layout) unless tmp.exempt_from_layout?
+
render_for_text(
@template._render_template_with_layout(tmp, layout, options, parts[3]))
end
diff --git a/actionpack/lib/action_controller/base/responder.rb b/actionpack/lib/action_controller/base/responder.rb
index f83abb5a4b..989f82444b 100644
--- a/actionpack/lib/action_controller/base/responder.rb
+++ b/actionpack/lib/action_controller/base/responder.rb
@@ -20,6 +20,7 @@ module ActionController
end
end
+ # Returns a set of the methods defined as actions in your controller
def action_methods
self.class.action_methods
end
diff --git a/actionpack/lib/action_controller/base/streaming.rb b/actionpack/lib/action_controller/base/streaming.rb
index e1786913a7..9f80f48c3d 100644
--- a/actionpack/lib/action_controller/base/streaming.rb
+++ b/actionpack/lib/action_controller/base/streaming.rb
@@ -1,5 +1,6 @@
module ActionController #:nodoc:
- # Methods for sending files and streams to the browser instead of rendering.
+ # Methods for sending arbitrary data and for streaming files to the browser,
+ # instead of rendering.
module Streaming
DEFAULT_SEND_FILE_OPTIONS = {
:type => 'application/octet-stream'.freeze,
@@ -103,8 +104,11 @@ module ActionController #:nodoc:
end
end
- # Send binary data to the user as a file download. May set content type, apparent file name,
- # and specify whether to show data inline or download as an attachment.
+ # Sends the given binary data to the browser. This method is similar to
+ # <tt>render :text => data</tt>, but also allows you to specify whether
+ # the browser should display the response as a file attachment (i.e. in a
+ # download dialog) or as inline data. You may also set the content type,
+ # the apparent file name, and other things.
#
# Options:
# * <tt>:filename</tt> - suggests a filename for the browser to use.
@@ -127,6 +131,10 @@ module ActionController #:nodoc:
# send_data image.data, :type => image.content_type, :disposition => 'inline'
#
# See +send_file+ for more information on HTTP Content-* headers and caching.
+ #
+ # <b>Tip:</b> if you want to stream large amounts of on-the-fly generated
+ # data to the browser, then use <tt>render :text => proc { ... }</tt>
+ # instead. See ActionController::Base#render for more information.
def send_data(data, options = {}) #:doc:
logger.info "Sending data #{options[:filename]}" if logger
send_file_headers! options.merge(:length => data.size)
@@ -152,7 +160,7 @@ module ActionController #:nodoc:
end
content_type = content_type.to_s.strip # fixes a problem with extra '\r' with some browsers
- headers.update(
+ headers.merge!(
'Content-Length' => options[:length],
'Content-Type' => content_type,
'Content-Disposition' => disposition,
diff --git a/actionpack/lib/action_controller/base/verification.rb b/actionpack/lib/action_controller/base/verification.rb
index 7bf09ba6ea..c62b81b666 100644
--- a/actionpack/lib/action_controller/base/verification.rb
+++ b/actionpack/lib/action_controller/base/verification.rb
@@ -90,7 +90,7 @@ module ActionController #:nodoc:
def verify_action(options) #:nodoc:
if prereqs_invalid?(options)
flash.update(options[:add_flash]) if options[:add_flash]
- response.headers.update(options[:add_headers]) if options[:add_headers]
+ response.headers.merge!(options[:add_headers]) if options[:add_headers]
apply_remaining_actions(options) unless performed?
end
end