diff options
author | Pratik Naik <pratiknaik@gmail.com> | 2009-11-17 22:47:23 +0000 |
---|---|---|
committer | Pratik Naik <pratiknaik@gmail.com> | 2009-11-17 22:47:23 +0000 |
commit | 5446d5cb05b50a9a3f317ded774be438e0eff909 (patch) | |
tree | 6b0b87efe3e95783763208215a3159fb63217a6d /activeresource | |
parent | 9754debb9a72f9385950e5282f3642b995ab76d8 (diff) | |
parent | f8877d4b2a2a6f68770b376f0b1391a6295f62f2 (diff) | |
download | rails-5446d5cb05b50a9a3f317ded774be438e0eff909.tar.gz rails-5446d5cb05b50a9a3f317ded774be438e0eff909.tar.bz2 rails-5446d5cb05b50a9a3f317ded774be438e0eff909.zip |
Merge remote branch 'mainstream/master'
Conflicts:
activesupport/lib/active_support/core_ext/hash/conversions.rb
Diffstat (limited to 'activeresource')
-rw-r--r-- | activeresource/Rakefile | 25 | ||||
-rw-r--r-- | activeresource/lib/active_resource/base.rb | 52 | ||||
-rw-r--r-- | activeresource/lib/active_resource/connection.rb | 99 | ||||
-rw-r--r-- | activeresource/test/abstract_unit.rb | 13 | ||||
-rw-r--r-- | activeresource/test/cases/authorization_test.rb | 170 | ||||
-rw-r--r-- | activeresource/test/cases/base/load_test.rb | 1 | ||||
-rw-r--r-- | activeresource/test/cases/base_test.rb | 6 | ||||
-rw-r--r-- | activeresource/test/connection_test.rb | 15 |
8 files changed, 310 insertions, 71 deletions
diff --git a/activeresource/Rakefile b/activeresource/Rakefile index c87345e3b5..6566e84d4c 100644 --- a/activeresource/Rakefile +++ b/activeresource/Rakefile @@ -35,15 +35,16 @@ Rake::TestTask.new { |t| t.warning = true } -task :isolated_test do - ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME')) - activesupport_path = "#{File.dirname(__FILE__)}/../activesupport/lib" - Dir.glob("test/**/*_test.rb").all? do |file| - system(ruby, '-w', "-Ilib:test:#{activesupport_path}", file) - end or raise "Failures" +namespace :test do + task :isolated do + ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME')) + activesupport_path = "#{File.dirname(__FILE__)}/../activesupport/lib" + Dir.glob("test/**/*_test.rb").all? do |file| + system(ruby, '-w', "-Ilib:test:#{activesupport_path}", file) + end or raise "Failures" + end end - # Generate the RDoc documentation Rake::RDocTask.new { |rdoc| @@ -62,8 +63,6 @@ spec = eval(File.read('activeresource.gemspec')) Rake::GemPackageTask.new(spec) do |p| p.gem_spec = spec - p.need_tar = true - p.need_zip = true end task :lines do @@ -80,10 +79,10 @@ task :lines do codelines += 1 end puts "L: #{sprintf("%4d", lines)}, LOC #{sprintf("%4d", codelines)} | #{file_name}" - + total_lines += lines total_codelines += codelines - + lines, codelines = 0, 0 end @@ -94,14 +93,14 @@ end # Publishing ------------------------------------------------------ desc "Publish the beta gem" -task :pgem => [:package] do +task :pgem => [:package] do require 'rake/contrib/sshpublisher' Rake::SshFilePublisher.new("gems.rubyonrails.org", "/u/sites/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload `ssh gems.rubyonrails.org '/u/sites/gems/gemupdate.sh'` end desc "Publish the API documentation" -task :pdoc => [:rdoc] do +task :pdoc => [:rdoc] do require 'rake/contrib/sshpublisher' Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/ar", "doc").upload end diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb index 803c6be53b..0e74592b0c 100644 --- a/activeresource/lib/active_resource/base.rb +++ b/activeresource/lib/active_resource/base.rb @@ -8,6 +8,7 @@ require 'active_support/core_ext/module/delegation' require 'active_support/core_ext/module/aliasing' require 'active_support/core_ext/object/blank' require 'active_support/core_ext/object/misc' +require 'active_support/core_ext/object/to_query' require 'set' require 'uri' @@ -280,8 +281,8 @@ module ActiveResource @site = nil else @site = create_site_uri_from(site) - @user = URI.decode(@site.user) if @site.user - @password = URI.decode(@site.password) if @site.password + @user = uri_parser.unescape(@site.user) if @site.user + @password = uri_parser.unescape(@site.password) if @site.password end end @@ -333,6 +334,17 @@ module ActiveResource @password = password end + def auth_type + if defined?(@auth_type) + @auth_type + end + end + + def auth_type=(auth_type) + @connection = nil + @auth_type = auth_type + end + # Sets the format that attributes are sent and received in from a mime type reference: # # Person.format = :json @@ -404,6 +416,7 @@ module ActiveResource @connection.proxy = proxy if proxy @connection.user = user if user @connection.password = password if password + @connection.auth_type = auth_type if auth_type @connection.timeout = timeout if timeout @connection.ssl_options = ssl_options if ssl_options @connection @@ -731,12 +744,12 @@ module ActiveResource # Accepts a URI and creates the site URI from that. def create_site_uri_from(site) - site.is_a?(URI) ? site.dup : URI.parse(site) + site.is_a?(URI) ? site.dup : uri_parser.parse(site) end # Accepts a URI and creates the proxy URI from that. def create_proxy_uri_from(proxy) - proxy.is_a?(URI) ? proxy.dup : URI.parse(proxy) + proxy.is_a?(URI) ? proxy.dup : uri_parser.parse(proxy) end # contains a set of the current prefix parameters. @@ -761,6 +774,10 @@ module ActiveResource [ prefix_options, query_options ] end + + def uri_parser + @uri_parser ||= URI.const_defined?(:Parser) ? URI::Parser.new : URI + end end attr_accessor :attributes #:nodoc: @@ -1146,15 +1163,16 @@ module ActiveResource def respond_to?(method, include_priv = false) method_name = method.to_s if attributes.nil? - return super + super elsif attributes.has_key?(method_name) - return true - elsif ['?','='].include?(method_name.last) && attributes.has_key?(method_name.first(-1)) - return true + true + elsif method_name =~ /(?:=|\?)$/ && attributes.include?($`) + true + else + # super must be called at the end of the method, because the inherited respond_to? + # would return true for generated readers, even if the attribute wasn't present + super end - # super must be called at the end of the method, because the inherited respond_to? - # would return true for generated readers, even if the attribute wasn't present - super end protected @@ -1243,13 +1261,15 @@ module ActiveResource def method_missing(method_symbol, *arguments) #:nodoc: method_name = method_symbol.to_s - case method_name.last + if method_name =~ /(=|\?)$/ + case $1 when "=" - attributes[method_name.first(-1)] = arguments.first + attributes[$`] = arguments.first when "?" - attributes[method_name.first(-1)] - else - attributes.has_key?(method_name) ? attributes[method_name] : super + attributes[$`] + end + else + attributes.include?(method_name) ? attributes[method_name] : super end end end diff --git a/activeresource/lib/active_resource/connection.rb b/activeresource/lib/active_resource/connection.rb index 9d551f04e7..193be89a82 100644 --- a/activeresource/lib/active_resource/connection.rb +++ b/activeresource/lib/active_resource/connection.rb @@ -17,7 +17,7 @@ module ActiveResource :head => 'Accept' } - attr_reader :site, :user, :password, :timeout, :proxy, :ssl_options + attr_reader :site, :user, :password, :auth_type, :timeout, :proxy, :ssl_options attr_accessor :format class << self @@ -31,20 +31,21 @@ module ActiveResource def initialize(site, format = ActiveResource::Formats::XmlFormat) raise ArgumentError, 'Missing site URI' unless site @user = @password = nil + @uri_parser = URI.const_defined?(:Parser) ? URI::Parser.new : URI self.site = site self.format = format end # Set URI for remote service. def site=(site) - @site = site.is_a?(URI) ? site : URI.parse(site) - @user = URI.decode(@site.user) if @site.user - @password = URI.decode(@site.password) if @site.password + @site = site.is_a?(URI) ? site : @uri_parser.parse(site) + @user = @uri_parser.unescape(@site.user) if @site.user + @password = @uri_parser.unescape(@site.password) if @site.password end # Set the proxy for remote service. def proxy=(proxy) - @proxy = proxy.is_a?(URI) ? proxy : URI.parse(proxy) + @proxy = proxy.is_a?(URI) ? proxy : @uri_parser.parse(proxy) end # Sets the user for remote service. @@ -57,6 +58,11 @@ module ActiveResource @password = password end + # Sets the auth type for remote service. + def auth_type=(auth_type) + @auth_type = legitimize_auth_type(auth_type) + end + # Sets the number of seconds after which HTTP requests to the remote service should time out. def timeout=(timeout) @timeout = timeout @@ -70,31 +76,31 @@ module ActiveResource # Executes a GET request. # Used to get (find) resources. def get(path, headers = {}) - format.decode(request(:get, path, build_request_headers(headers, :get)).body) + with_auth { format.decode(request(:get, path, build_request_headers(headers, :get, self.site.merge(path))).body) } end # Executes a DELETE request (see HTTP protocol documentation if unfamiliar). # Used to delete resources. def delete(path, headers = {}) - request(:delete, path, build_request_headers(headers, :delete)) + with_auth { request(:delete, path, build_request_headers(headers, :delete, self.site.merge(path))) } end # Executes a PUT request (see HTTP protocol documentation if unfamiliar). # Used to update resources. def put(path, body = '', headers = {}) - request(:put, path, body.to_s, build_request_headers(headers, :put)) + with_auth { request(:put, path, body.to_s, build_request_headers(headers, :put, self.site.merge(path))) } end # Executes a POST request. # Used to create new resources. def post(path, body = '', headers = {}) - request(:post, path, body.to_s, build_request_headers(headers, :post)) + with_auth { request(:post, path, body.to_s, build_request_headers(headers, :post, self.site.merge(path))) } end # Executes a HEAD request. # Used to obtain meta-information about resources, such as whether they exist and their size (via response headers). def head(path, headers = {}) - request(:head, path, build_request_headers(headers, :head)) + with_auth { request(:head, path, build_request_headers(headers, :head, self.site.merge(path))) } end @@ -198,13 +204,70 @@ module ActiveResource end # Builds headers for request to remote service. - def build_request_headers(headers, http_method=nil) - authorization_header.update(default_header).update(http_format_header(http_method)).update(headers) + def build_request_headers(headers, http_method, uri) + authorization_header(http_method, uri).update(default_header).update(http_format_header(http_method)).update(headers) + end + + def response_auth_header + @response_auth_header ||= "" + end + + def with_auth + retried ||= false + yield + rescue UnauthorizedAccess => e + raise if retried || auth_type != :digest + @response_auth_header = e.response['WWW-Authenticate'] + retried = true + retry + end + + def authorization_header(http_method, uri) + if @user || @password + if auth_type == :digest + { 'Authorization' => digest_auth_header(http_method, uri) } + else + { 'Authorization' => 'Basic ' + ["#{@user}:#{@password}"].pack('m').delete("\r\n") } + end + else + {} + end end - # Sets authorization header - def authorization_header - (@user || @password ? { 'Authorization' => 'Basic ' + ["#{@user}:#{ @password}"].pack('m').delete("\r\n") } : {}) + def digest_auth_header(http_method, uri) + params = extract_params_from_response + + ha1 = Digest::MD5.hexdigest("#{@user}:#{params['realm']}:#{@password}") + ha2 = Digest::MD5.hexdigest("#{http_method.to_s.upcase}:#{uri.path}") + + params.merge!('cnonce' => client_nonce) + request_digest = Digest::MD5.hexdigest([ha1, params['nonce'], "0", params['cnonce'], params['qop'], ha2].join(":")) + "Digest #{auth_attributes_for(uri, request_digest, params)}" + end + + def client_nonce + Digest::MD5.hexdigest("%x" % (Time.now.to_i + rand(65535))) + end + + def extract_params_from_response + params = {} + if response_auth_header =~ /^(\w+) (.*)/ + $2.gsub(/(\w+)="(.*?)"/) { params[$1] = $2 } + end + params + end + + def auth_attributes_for(uri, request_digest, params) + [ + %Q(username="#{@user}"), + %Q(realm="#{params['realm']}"), + %Q(qop="#{params['qop']}"), + %Q(uri="#{uri.path}"), + %Q(nonce="#{params['nonce']}"), + %Q(nc="0"), + %Q(cnonce="#{params['cnonce']}"), + %Q(opaque="#{params['opaque']}"), + %Q(response="#{request_digest}")].join(", ") end def http_format_header(http_method) @@ -214,5 +277,11 @@ module ActiveResource def logger #:nodoc: Base.logger end + + def legitimize_auth_type(auth_type) + return :basic if auth_type.nil? + auth_type = auth_type.to_sym + [:basic, :digest].include?(auth_type) ? auth_type : :basic + end end end diff --git a/activeresource/test/abstract_unit.rb b/activeresource/test/abstract_unit.rb index 05efac79cf..10849be20c 100644 --- a/activeresource/test/abstract_unit.rb +++ b/activeresource/test/abstract_unit.rb @@ -1,8 +1,17 @@ +root = File.expand_path('../../..', __FILE__) +begin + require "#{root}/vendor/gems/environment" +rescue LoadError + $:.unshift("#{root}/activesupport/lib") +end + +lib = File.expand_path("#{File.dirname(__FILE__)}/../lib") +$:.unshift(lib) unless $:.include?('lib') || $:.include?(lib) + require 'rubygems' require 'test/unit' +require 'active_support' require 'active_support/test_case' - -$:.unshift "#{File.dirname(__FILE__)}/../lib" require 'active_resource' $:.unshift "#{File.dirname(__FILE__)}/../test" diff --git a/activeresource/test/cases/authorization_test.rb b/activeresource/test/cases/authorization_test.rb index ca25f437e3..1a7c9ec8a4 100644 --- a/activeresource/test/cases/authorization_test.rb +++ b/activeresource/test/cases/authorization_test.rb @@ -8,46 +8,75 @@ class AuthorizationTest < Test::Unit::TestCase @matz = { :id => 1, :name => 'Matz' }.to_xml(:root => 'person') @david = { :id => 2, :name => 'David' }.to_xml(:root => 'person') @authenticated_conn = ActiveResource::Connection.new("http://david:test123@localhost") - @authorization_request_header = { 'Authorization' => 'Basic ZGF2aWQ6dGVzdDEyMw==' } + @basic_authorization_request_header = { 'Authorization' => 'Basic ZGF2aWQ6dGVzdDEyMw==' } + + @nonce = "MTI0OTUxMzc4NzpjYWI3NDM3NDNmY2JmODU4ZjQ2ZjcwNGZkMTJiMjE0NA==" ActiveResource::HttpMock.respond_to do |mock| - mock.get "/people/2.xml", @authorization_request_header, @david - mock.put "/people/2.xml", @authorization_request_header, nil, 204 - mock.delete "/people/2.xml", @authorization_request_header, nil, 200 - mock.post "/people/2/addresses.xml", @authorization_request_header, nil, 201, 'Location' => '/people/1/addresses/5' + mock.get "/people/2.xml", @basic_authorization_request_header, @david + mock.get "/people/1.xml", @basic_authorization_request_header, nil, 401, { 'WWW-Authenticate' => 'i_should_be_ignored' } + mock.put "/people/2.xml", @basic_authorization_request_header, nil, 204 + mock.delete "/people/2.xml", @basic_authorization_request_header, nil, 200 + mock.post "/people/2/addresses.xml", @basic_authorization_request_header, nil, 201, 'Location' => '/people/1/addresses/5' + mock.head "/people/2.xml", @basic_authorization_request_header, nil, 200 + + mock.get "/people/2.xml", { 'Authorization' => blank_digest_auth_header("/people/2.xml", "a10c9bd131c9d4d7755b8f4706fd04af") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header } + mock.get "/people/2.xml", { 'Authorization' => request_digest_auth_header("/people/2.xml", "912c7a643f18cda562b8d9662c47b6f5") }, @david, 200 + mock.get "/people/1.xml", { 'Authorization' => request_digest_auth_header("/people/1.xml", "d76e675c0ecfa2bb1abe01491b068a06") }, @matz, 200 + + mock.put "/people/2.xml", { 'Authorization' => blank_digest_auth_header("/people/2.xml", "7de8a265a5be3c4c2d3a246562ecd6bd") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header } + mock.put "/people/2.xml", { 'Authorization' => request_digest_auth_header("/people/2.xml", "3fb3b33d9d0b869cc75815aa11faacd9") }, nil, 204 + + mock.delete "/people/2.xml", { 'Authorization' => blank_digest_auth_header("/people/2.xml", "07dfc32769a34ea3510d3a77d64ca495") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header } + mock.delete "/people/2.xml", { 'Authorization' => request_digest_auth_header("/people/2.xml", "5d438610de7ec163b29096c9afcbb254") }, nil, 200 + + mock.post "/people/2/addresses.xml", { 'Authorization' => blank_digest_auth_header("/people/2/addresses.xml", "966dab13620421f928d051f2b9d7b9af") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header } + mock.post "/people/2/addresses.xml", { 'Authorization' => request_digest_auth_header("/people/2/addresses.xml", "ed540d032c63f8ee34959116c090ec45") }, nil, 201, 'Location' => '/people/1/addresses/5' + + mock.head "/people/2.xml", { 'Authorization' => blank_digest_auth_header("/people/2.xml", "2854eeb92cce2aed29350ea0ce7ba1e2") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header } + mock.head "/people/2.xml", { 'Authorization' => request_digest_auth_header("/people/2.xml", "07cd4d247e9c130f92ba2501a080b328") }, nil, 200 + end + + # Make client nonce deterministic + class << @authenticated_conn + private + + def client_nonce + 'i-am-a-client-nonce' + end end end def test_authorization_header - authorization_header = @authenticated_conn.__send__(:authorization_header) - assert_equal @authorization_request_header['Authorization'], authorization_header['Authorization'] + authorization_header = @authenticated_conn.__send__(:authorization_header, :get, URI.parse('/people/2.xml')) + assert_equal @basic_authorization_request_header['Authorization'], authorization_header['Authorization'] authorization = authorization_header["Authorization"].to_s.split - + assert_equal "Basic", authorization[0] assert_equal ["david", "test123"], ActiveSupport::Base64.decode64(authorization[1]).split(":")[0..1] end - + def test_authorization_header_with_username_but_no_password @conn = ActiveResource::Connection.new("http://david:@localhost") - authorization_header = @conn.__send__(:authorization_header) + authorization_header = @conn.__send__(:authorization_header, :get, URI.parse('/people/2.xml')) authorization = authorization_header["Authorization"].to_s.split - + assert_equal "Basic", authorization[0] assert_equal ["david"], ActiveSupport::Base64.decode64(authorization[1]).split(":")[0..1] end - + def test_authorization_header_with_password_but_no_username @conn = ActiveResource::Connection.new("http://:test123@localhost") - authorization_header = @conn.__send__(:authorization_header) + authorization_header = @conn.__send__(:authorization_header, :get, URI.parse('/people/2.xml')) authorization = authorization_header["Authorization"].to_s.split - + assert_equal "Basic", authorization[0] assert_equal ["", "test123"], ActiveSupport::Base64.decode64(authorization[1]).split(":")[0..1] end - + def test_authorization_header_with_decoded_credentials_from_url @conn = ActiveResource::Connection.new("http://my%40email.com:%31%32%33@localhost") - authorization_header = @conn.__send__(:authorization_header) + authorization_header = @conn.__send__(:authorization_header, :get, URI.parse('/people/2.xml')) authorization = authorization_header["Authorization"].to_s.split assert_equal "Basic", authorization[0] @@ -58,8 +87,8 @@ class AuthorizationTest < Test::Unit::TestCase @authenticated_conn = ActiveResource::Connection.new("http://@localhost") @authenticated_conn.user = 'david' @authenticated_conn.password = 'test123' - authorization_header = @authenticated_conn.__send__(:authorization_header) - assert_equal @authorization_request_header['Authorization'], authorization_header['Authorization'] + authorization_header = @authenticated_conn.__send__(:authorization_header, :get, URI.parse('/people/2.xml')) + assert_equal @basic_authorization_request_header['Authorization'], authorization_header['Authorization'] authorization = authorization_header["Authorization"].to_s.split assert_equal "Basic", authorization[0] @@ -69,7 +98,7 @@ class AuthorizationTest < Test::Unit::TestCase def test_authorization_header_explicitly_setting_username_but_no_password @conn = ActiveResource::Connection.new("http://@localhost") @conn.user = "david" - authorization_header = @conn.__send__(:authorization_header) + authorization_header = @conn.__send__(:authorization_header, :get, URI.parse('/people/2.xml')) authorization = authorization_header["Authorization"].to_s.split assert_equal "Basic", authorization[0] @@ -79,38 +108,119 @@ class AuthorizationTest < Test::Unit::TestCase def test_authorization_header_explicitly_setting_password_but_no_username @conn = ActiveResource::Connection.new("http://@localhost") @conn.password = "test123" - authorization_header = @conn.__send__(:authorization_header) + authorization_header = @conn.__send__(:authorization_header, :get, URI.parse('/people/2.xml')) authorization = authorization_header["Authorization"].to_s.split assert_equal "Basic", authorization[0] assert_equal ["", "test123"], ActiveSupport::Base64.decode64(authorization[1]).split(":")[0..1] end + def test_authorization_header_if_credentials_supplied_and_auth_type_is_basic + @authenticated_conn.auth_type = :basic + authorization_header = @authenticated_conn.__send__(:authorization_header, :get, URI.parse('/people/2.xml')) + assert_equal @basic_authorization_request_header['Authorization'], authorization_header['Authorization'] + authorization = authorization_header["Authorization"].to_s.split + + assert_equal "Basic", authorization[0] + assert_equal ["david", "test123"], ActiveSupport::Base64.decode64(authorization[1]).split(":")[0..1] + end + + def test_authorization_header_if_credentials_supplied_and_auth_type_is_digest + @authenticated_conn.auth_type = :digest + authorization_header = @authenticated_conn.__send__(:authorization_header, :get, URI.parse('/people/2.xml')) + assert_equal blank_digest_auth_header("/people/2.xml", "a10c9bd131c9d4d7755b8f4706fd04af"), authorization_header['Authorization'] + end + def test_get david = @authenticated_conn.get("/people/2.xml") assert_equal "David", david["name"] end - + def test_post response = @authenticated_conn.post("/people/2/addresses.xml") assert_equal "/people/1/addresses/5", response["Location"] end - + def test_put response = @authenticated_conn.put("/people/2.xml") assert_equal 204, response.code end - + def test_delete response = @authenticated_conn.delete("/people/2.xml") assert_equal 200, response.code end + def test_head + response = @authenticated_conn.head("/people/2.xml") + assert_equal 200, response.code + end + + def test_get_with_digest_auth_handles_initial_401_response_and_retries + @authenticated_conn.auth_type = :digest + response = @authenticated_conn.get("/people/2.xml") + assert_equal "David", response["name"] + end + + def test_post_with_digest_auth_handles_initial_401_response_and_retries + @authenticated_conn.auth_type = :digest + response = @authenticated_conn.post("/people/2/addresses.xml") + assert_equal "/people/1/addresses/5", response["Location"] + assert_equal 201, response.code + end + + def test_put_with_digest_auth_handles_initial_401_response_and_retries + @authenticated_conn.auth_type = :digest + response = @authenticated_conn.put("/people/2.xml") + assert_equal 204, response.code + end + + def test_delete_with_digest_auth_handles_initial_401_response_and_retries + @authenticated_conn.auth_type = :digest + response = @authenticated_conn.delete("/people/2.xml") + assert_equal 200, response.code + end + + def test_head_with_digest_auth_handles_initial_401_response_and_retries + @authenticated_conn.auth_type = :digest + response = @authenticated_conn.head("/people/2.xml") + assert_equal 200, response.code + end + + def test_get_with_digest_auth_caches_nonce + @authenticated_conn.auth_type = :digest + response = @authenticated_conn.get("/people/2.xml") + assert_equal "David", response["name"] + + # There is no mock for this request with a non-cached nonce. + response = @authenticated_conn.get("/people/1.xml") + assert_equal "Matz", response["name"] + end + + def test_retry_on_401_only_happens_with_digest_auth + assert_raise(ActiveResource::UnauthorizedAccess) { @authenticated_conn.get("/people/1.xml") } + assert_equal "", @authenticated_conn.send(:response_auth_header) + end + def test_raises_invalid_request_on_unauthorized_requests - assert_raise(ActiveResource::InvalidRequestError) { @conn.post("/people/2.xml") } + assert_raise(ActiveResource::InvalidRequestError) { @conn.get("/people/2.xml") } + assert_raise(ActiveResource::InvalidRequestError) { @conn.post("/people/2/addresses.xml") } + assert_raise(ActiveResource::InvalidRequestError) { @conn.put("/people/2.xml") } + assert_raise(ActiveResource::InvalidRequestError) { @conn.delete("/people/2.xml") } + assert_raise(ActiveResource::InvalidRequestError) { @conn.head("/people/2.xml") } + end + + def test_raises_invalid_request_on_unauthorized_requests_with_digest_auth + @conn.auth_type = :digest + assert_raise(ActiveResource::InvalidRequestError) { @conn.get("/people/2.xml") } assert_raise(ActiveResource::InvalidRequestError) { @conn.post("/people/2/addresses.xml") } assert_raise(ActiveResource::InvalidRequestError) { @conn.put("/people/2.xml") } assert_raise(ActiveResource::InvalidRequestError) { @conn.delete("/people/2.xml") } + assert_raise(ActiveResource::InvalidRequestError) { @conn.head("/people/2.xml") } + end + + def test_client_nonce_is_not_nil + assert_not_nil ActiveResource::Connection.new("http://david:test123@localhost").send(:client_nonce) end protected @@ -119,4 +229,16 @@ class AuthorizationTest < Test::Unit::TestCase @conn.__send__(:handle_response, Response.new(code)) end end + + def blank_digest_auth_header(uri, response) + %Q(Digest username="david", realm="", qop="", uri="#{uri}", nonce="", nc="0", cnonce="i-am-a-client-nonce", opaque="", response="#{response}") + end + + def request_digest_auth_header(uri, response) + %Q(Digest username="david", realm="RailsTestApp", qop="auth", uri="#{uri}", nonce="#{@nonce}", nc="0", cnonce="i-am-a-client-nonce", opaque="ef6dfb078ba22298d366f99567814ffb", response="#{response}") + end + + def response_digest_auth_header + %Q(Digest realm="RailsTestApp", qop="auth", algorithm=MD5, nonce="#{@nonce}", opaque="ef6dfb078ba22298d366f99567814ffb") + end end diff --git a/activeresource/test/cases/base/load_test.rb b/activeresource/test/cases/base/load_test.rb index 189a4d81fe..7745a9439b 100644 --- a/activeresource/test/cases/base/load_test.rb +++ b/activeresource/test/cases/base/load_test.rb @@ -1,7 +1,6 @@ require 'abstract_unit' require "fixtures/person" require "fixtures/street_address" -require 'active_support/core_ext/symbol' require 'active_support/core_ext/hash/conversions' module Highrise diff --git a/activeresource/test/cases/base_test.rb b/activeresource/test/cases/base_test.rb index 1593e25595..1d3f7891ec 100644 --- a/activeresource/test/cases/base_test.rb +++ b/activeresource/test/cases/base_test.rb @@ -163,6 +163,12 @@ class BaseTest < Test::Unit::TestCase assert_equal('test123', Forum.connection.password) end + def test_should_accept_setting_auth_type + Forum.auth_type = :digest + assert_equal(:digest, Forum.auth_type) + assert_equal(:digest, Forum.connection.auth_type) + end + def test_should_accept_setting_timeout Forum.timeout = 5 assert_equal(5, Forum.timeout) diff --git a/activeresource/test/connection_test.rb b/activeresource/test/connection_test.rb index 2a3e04272a..a2744d7531 100644 --- a/activeresource/test/connection_test.rb +++ b/activeresource/test/connection_test.rb @@ -225,6 +225,21 @@ class ConnectionTest < Test::Unit::TestCase assert_raise(ActiveResource::SSLError) { @conn.get('/people/1.xml') } end + def test_auth_type_can_be_string + @conn.auth_type = 'digest' + assert_equal(:digest, @conn.auth_type) + end + + def test_auth_type_defaults_to_basic + @conn.auth_type = nil + assert_equal(:basic, @conn.auth_type) + end + + def test_auth_type_ignores_nonsensical_values + @conn.auth_type = :wibble + assert_equal(:basic, @conn.auth_type) + end + protected def assert_response_raises(klass, code) assert_raise(klass, "Expected response code #{code} to raise #{klass}") do |