Skip to content

Commit

Permalink
Merge pull request #4079 from drogus/http_digest_issue
Browse files Browse the repository at this point in the history
Fix http digest authentication when url ends with `/` or `?`
  • Loading branch information
josevalim committed Dec 21, 2011
2 parents 1f75a1f + 3131a93 commit 618cb44
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 8 deletions.
13 changes: 8 additions & 5 deletions actionpack/lib/action_controller/metal/http_authentication.rb
Expand Up @@ -192,12 +192,15 @@ def validate_digest_response(request, realm, &password_procedure)
return false unless password

method = request.env['rack.methodoverride.original_method'] || request.env['REQUEST_METHOD']
uri = credentials[:uri][0,1] == '/' ? request.fullpath : request.url
uri = credentials[:uri][0,1] == '/' ? request.original_fullpath : request.original_url

[true, false].any? do |password_is_ha1|
expected = expected_response(method, uri, credentials, password, password_is_ha1)
expected == credentials[:response]
end
[true, false].any? do |trailing_question_mark|
[true, false].any? do |password_is_ha1|
_uri = trailing_question_mark ? uri + "?" : uri
expected = expected_response(method, _uri, credentials, password, password_is_ha1)
expected == credentials[:response]
end
end
end
end

Expand Down
8 changes: 8 additions & 0 deletions actionpack/lib/action_dispatch/http/request.rb
Expand Up @@ -122,10 +122,18 @@ def headers
Http::Headers.new(@env)
end

def original_fullpath
@original_fullpath ||= (env["ORIGINAL_FULLPATH"] || fullpath)
end

def fullpath
@fullpath ||= super
end

def original_url
base_url + original_fullpath
end

def media_type
content_mime_type.to_s
end
Expand Down
45 changes: 43 additions & 2 deletions actionpack/test/controller/http_digest_authentication_test.rb
Expand Up @@ -139,7 +139,7 @@ def authenticate_with_request

test "authentication request with request-uri that doesn't match credentials digest-uri" do
@request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please')
@request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest/altered/uri"
@request.env['ORIGINAL_FULLPATH'] = "/http_digest_authentication_test/dummy_digest/altered/uri"
get :display

assert_response :unauthorized
Expand Down Expand Up @@ -208,6 +208,44 @@ def authenticate_with_request
assert !ActionController::HttpAuthentication::Digest.validate_digest_response(@request, "SuperSecret"){nil}
end

test "authentication request with request-uri ending in '/'" do
@request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest/"
@request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please')

# simulate normalizing PATH_INFO
@request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest"
get :display

assert_response :success
assert_equal 'Definitely Maybe', @response.body
end

test "authentication request with request-uri ending in '?'" do
@request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest/?"
@request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please')

# simulate normalizing PATH_INFO
@request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest"
get :display

assert_response :success
assert_equal 'Definitely Maybe', @response.body
end

test "authentication request with absolute uri in credentials (as in IE) ending with /" do
@request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest/"
@request.env['HTTP_AUTHORIZATION'] = encode_credentials(:uri => "http://test.host/http_digest_authentication_test/dummy_digest/",
:username => 'pretty', :password => 'please')

# simulate normalizing PATH_INFO
@request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest"
get :display

assert_response :success
assert assigns(:logged_in)
assert_equal 'Definitely Maybe', @response.body
end

private

def encode_credentials(options)
Expand All @@ -228,7 +266,10 @@ def encode_credentials(options)

credentials = decode_credentials(@response.headers['WWW-Authenticate'])
credentials.merge!(options)
credentials.merge!(:uri => @request.env['PATH_INFO'].to_s)
path_info = @request.env['PATH_INFO'].to_s
uri = options[:uri] || path_info
credentials.merge!(:uri => uri)
@request.env["ORIGINAL_FULLPATH"] = path_info
ActionController::HttpAuthentication::Digest.encode_credentials(method, credentials, password, options[:password_is_ha1])
end

Expand Down
24 changes: 24 additions & 0 deletions actionpack/test/dispatch/request_test.rb
Expand Up @@ -618,6 +618,30 @@ def url_for(options = {})
assert_equal "/authenticate?secret", path
end

test "original_fullpath returns ORIGINAL_FULLPATH" do
request = stub_request('ORIGINAL_FULLPATH' => "/foo?bar")

path = request.original_fullpath
assert_equal "/foo?bar", path
end

test "original_url returns url built using ORIGINAL_FULLPATH" do
request = stub_request('ORIGINAL_FULLPATH' => "/foo?bar",
'HTTP_HOST' => "example.org",
'rack.url_scheme' => "http")

