Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Add Basic HTTP Authentication to ActiveResource (closes #6305). [jona…

…than]

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@5208 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
  • Loading branch information...
commit 7ac6ed893fbfe9b4d4ce0e0ef18c3fecfbd48ff4 1 parent d15d15b
risk danger olson technoweenie authored
2  activeresource/CHANGELOG
View
@@ -1,5 +1,7 @@
*SVN*
+* Add Basic HTTP Authentication to ActiveResource (closes #6305). [jonathan]
+
* Extracted #id_from_response as an entry point for customizing how a created resource gets its own ID.
By default, it extracts from the Location response header.
24 activeresource/lib/active_resource/connection.rb
View
@@ -25,7 +25,7 @@ class ServerError < ConnectionError; end # 5xx Server Error
class Connection
- attr_accessor :site
+ attr_reader :site
class << self
def requests
@@ -39,23 +39,27 @@ class << self ; attr_reader :default_header end
end
def initialize(site)
- @site = site
+ self.site = site.is_a?(URI) ? site : URI.parse(site)
+ end
+
+ def site=(site)
+ @site = site.is_a?(URI) ? site : URI.parse(site)
end
def get(path)
- Hash.from_xml(request(:get, path).body)
+ Hash.from_xml(request(:get, path, build_request_headers).body)
end
def delete(path)
- request(:delete, path, self.class.default_header)
+ request(:delete, path, build_request_headers)
end
def put(path, body = '')
- request(:put, path, body, self.class.default_header)
+ request(:put, path, body, build_request_headers)
end
def post(path, body = '')
- request(:post, path, body, self.class.default_header)
+ request(:post, path, body, build_request_headers)
end
private
@@ -91,5 +95,13 @@ def http
@http
end
+
+ def build_request_headers
+ authorization_header.update(self.class.default_header)
+ end
+
+ def authorization_header
+ (@site.user || @site.password ? { 'Authorization' => 'Basic ' + ["#{@site.user}:#{ @site.password}"].pack('m').delete("\r\n") } : {})
+ end
end
end
82 activeresource/test/authorization_test.rb
View
@@ -0,0 +1,82 @@
+require "#{File.dirname(__FILE__)}/abstract_unit"
+require 'base64'
+
+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")
+ @authorization_request_header = { 'Authorization' => 'Basic ZGF2aWQ6dGVzdDEyMw==' }
+
+ 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'
+ end
+ end
+
+ def test_authorization_header
+ authorization_header = @authenticated_conn.send(:authorization_header)
+ assert_equal @authorization_request_header['Authorization'], authorization_header['Authorization']
+ authorization = authorization_header["Authorization"].to_s.split
+
+ assert_equal "Basic", authorization[0]
+ assert_equal ["david", "test123"], 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 = authorization_header["Authorization"].to_s.split
+
+ assert_equal "Basic", authorization[0]
+ assert_equal ["david"], 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 = authorization_header["Authorization"].to_s.split
+
+ assert_equal "Basic", authorization[0]
+ assert_equal ["", "test123"], Base64.decode64(authorization[1]).split(":")[0..1]
+ end
+
+ def test_get
+ david = @authenticated_conn.get("/people/2.xml")
+ assert_equal "David", david["person"]["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_raises_invalid_request_on_unauthorized_requests
+ assert_raises(ActiveResource::InvalidRequestError) { @conn.post("/people/2.xml") }
+ assert_raises(ActiveResource::InvalidRequestError) { @conn.post("/people/2/addresses.xml") }
+ assert_raises(ActiveResource::InvalidRequestError) { @conn.put("/people/2.xml") }
+ assert_raises(ActiveResource::InvalidRequestError) { @conn.delete("/people/2.xml") }
+ 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
+end
2  activeresource/test/base_errors_test.rb
View
@@ -4,7 +4,7 @@
class BaseErrorsTest < Test::Unit::TestCase
def setup
ActiveResource::HttpMock.respond_to do |mock|
- mock.post "/people.xml", "<?xml version=\"1.0\" encoding=\"UTF-8\"?><errors><error>Age can't be blank</error><error>Name can't be blank</error><error>Name must start with a letter</error><error>Person quota full for today.</error></errors>", 400
+ mock.post "/people.xml", {}, "<?xml version=\"1.0\" encoding=\"UTF-8\"?><errors><error>Age can't be blank</error><error>Name can't be blank</error><error>Name must start with a letter</error><error>Person quota full for today.</error></errors>", 400
end
@exception = nil
@person = Person.new(:name => '', :age => '')
46 activeresource/test/base_test.rb
View
@@ -7,25 +7,27 @@ def setup
@matz = { :id => 1, :name => 'Matz' }.to_xml(:root => 'person')
@david = { :id => 2, :name => 'David' }.to_xml(:root => 'person')
@addy = { :id => 1, :street => '12345 Street' }.to_xml(:root => 'address')
+ @default_request_headers = { 'Content-Type' => 'application/xml' }
+
ActiveResource::HttpMock.respond_to do |mock|
- mock.get "/people/1.xml", @matz
- mock.get "/people/2.xml", @david
- mock.put "/people/1.xml", nil, 204
- mock.delete "/people/1.xml", nil, 200
- mock.delete "/people/2.xml", nil, 400
- mock.post "/people.xml", nil, 201, 'Location' => '/people/5.xml'
- mock.get "/people/99.xml", nil, 404
- mock.get "/people.xml", "<people>#{@matz}#{@david}</people>"
- mock.get "/people/1/addresses.xml", "<addresses>#{@addy}</addresses>"
- mock.get "/people/1/addresses/1.xml", @addy
- mock.put "/people/1/addresses/1.xml", nil, 204
- mock.delete "/people/1/addresses/1.xml", nil, 200
- mock.post "/people/1/addresses.xml", nil, 201, 'Location' => '/people/1/addresses/5'
- mock.get "/people//addresses.xml", nil, 404
- mock.get "/people//addresses/1.xml", nil, 404
- mock.put "/people//addresses/1.xml", nil, 404
- mock.delete "/people//addresses/1.xml", nil, 404
- mock.post "/people//addresses.xml", nil, 404
+ mock.get "/people/1.xml", {}, @matz
+ mock.get "/people/2.xml", {}, @david
+ mock.put "/people/1.xml", {}, nil, 204
+ mock.delete "/people/1.xml", {}, nil, 200
+ mock.delete "/people/2.xml", {}, nil, 400
+ mock.post "/people.xml", {}, nil, 201, 'Location' => '/people/5.xml'
+ mock.get "/people/99.xml", {}, nil, 404
+ mock.get "/people.xml", {}, "<people>#{@matz}#{@david}</people>"
+ mock.get "/people/1/addresses.xml", {}, "<addresses>#{@addy}</addresses>"
+ mock.get "/people/1/addresses/1.xml", {}, @addy
+ mock.put "/people/1/addresses/1.xml", {}, nil, 204
+ mock.delete "/people/1/addresses/1.xml", {}, nil, 200
+ mock.post "/people/1/addresses.xml", {}, nil, 201, 'Location' => '/people/1/addresses/5'
+ mock.get "/people//addresses.xml", {}, nil, 404
+ mock.get "/people//addresses/1.xml", {}, nil, 404
+ mock.put "/people//addresses/1.xml", {}, nil, 404
+ mock.delete "/people//addresses/1.xml", {}, nil, 404
+ mock.post "/people//addresses.xml", {}, nil, 404
end
end
@@ -144,8 +146,8 @@ def test_update_with_custom_prefix
def test_update_conflict
ActiveResource::HttpMock.respond_to do |mock|
- mock.get "/people/2.xml", @david
- mock.put "/people/2.xml", nil, 409
+ mock.get "/people/2.xml", {}, @david
+ mock.put "/people/2.xml", @default_request_headers, nil, 409
end
assert_raises(ActiveResource::ResourceConflict) { Person.find(2).save }
end
@@ -153,7 +155,7 @@ def test_update_conflict
def test_destroy
assert Person.find(1).destroy
ActiveResource::HttpMock.respond_to do |mock|
- mock.get "/people/1.xml", nil, 404
+ mock.get "/people/1.xml", {}, nil, 404
end
assert_raises(ActiveResource::ResourceNotFound) { Person.find(1).destroy }
end
@@ -161,7 +163,7 @@ def test_destroy
def test_destroy_with_custom_prefix
assert StreetAddress.find(1, :person_id => 1).destroy
ActiveResource::HttpMock.respond_to do |mock|
- mock.get "/people/1/addresses/1.xml", nil, 404
+ mock.get "/people/1/addresses/1.xml", {}, nil, 404
end
assert_raises(ActiveResource::ResourceNotFound) { StreetAddress.find(1, :person_id => 1).destroy }
end
40 activeresource/test/connection_test.rb
View
@@ -1,10 +1,20 @@
require "#{File.dirname(__FILE__)}/abstract_unit"
+require 'base64'
class ConnectionTest < 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')
+ @default_request_headers = { 'Content-Type' => 'application/xml' }
+ ActiveResource::HttpMock.respond_to do |mock|
+ mock.get "/people/1.xml", {}, @matz
+ mock.put "/people/1.xml", {}, nil, 204
+ mock.delete "/people/1.xml", {}, nil, 200
+ mock.post "/people.xml", {}, nil, 201, 'Location' => '/people/5.xml'
+ end
end
def test_handle_response
@@ -38,7 +48,37 @@ def test_handle_response
assert_response_raises ActiveResource::ConnectionError, code
end
end
+
+ def test_site_accessor_accepts_uri_or_string_argument
+ site = URI.parse("http://localhost")
+
+ assert_nothing_raised { @conn.site = "http://localhost" }
+ assert_equal site, @conn.site
+ assert_nothing_raised { @conn.site = site }
+ assert_equal site, @conn.site
+ end
+
+ def test_get
+ matz = @conn.get("/people/1.xml")
+ assert_equal "Matz", matz["person"]["name"]
+ end
+
+ def test_post
+ response = @conn.post("/people.xml")
+ assert_equal "/people/5.xml", response["Location"]
+ end
+
+ def test_put
+ response = @conn.put("/people/1.xml")
+ assert_equal 204, response.code
+ end
+
+ def test_delete
+ response = @conn.delete("/people/1.xml")
+ assert_equal 200, response.code
+ end
+
protected
def assert_response_raises(klass, code)
assert_raise(klass, "Expected response code #{code} to raise #{klass}") do
33 activeresource/test/http_mock.rb
View
@@ -1,6 +1,8 @@
require 'active_resource/connection'
module ActiveResource
+ class InvalidRequestError < StandardError; end
+
class HttpMock
class Responder
def initialize(responses)
@@ -9,8 +11,8 @@ def initialize(responses)
for method in [ :post, :put, :get, :delete ]
module_eval <<-EOE
- def #{method}(path, body = nil, status = 200, headers = {})
- @responses[Request.new(:#{method}, path, nil)] = Response.new(body || {}, status, headers)
+ def #{method}(path, request_headers = {}, body = nil, status = 200, response_headers = {})
+ @responses[Request.new(:#{method}, path, nil, request_headers)] = Response.new(body || {}, status, response_headers)
end
EOE
end
@@ -39,12 +41,22 @@ def reset!
end
end
- for method in [ :post, :put, :get, :delete ]
+ for method in [ :post, :put ]
+ module_eval <<-EOE
+ def #{method}(path, body, headers)
+ request = ActiveResource::Request.new(:#{method}, path, body, headers)
+ self.class.requests << request
+ self.class.responses[request] || raise(InvalidRequestError.new("No response recorded for: \#{request}"))
+ end
+ EOE
+ end
+
+ for method in [ :get, :delete ]
module_eval <<-EOE
- def #{method}(*arguments)
- request = ActiveResource::Request.new(:#{method}, *arguments)
+ def #{method}(path, headers)
+ request = ActiveResource::Request.new(:#{method}, path, nil, headers)
self.class.requests << request
- self.class.responses[request] || raise("No response recorded for: \#{request}")
+ self.class.responses[request] || raise(InvalidRequestError.new("No response recorded for: \#{request}"))
end
EOE
end
@@ -55,10 +67,11 @@ def initialize(site)
end
class Request
- attr_accessor :path, :method, :body
+ attr_accessor :path, :method, :body, :headers
def initialize(method, path, body = nil, headers = nil)
- @method, @path, @body = method, path, body
+ @method, @path, @body, @headers = method, path, body, headers
+ @headers.update('Content-Type' => 'application/xml')
end
def ==(other_request)
@@ -70,11 +83,11 @@ def eql?(other_request)
end
def to_s
- "<#{method.to_s.upcase}: #{path} (#{body})>"
+ "<#{method.to_s.upcase}: #{path} [#{headers}] (#{body})>"
end
def hash
- "#{path}#{method}".hash
+ "#{path}#{method}#{headers}".hash
end
end
Please sign in to comment.
Something went wrong with that request. Please try again.