mirror of
https://github.com/github/rails.git
synced 2026-04-04 03:00:58 -04:00
Add user and password configuration options to ActiveResource::Base, not all credentials can be specified inline. Closes #11112 [ernesto.jimenez]
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@8891 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
@@ -85,16 +85,26 @@ module ActiveResource
|
||||
# == Authentication
|
||||
#
|
||||
# Many REST APIs will require authentication, usually in the form of basic
|
||||
# HTTP authentication. Authentication can be specified by putting the credentials
|
||||
# in the +site+ variable of the Active Resource class you need to authenticate.
|
||||
# HTTP authentication. Authentication can be specified by:
|
||||
# * putting the credentials in the URL for the +site+ variable.
|
||||
#
|
||||
# class Person < ActiveResource::Base
|
||||
# self.site = "http://ryan:password@api.people.com:3000/"
|
||||
# end
|
||||
# class Person < ActiveResource::Base
|
||||
# self.site = "http://ryan:password@api.people.com:3000/"
|
||||
# end
|
||||
#
|
||||
# * defining +user+ and/or +password+ variables
|
||||
#
|
||||
# class Person < ActiveResource::Base
|
||||
# self.site = "http://api.people.com:3000/"
|
||||
# self.user = "ryan"
|
||||
# self.password = "password"
|
||||
# end
|
||||
#
|
||||
# For obvious security reasons, it is probably best if such services are available
|
||||
# over HTTPS.
|
||||
#
|
||||
# Note: Some values cannot be provided in the URL passed to site. e.g. email addresses
|
||||
# as usernames. In those situations you should use the seperate user and password option.
|
||||
# == Errors & Validation
|
||||
#
|
||||
# Error handling and validation is handled in much the same manner as you're used to seeing in
|
||||
@@ -164,6 +174,21 @@ module ActiveResource
|
||||
# Gets the URI of the REST resources to map for this class. The site variable is required
|
||||
# ActiveResource's mapping to work.
|
||||
def site
|
||||
# Not using superclass_delegating_reader because don't want subclasses to modify superclass instance
|
||||
#
|
||||
# With superclass_delegating_reader
|
||||
#
|
||||
# Parent.site = 'http://anonymous@test.com'
|
||||
# Subclass.site # => 'http://anonymous@test.com'
|
||||
# Subclass.site.user = 'david'
|
||||
# Parent.site # => 'http://david@test.com'
|
||||
#
|
||||
# Without superclass_delegating_reader (expected behaviour)
|
||||
#
|
||||
# Parent.site = 'http://anonymous@test.com'
|
||||
# Subclass.site # => 'http://anonymous@test.com'
|
||||
# Subclass.site.user = 'david' # => TypeError: can't modify frozen object
|
||||
#
|
||||
if defined?(@site)
|
||||
@site
|
||||
elsif superclass != Object && superclass.site
|
||||
@@ -175,7 +200,45 @@ module ActiveResource
|
||||
# The site variable is required ActiveResource's mapping to work.
|
||||
def site=(site)
|
||||
@connection = nil
|
||||
@site = site.nil? ? nil : create_site_uri_from(site)
|
||||
if site.nil?
|
||||
@site = nil
|
||||
else
|
||||
@site = create_site_uri_from(site)
|
||||
@user = @site.user if @site.user
|
||||
@password = @site.password if @site.password
|
||||
end
|
||||
end
|
||||
|
||||
# Gets the user for REST HTTP authentication
|
||||
def user
|
||||
# Not using superclass_delegating_reader. See +site+ for explanation
|
||||
if defined?(@user)
|
||||
@user
|
||||
elsif superclass != Object && superclass.user
|
||||
superclass.user.dup.freeze
|
||||
end
|
||||
end
|
||||
|
||||
# Sets the user for REST HTTP authentication
|
||||
def user=(user)
|
||||
@connection = nil
|
||||
@user = user
|
||||
end
|
||||
|
||||
# Gets the password for REST HTTP authentication
|
||||
def password
|
||||
# Not using superclass_delegating_reader. See +site+ for explanation
|
||||
if defined?(@password)
|
||||
@password
|
||||
elsif superclass != Object && superclass.password
|
||||
superclass.password.dup.freeze
|
||||
end
|
||||
end
|
||||
|
||||
# Sets the password for REST HTTP authentication
|
||||
def password=(password)
|
||||
@connection = nil
|
||||
@password = password
|
||||
end
|
||||
|
||||
# Sets the format that attributes are sent and received in from a mime type reference. Example:
|
||||
@@ -206,6 +269,8 @@ module ActiveResource
|
||||
def connection(refresh = false)
|
||||
if defined?(@connection) || superclass == Object
|
||||
@connection = Connection.new(site, format) if refresh || @connection.nil?
|
||||
@connection.user = user if user
|
||||
@connection.password = password if password
|
||||
@connection
|
||||
else
|
||||
superclass.connection
|
||||
|
||||
@@ -55,7 +55,7 @@ module ActiveResource
|
||||
# This class is used by ActiveResource::Base to interface with REST
|
||||
# services.
|
||||
class Connection
|
||||
attr_reader :site
|
||||
attr_reader :site, :user, :password
|
||||
attr_accessor :format
|
||||
|
||||
class << self
|
||||
@@ -68,6 +68,7 @@ module ActiveResource
|
||||
# attribute to the URI for the remote resource service.
|
||||
def initialize(site, format = ActiveResource::Formats[:xml])
|
||||
raise ArgumentError, 'Missing site URI' unless site
|
||||
@user = @password = nil
|
||||
self.site = site
|
||||
self.format = format
|
||||
end
|
||||
@@ -75,6 +76,18 @@ module ActiveResource
|
||||
# Set URI for remote service.
|
||||
def site=(site)
|
||||
@site = site.is_a?(URI) ? site : URI.parse(site)
|
||||
@user = @site.user if @site.user
|
||||
@password = @site.password if @site.password
|
||||
end
|
||||
|
||||
# Set user for remote service.
|
||||
def user=(user)
|
||||
@user = user
|
||||
end
|
||||
|
||||
# Set password for remote service.
|
||||
def password=(password)
|
||||
@password = password
|
||||
end
|
||||
|
||||
# Execute a GET request.
|
||||
@@ -166,9 +179,9 @@ module ActiveResource
|
||||
authorization_header.update(default_header).update(headers)
|
||||
end
|
||||
|
||||
# Sets authorization header; authentication information is pulled from credentials provided with site URI.
|
||||
# Sets authorization header
|
||||
def authorization_header
|
||||
(@site.user || @site.password ? { 'Authorization' => 'Basic ' + ["#{@site.user}:#{ @site.password}"].pack('m').delete("\r\n") } : {})
|
||||
(@user || @password ? { 'Authorization' => 'Basic ' + ["#{@user}:#{ @password}"].pack('m').delete("\r\n") } : {})
|
||||
end
|
||||
|
||||
def logger #:nodoc:
|
||||
|
||||
@@ -45,6 +45,38 @@ class AuthorizationTest < Test::Unit::TestCase
|
||||
assert_equal ["", "test123"], 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)
|
||||
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"], 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)
|
||||
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)
|
||||
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_get
|
||||
david = @authenticated_conn.get("/people/2.xml")
|
||||
assert_equal "David", david["name"]
|
||||
|
||||
@@ -32,6 +32,9 @@ class CustomMethodsTest < Test::Unit::TestCase
|
||||
mock.put "/people/1/addresses/sort.xml?by=name", {}, nil, 204
|
||||
mock.post "/people/1/addresses/new/link.xml", {}, { :street => '12345 Street' }.to_xml(:root => 'address'), 201, 'Location' => '/people/1/addresses/2.xml'
|
||||
end
|
||||
|
||||
Person.user = nil
|
||||
Person.password = nil
|
||||
end
|
||||
|
||||
def teardown
|
||||
|
||||
@@ -42,6 +42,9 @@ class BaseTest < Test::Unit::TestCase
|
||||
mock.head "/people/1/addresses/2.xml", {}, nil, 404
|
||||
mock.head "/people/2/addresses/1.xml", {}, nil, 404
|
||||
end
|
||||
|
||||
Person.user = nil
|
||||
Person.password = nil
|
||||
end
|
||||
|
||||
|
||||
@@ -68,6 +71,38 @@ class BaseTest < Test::Unit::TestCase
|
||||
assert_nil actor.site
|
||||
end
|
||||
|
||||
def test_should_accept_setting_user
|
||||
Forum.user = 'david'
|
||||
assert_equal('david', Forum.user)
|
||||
assert_equal('david', Forum.connection.user)
|
||||
end
|
||||
|
||||
def test_should_accept_setting_password
|
||||
Forum.password = 'test123'
|
||||
assert_equal('test123', Forum.password)
|
||||
assert_equal('test123', Forum.connection.password)
|
||||
end
|
||||
|
||||
def test_user_variable_can_be_reset
|
||||
actor = Class.new(ActiveResource::Base)
|
||||
actor.site = 'http://cinema'
|
||||
assert_nil actor.user
|
||||
actor.user = 'username'
|
||||
actor.user = nil
|
||||
assert_nil actor.user
|
||||
assert_nil actor.connection.user
|
||||
end
|
||||
|
||||
def test_password_variable_can_be_reset
|
||||
actor = Class.new(ActiveResource::Base)
|
||||
actor.site = 'http://cinema'
|
||||
assert_nil actor.password
|
||||
actor.password = 'username'
|
||||
actor.password = nil
|
||||
assert_nil actor.password
|
||||
assert_nil actor.connection.password
|
||||
end
|
||||
|
||||
def test_site_reader_uses_superclass_site_until_written
|
||||
# Superclass is Object so returns nil.
|
||||
assert_nil ActiveResource::Base.site
|
||||
@@ -103,12 +138,88 @@ class BaseTest < Test::Unit::TestCase
|
||||
apple = Class.new(fruit)
|
||||
|
||||
fruit.site = 'http://market'
|
||||
assert_equal fruit.site, apple.site, 'subclass did not adopt changes to parent class'
|
||||
assert_equal fruit.site, apple.site, 'subclass did not adopt changes from parent class'
|
||||
|
||||
fruit.site = 'http://supermarket'
|
||||
assert_equal fruit.site, apple.site, 'subclass did not adopt changes to parent class'
|
||||
assert_equal fruit.site, apple.site, 'subclass did not adopt changes from parent class'
|
||||
end
|
||||
|
||||
def test_user_reader_uses_superclass_user_until_written
|
||||
# Superclass is Object so returns nil.
|
||||
assert_nil ActiveResource::Base.user
|
||||
assert_nil Class.new(ActiveResource::Base).user
|
||||
Person.user = 'anonymous'
|
||||
|
||||
# Subclass uses superclass user.
|
||||
actor = Class.new(Person)
|
||||
assert_equal Person.user, actor.user
|
||||
|
||||
# Subclass returns frozen superclass copy.
|
||||
assert !Person.user.frozen?
|
||||
assert actor.user.frozen?
|
||||
|
||||
# Changing subclass user doesn't change superclass user.
|
||||
actor.user = 'david'
|
||||
assert_not_equal Person.user, actor.user
|
||||
|
||||
# Changing superclass user doesn't overwrite subclass user.
|
||||
Person.user = 'john'
|
||||
assert_not_equal Person.user, actor.user
|
||||
|
||||
# Changing superclass user after subclassing changes subclass user.
|
||||
jester = Class.new(actor)
|
||||
actor.user = 'john.doe'
|
||||
assert_equal actor.user, jester.user
|
||||
|
||||
# Subclasses are always equal to superclass user when not overridden
|
||||
fruit = Class.new(ActiveResource::Base)
|
||||
apple = Class.new(fruit)
|
||||
|
||||
fruit.user = 'manager'
|
||||
assert_equal fruit.user, apple.user, 'subclass did not adopt changes from parent class'
|
||||
|
||||
fruit.user = 'client'
|
||||
assert_equal fruit.user, apple.user, 'subclass did not adopt changes from parent class'
|
||||
end
|
||||
|
||||
def test_password_reader_uses_superclass_password_until_written
|
||||
# Superclass is Object so returns nil.
|
||||
assert_nil ActiveResource::Base.password
|
||||
assert_nil Class.new(ActiveResource::Base).password
|
||||
Person.password = 'my-password'
|
||||
|
||||
# Subclass uses superclass password.
|
||||
actor = Class.new(Person)
|
||||
assert_equal Person.password, actor.password
|
||||
|
||||
# Subclass returns frozen superclass copy.
|
||||
assert !Person.password.frozen?
|
||||
assert actor.password.frozen?
|
||||
|
||||
# Changing subclass password doesn't change superclass password.
|
||||
actor.password = 'secret'
|
||||
assert_not_equal Person.password, actor.password
|
||||
|
||||
# Changing superclass password doesn't overwrite subclass password.
|
||||
Person.password = 'super-secret'
|
||||
assert_not_equal Person.password, actor.password
|
||||
|
||||
# Changing superclass password after subclassing changes subclass password.
|
||||
jester = Class.new(actor)
|
||||
actor.password = 'even-more-secret'
|
||||
assert_equal actor.password, jester.password
|
||||
|
||||
# Subclasses are always equal to superclass password when not overridden
|
||||
fruit = Class.new(ActiveResource::Base)
|
||||
apple = Class.new(fruit)
|
||||
|
||||
fruit.password = 'mega-secret'
|
||||
assert_equal fruit.password, apple.password, 'subclass did not adopt changes from parent class'
|
||||
|
||||
fruit.password = 'ok-password'
|
||||
assert_equal fruit.password, apple.password, 'subclass did not adopt changes from parent class'
|
||||
end
|
||||
|
||||
def test_updating_baseclass_site_object_wipes_descendent_cached_connection_objects
|
||||
# Subclasses are always equal to superclass site when not overridden
|
||||
fruit = Class.new(ActiveResource::Base)
|
||||
@@ -116,9 +227,44 @@ class BaseTest < Test::Unit::TestCase
|
||||
|
||||
fruit.site = 'http://market'
|
||||
assert_equal fruit.connection.site, apple.connection.site
|
||||
first_connection = apple.connection.object_id
|
||||
|
||||
fruit.site = 'http://supermarket'
|
||||
assert_equal fruit.connection.site, apple.connection.site
|
||||
assert_equal fruit.connection.site, apple.connection.site
|
||||
second_connection = apple.connection.object_id
|
||||
assert_not_equal(first_connection, second_connection, 'Connection should be re-created')
|
||||
end
|
||||
|
||||
def test_updating_baseclass_user_wipes_descendent_cached_connection_objects
|
||||
# Subclasses are always equal to superclass user when not overridden
|
||||
fruit = Class.new(ActiveResource::Base)
|
||||
apple = Class.new(fruit)
|
||||
fruit.site = 'http://market'
|
||||
|
||||
fruit.user = 'david'
|
||||
assert_equal fruit.connection.user, apple.connection.user
|
||||
first_connection = apple.connection.object_id
|
||||
|
||||
fruit.user = 'john'
|
||||
assert_equal fruit.connection.user, apple.connection.user
|
||||
second_connection = apple.connection.object_id
|
||||
assert_not_equal(first_connection, second_connection, 'Connection should be re-created')
|
||||
end
|
||||
|
||||
def test_updating_baseclass_password_wipes_descendent_cached_connection_objects
|
||||
# Subclasses are always equal to superclass password when not overridden
|
||||
fruit = Class.new(ActiveResource::Base)
|
||||
apple = Class.new(fruit)
|
||||
fruit.site = 'http://market'
|
||||
|
||||
fruit.password = 'secret'
|
||||
assert_equal fruit.connection.password, apple.connection.password
|
||||
first_connection = apple.connection.object_id
|
||||
|
||||
fruit.password = 'supersecret'
|
||||
assert_equal fruit.connection.password, apple.connection.password
|
||||
second_connection = apple.connection.object_id
|
||||
assert_not_equal(first_connection, second_connection, 'Connection should be re-created')
|
||||
end
|
||||
|
||||
def test_collection_name
|
||||
|
||||
Reference in New Issue
Block a user