url = request.original_url
assert_equal "http://example.org/foo?bar", url
end

test "original_fullpath returns fullpath if ORIGINAL_FULLPATH is not present" do
request = stub_request('PATH_INFO' => "/foo",
'QUERY_STRING' => "bar")

path = request.original_fullpath
assert_equal "/foo?bar", path
end

protected

def stub_request(env = {})
Expand Down
17 changes: 17 additions & 0 deletions railties/lib/rails/application.rb
Expand Up @@ -215,6 +215,11 @@ def helpers_paths #:nodoc:
config.helpers_paths
end

def call(env)
env["ORIGINAL_FULLPATH"] = build_original_fullpath(env)
super(env)
end

protected

alias :build_middleware_stack :app
Expand Down Expand Up @@ -291,5 +296,17 @@ def initialize_console #:nodoc:
require "rails/console/app"
require "rails/console/helpers"
end

def build_original_fullpath(env)
path_info = env["PATH_INFO"]
query_string = env["QUERY_STRING"]
script_name = env["SCRIPT_NAME"]

if query_string.present?
"#{script_name}#{path_info}?#{query_string}"
else
"#{script_name}#{path_info}"
end
end
end
end
27 changes: 27 additions & 0 deletions railties/test/application/build_original_fullpath_test.rb
@@ -0,0 +1,27 @@
require "abstract_unit"

module ApplicationTests
class BuildOriginalPathTest < Test::Unit::TestCase
def test_include_original_PATH_info_in_ORIGINAL_FULLPATH
env = { 'PATH_INFO' => '/foo/' }
assert_equal "/foo/", Rails.application.send(:build_original_fullpath, env)
end

def test_include_SCRIPT_NAME
env = {
'SCRIPT_NAME' => '/foo',
'PATH_INFO' => '/bar'
}

assert_equal "/foo/bar", Rails.application.send(:build_original_fullpath, env)
end

def test_include_QUERY_STRING
env = {
'PATH_INFO' => '/foo',
'QUERY_STRING' => 'bar',
}
assert_equal "/foo?bar", Rails.application.send(:build_original_fullpath, env)
end
end
end
11 changes: 10 additions & 1 deletion railties/test/application/middleware_test.rb
@@ -1,5 +1,6 @@
require 'isolation/abstract_unit'
require 'stringio'
require 'rack/test'

module ApplicationTests
class MiddlewareTest < Test::Unit::TestCase
Expand Down Expand Up @@ -75,7 +76,7 @@ def app
add_to_config "config.force_ssl = true"
add_to_config "config.ssl_options = { :host => 'example.com' }"
boot!

assert_equal AppTemplate::Application.middleware.first.args, [{:host => 'example.com'}]
end

Expand Down Expand Up @@ -193,6 +194,14 @@ def index
assert_equal nil, last_response.headers["Etag"]
end

test "ORIGINAL_FULLPATH is passed to env" do
boot!
env = ::Rack::MockRequest.env_for("/foo/?something")
Rails.application.call(env)

assert_equal "/foo/?something", env["ORIGINAL_FULLPATH"]
end

private

def boot!
Expand Down

9 comments on commit 618cb44

@trevor
Copy link
Contributor

@trevor trevor commented on 618cb44 Jan 5, 2012

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will this be merged into 3.2.0?

not seeing it in 3.2.0rc2

@drogus
Copy link
Member

@drogus drogus commented on 618cb44 Jan 6, 2012

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@josevalim @tenderlove @spastorino can we still backport it to 3-2-stable or is it too late?

@tenderlove
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a bugfix, so it seems fine to backport to 3-2-stable. Should we also backport to 3-1-stable and 3-0-stable?

@drogus
Copy link
Member

@drogus drogus commented on 618cb44 Jan 7, 2012

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tenderlove technically this bugfix also needs adding a feature (keeping original path for the request somewhere), if that's ok I can backport it to 3-2-stable. 3-1-stable and 3-0-stable also seems good

@tenderlove
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@drogus that's fine with me!

@drogus
Copy link
Member

@drogus drogus commented on 618cb44 Jan 9, 2012

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tenderlove cool, I will backport it today

@drogus
Copy link
Member

@drogus drogus commented on 618cb44 Jan 10, 2012

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I backported it to 3-1 and 3-2, if anyone needs it for 3-0-stable, please ping me, it will need a bit more changes.

@trevor
Copy link
Contributor

@trevor trevor commented on 618cb44 Jan 10, 2012

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@drogus thanks for working on this!

@drogus
Copy link
Member

@drogus drogus commented on 618cb44 Jan 10, 2012

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@trevor you're welcome :)

Please sign in to comment.