Permalink
Browse files

Merge commit 'mainstream/master'

Conflicts:
	actionpack/lib/action_view/helpers/text_helper.rb
	activesupport/lib/active_support/inflector.rb
  • Loading branch information...
lifo committed Mar 12, 2009
2 parents 053afbe + 47bdf3b commit 53744c543880999a7ad3f1e026875df3283978f1
Showing with 1,146 additions and 193 deletions.
  1. +1 −1 actionmailer/lib/action_mailer/base.rb
  2. +29 −1 actionmailer/test/mail_layout_test.rb
  3. +2 −0 actionpack/CHANGELOG
  4. +0 −1 actionpack/Rakefile
  5. +6 −1 actionpack/lib/action_controller.rb
  6. +10 −17 actionpack/lib/action_controller/caching/actions.rb
  7. +51 −21 actionpack/lib/action_controller/http_authentication.rb
  8. +1 −0 actionpack/lib/action_controller/request.rb
  9. +1 −0 actionpack/lib/action_controller/vendor/rack-1.0/rack.rb
  10. +2 −2 actionpack/lib/action_controller/vendor/rack-1.0/rack/auth/abstract/handler.rb
  11. +1 −1 actionpack/lib/action_controller/vendor/rack-1.0/rack/auth/digest/md5.rb
  12. +1 −1 actionpack/lib/action_controller/vendor/rack-1.0/rack/auth/digest/request.rb
  13. +1 −5 actionpack/lib/action_controller/vendor/rack-1.0/rack/builder.rb
  14. +5 −3 actionpack/lib/action_controller/vendor/rack-1.0/rack/content_length.rb
  15. +23 −0 actionpack/lib/action_controller/vendor/rack-1.0/rack/content_type.rb
  16. +2 −2 actionpack/lib/action_controller/vendor/rack-1.0/rack/deflater.rb
  17. +4 −2 actionpack/lib/action_controller/vendor/rack-1.0/rack/directory.rb
  18. +1 −1 actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/lsws.rb
  19. +6 −1 actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/webrick.rb
  20. +4 −2 actionpack/lib/action_controller/vendor/rack-1.0/rack/lint.rb
  21. +2 −0 actionpack/lib/action_controller/vendor/rack-1.0/rack/response.rb
  22. +1 −1 actionpack/lib/action_controller/vendor/rack-1.0/rack/showstatus.rb
  23. +13 −0 actionpack/lib/action_controller/vendor/rack-1.0/rack/utils.rb
  24. +4 −3 actionpack/lib/action_view/helpers/text_helper.rb
  25. +25 −0 actionpack/test/activerecord/active_record_store_test.rb
  26. +14 −0 actionpack/test/controller/caching_test.rb
  27. +46 −7 actionpack/test/controller/http_digest_authentication_test.rb
  28. +24 −16 actionpack/test/controller/session/mem_cache_store_test.rb
  29. +6 −0 actionpack/test/template/text_helper_test.rb
  30. +1 −2 activerecord/CHANGELOG
  31. +9 −4 activerecord/lib/active_record/associations.rb
  32. +33 −20 activerecord/lib/active_record/associations/association_collection.rb
  33. +2 −2 activerecord/lib/active_record/autosave_association.rb
  34. +11 −12 activerecord/lib/active_record/base.rb
  35. +6 −3 activerecord/lib/active_record/batches.rb
  36. +3 −2 activerecord/lib/active_record/named_scope.rb
  37. +12 −6 activerecord/lib/active_record/serializers/xml_serializer.rb
  38. +16 −0 activerecord/test/cases/associations/belongs_to_associations_test.rb
  39. +29 −0 activerecord/test/cases/associations/eager_load_nested_include_test.rb
  40. +27 −0 activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
  41. +63 −0 activerecord/test/cases/associations/has_many_associations_test.rb
  42. +18 −0 activerecord/test/cases/associations/has_many_through_associations_test.rb
  43. +16 −0 activerecord/test/cases/associations/has_one_associations_test.rb
  44. +16 −0 activerecord/test/cases/associations/has_one_through_associations_test.rb
  45. +35 −0 activerecord/test/cases/autosave_association_test.rb
  46. +15 −3 activerecord/test/cases/batches_test.rb
  47. +37 −7 activerecord/test/cases/method_scoping_test.rb
  48. +35 −9 activerecord/test/cases/named_scope_test.rb
  49. +12 −1 activerecord/test/cases/xml_serialization_test.rb
  50. +48 −1 activerecord/test/models/pirate.rb
  51. +3 −1 activerecord/test/models/topic.rb
  52. +1 −1 activeresource/test/base_test.rb
  53. +2 −1 activesupport/CHANGELOG
  54. +6 −0 activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
  55. +10 −8 activesupport/lib/active_support/inflector.rb
  56. +20 −2 activesupport/lib/active_support/json/decoding.rb
  57. +6 −1 activesupport/lib/active_support/testing/setup_and_teardown.rb
  58. +16 −3 activesupport/lib/active_support/xml_mini.rb
  59. +9 −7 activesupport/lib/active_support/xml_mini/libxml.rb
  60. +77 −0 activesupport/lib/active_support/xml_mini/nokogiri.rb
  61. +13 −1 activesupport/test/core_ext/hash_ext_test.rb
  62. +6 −0 activesupport/test/inflector_test.rb
  63. +5 −1 activesupport/test/json/decoding_test.rb
  64. +157 −0 activesupport/test/xml_mini/nokogiri_engine_test.rb
  65. +15 −0 activesupport/test/xml_mini/rexml_engine_test.rb
  66. +2 −1 railties/lib/commands/plugin.rb
  67. +13 −4 railties/lib/rails/rack/static.rb
  68. +1 −1 railties/lib/rails_generator/generators/applications/app/template_runner.rb
  69. +1 −0 railties/test/fixtures/public/foo/bar.html
  70. +1 −0 railties/test/fixtures/public/foo/index.html
  71. +1 −0 railties/test/fixtures/public/index.html
  72. +5 −0 railties/test/generators/rails_template_runner_test.rb
  73. +46 −0 railties/test/rack_static_test.rb
