Files
rails/activeresource/test/cases/authorization_test.rb
Jacques Crocker 51f1f550da Allow per Resource format settings
Previously, ActiveResource was using the connection level formatter for get requests. This made it impossible to use custom formatters per resource.

Additionally this commit makes the Connection request methods more consistent. It always returns a Response. The base will then decode it each the response using its format setting.

Merging this commit will allow users to add custom formatters on a per Resource basis. This enables handling pagination responses from the server side, a very common use case that was previously impossible without monkeypatching XmlFormat.

Signed-off-by: José Valim <jose.valim@gmail.com>
2010-09-25 13:54:09 +02:00

249 lines
12 KiB
Ruby

require 'abstract_unit'
class AuthorizationTest < Test::Unit::TestCase
Response = Struct.new(:code)
def setup
@conn = ActiveResource::Connection.new('http://localhost')
@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")
@basic_authorization_request_header = { 'Authorization' => 'Basic ZGF2aWQ6dGVzdDEyMw==' }
@nonce = "MTI0OTUxMzc4NzpjYWI3NDM3NDNmY2JmODU4ZjQ2ZjcwNGZkMTJiMjE0NA=="
ActiveResource::HttpMock.respond_to do |mock|
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, :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, :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, :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, :get, URI.parse('/people/2.xml'))
authorization = authorization_header["Authorization"].to_s.split
assert_equal "Basic", authorization[0]
assert_equal ["my@email.com", "123"], ActiveSupport::Base64.decode64(authorization[1]).split(":")[0..1]
end
def test_authorization_header_explicitly_setting_username_and_password
@authenticated_conn = ActiveResource::Connection.new("http://@localhost")
@authenticated_conn.user = 'david'
@authenticated_conn.password = 'test123'
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_explicitly_setting_username_but_no_password
@conn = ActiveResource::Connection.new("http://@localhost")
@conn.user = "david"
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_explicitly_setting_password_but_no_username
@conn = ActiveResource::Connection.new("http://@localhost")
@conn.password = "test123"
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 = decode(@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", decode(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", decode(response)["name"]
# There is no mock for this request with a non-cached nonce.
response = @authenticated_conn.get("/people/1.xml")
assert_equal "Matz", decode(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.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
def assert_response_raises(klass, code)
assert_raise(klass, "Expected response code #{code} to raise #{klass}") do
@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
def decode(response)
@authenticated_conn.format.decode(response.body)
end
end