@@ -479,7 +479,7 @@ def create!(method_name, *parameters) #:nodoc:
)
end
unless @parts.empty?
- @content_type = "multipart/alternative"
+ @content_type = "multipart/alternative" if @content_type !~ /^multipart/
@parts = sort_parts(@parts, @implicit_parts_order)
end
end
@@ -21,10 +21,12 @@ def nolayout(recipient)
body render(:inline => "Hello, <%= @world %>", :layout => false, :body => { :world => "Earth" })
end
- def multipart(recipient)
+ def multipart(recipient, type = nil)
recipients recipient
subject "You have a mail"
from "tester@example.com"
+
+ content_type(type) if type
end
end
@@ -64,6 +66,19 @@ def test_should_pickup_default_layout
def test_should_pickup_multipart_layout
mail = AutoLayoutMailer.create_multipart(@recipient)
+ assert_equal "multipart/alternative", mail.content_type
+ assert_equal 2, mail.parts.size
+
+ assert_equal 'text/plain', mail.parts.first.content_type
+ assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body
+
+ assert_equal 'text/html', mail.parts.last.content_type
+ assert_equal "Hello from layout text/html multipart", mail.parts.last.body
+ end
+
+ def test_should_pickup_multipartmixed_layout
+ mail = AutoLayoutMailer.create_multipart(@recipient, "multipart/mixed")
+ assert_equal "multipart/mixed", mail.content_type
assert_equal 2, mail.parts.size
assert_equal 'text/plain', mail.parts.first.content_type
@@ -73,6 +88,19 @@ def test_should_pickup_multipart_layout
assert_equal "Hello from layout text/html multipart", mail.parts.last.body
end
+ def test_should_fix_multipart_layout
+ mail = AutoLayoutMailer.create_multipart(@recipient, "text/plain")
+ assert_equal "multipart/alternative", mail.content_type
+ assert_equal 2, mail.parts.size
+
+ assert_equal 'text/plain', mail.parts.first.content_type
+ assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body
+
+ assert_equal 'text/html', mail.parts.last.content_type
+ assert_equal "Hello from layout text/html multipart", mail.parts.last.body
+ end
+
+
def test_should_pickup_layout_given_to_render
mail = AutoLayoutMailer.create_spam(@recipient)
assert_equal "Spammer layout Hello, Earth", mail.body.strip
View
@@ -6,6 +6,8 @@
* Fixed that passing a custom form builder would be forwarded to nested fields_for calls #2023 [Eloy Duran/Nate Wiger]
+* Form option helpers now support disabled option tags and the use of lambdas for selecting/disabling option tags from collections #837 [Tekin]
+
* Added partial scoping to TranslationHelper#translate, so if you call translate(".foo") from the people/index.html.erb template, you'll actually be calling I18n.translate("people.index.foo") [DHH]
* Fix a syntax error in current_page?() that was prevent matches against URL's with multiple query parameters #1385, #1868 [chris finne/Andrew White]
View
@@ -81,7 +81,6 @@ spec = Gem::Specification.new do |s|
s.requirements << 'none'
s.add_dependency('activesupport', '= 2.3.1' + PKG_BUILD)
- s.add_dependency('rack', '>= 0.9.0')
s.require_path = 'lib'
s.autorequire = 'action_controller'
@@ -31,7 +31,12 @@
end
end
-require 'action_controller/vendor/rack-1.0/rack'
+begin
+ gem 'rack', '~> 1.0.0'
+ require 'rack'
+rescue Gem::LoadError
+ require 'action_controller/vendor/rack-1.0/rack'
+end
module ActionController
# TODO: Review explicit to see if they will automatically be handled by
@@ -129,24 +129,23 @@ class ActionCachePath
attr_reader :path, :extension
class << self
- def path_for(controller, options, infer_extension=true)
+ def path_for(controller, options, infer_extension = true)
new(controller, options, infer_extension).path
end
end
# When true, infer_extension will look up the cache path extension from the request's path & format.
- # This is desirable when reading and writing the cache, but not when expiring the cache - expire_action should expire the same files regardless of the request format.
- def initialize(controller, options = {}, infer_extension=true)
- if infer_extension and options.is_a? Hash
- request_extension = extract_extension(controller.request)
- options = options.reverse_merge(:format => request_extension)
+ # This is desirable when reading and writing the cache, but not when expiring the cache -
+ # expire_action should expire the same files regardless of the request format.
+ def initialize(controller, options = {}, infer_extension = true)
+ if infer_extension
+ extract_extension(controller.request)
+ options = options.reverse_merge(:format => @extension) if options.is_a?(Hash)
end
+
path = controller.url_for(options).split('://').last
normalize!(path)
- if infer_extension
- @extension = request_extension
- add_extension!(path, @extension)
- end
+ add_extension!(path, @extension)
@path = URI.unescape(path)
end
@@ -162,13 +161,7 @@ def add_extension!(path, extension)
def extract_extension(request)
# Don't want just what comes after the last '.' to accommodate multi part extensions
# such as tar.gz.
- extension = request.path[/^[^.]+\.(.+)$/, 1]
-
- # If there's no extension in the path, check request.format
- if extension.nil?
- extension = request.cache_format
- end
- extension
+ @extension = request.path[/^[^.]+\.(.+)$/, 1] || request.cache_format
end
end
end
@@ -68,8 +68,11 @@ module HttpAuthentication
#
# 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 HttpAuthentication
#
# 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 @@ def authorization(request)
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'], credentials[:uri], 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 @@ def decode_credentials(header)
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 @@ def authentication_request(controller, realm, message = nil)
# 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
@@ -442,6 +442,7 @@ def session=(session) #:nodoc:
end
def reset_session
+ @env['rack.session.options'].delete(:id)
@env['rack.session'] = {}
end
@@ -31,6 +31,7 @@ def self.release
autoload :CommonLogger, "rack/commonlogger"
autoload :ConditionalGet, "rack/conditionalget"
autoload :ContentLength, "rack/content_length"
+ autoload :ContentType, "rack/content_type"
autoload :File, "rack/file"
autoload :Deflater, "rack/deflater"
autoload :Directory, "rack/directory"
@@ -8,8 +8,8 @@ class AbstractHandler
attr_accessor :realm
- def initialize(app, &authenticator)
- @app, @authenticator = app, authenticator
+ def initialize(app, realm=nil, &authenticator)
+ @app, @realm, @authenticator = app, realm, authenticator
end
@@ -21,7 +21,7 @@ class MD5 < AbstractHandler
attr_writer :passwords_hashed
- def initialize(app)
+ def initialize(*args)
super
@passwords_hashed = nil
end
@@ -8,7 +8,7 @@ module Digest
class Request < Auth::AbstractRequest
def method
- @env['REQUEST_METHOD']
+ @env['rack.methodoverride.original_method'] || @env['REQUEST_METHOD']
end
def digest?
@@ -34,11 +34,7 @@ def self.app(&block)
end
def use(middleware, *args, &block)
- @ins << if block_given?
- lambda { |app| middleware.new(app, *args, &block) }
- else
- lambda { |app| middleware.new(app, *args) }
- end
+ @ins << lambda { |app| middleware.new(app, *args, &block) }
end
def run(app)
@@ -3,21 +3,23 @@
module Rack
# Sets the Content-Length header on responses with fixed-length bodies.
class ContentLength
+ include Rack::Utils
+
def initialize(app)
@app = app
end
def call(env)
status, headers, body = @app.call(env)
- headers = Utils::HeaderHash.new(headers)
+ headers = HeaderHash.new(headers)
- if !Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status) &&
+ if !STATUS_WITH_NO_ENTITY_BODY.include?(status) &&
!headers['Content-Length'] &&
!headers['Transfer-Encoding'] &&
(body.respond_to?(:to_ary) || body.respond_to?(:to_str))
body = [body] if body.respond_to?(:to_str) # rack 0.4 compat
- length = body.to_ary.inject(0) { |len, part| len + part.length }
+ length = body.to_ary.inject(0) { |len, part| len + bytesize(part) }
headers['Content-Length'] = length.to_s
end
@@ -0,0 +1,23 @@
+require 'rack/utils'
+
+module Rack
+
+ # Sets the Content-Type header on responses which don't have one.
+ #
+ # Builder Usage:
+ # use Rack::ContentType, "text/plain"
+ #
+ # When no content type argument is provided, "text/html" is assumed.
+ class ContentType
+ def initialize(app, content_type = "text/html")
+ @app, @content_type = app, content_type
+ end
+
+ def call(env)
+ status, headers, body = @app.call(env)
+ headers = Utils::HeaderHash.new(headers)
+ headers['Content-Type'] ||= @content_type
+ [status, headers.to_hash, body]
+ end
+ end
+end
Oops, something went wrong.

0 comments on commit 53744c5

Please sign in to comment.