Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Changeset 8891

Show
Ignore:
Timestamp:
02/18/08 00:21:18 (1 year ago)
Author:
nzkoz
Message:

Add user and password configuration options to ActiveResource::Base, not all credentials can be specified inline. Closes #11112 [ernesto.jimenez]

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/activeresource/lib/active_resource/base.rb

    r8827 r8891  
    8686  #  
    8787  # Many REST APIs will require authentication, usually in the form of basic 
    88   # HTTP authentication.  Authentication can be specified by putting the credentials 
    89   # in the +site+ variable of the Active Resource class you need to authenticate. 
    90   #  
    91   #   class Person < ActiveResource::Base 
    92   #     self.site = "http://ryan:password@api.people.com:3000/" 
    93   #   end 
    94   #  
     88  # HTTP authentication.  Authentication can be specified by: 
     89  # * putting the credentials in the URL for the +site+ variable. 
     90  #  
     91  #    class Person < ActiveResource::Base 
     92  #      self.site = "http://ryan:password@api.people.com:3000/" 
     93  #    end 
     94  #  
     95  # * defining +user+ and/or +password+ variables 
     96  # 
     97  #    class Person < ActiveResource::Base 
     98  #      self.site = "http://api.people.com:3000/" 
     99  #      self.user = "ryan" 
     100  #      self.password = "password" 
     101  #    end 
     102  # 
    95103  # For obvious security reasons, it is probably best if such services are available  
    96104  # over HTTPS. 
    97105  #  
     106  # Note: Some values cannot be provided in the URL passed to site.  e.g. email addresses  
     107  # as usernames.  In those situations you should use the seperate user and password option. 
    98108  # == Errors & Validation 
    99109  # 
     
    165175      # ActiveResource's mapping to work. 
    166176      def site 
     177        # Not using superclass_delegating_reader because don't want subclasses to modify superclass instance 
     178        # 
     179        # With superclass_delegating_reader 
     180        # 
     181        #   Parent.site = 'http://anonymous@test.com' 
     182        #   Subclass.site # => 'http://anonymous@test.com' 
     183        #   Subclass.site.user = 'david' 
     184        #   Parent.site # => 'http://david@test.com' 
     185        # 
     186        # Without superclass_delegating_reader (expected behaviour) 
     187        # 
     188        #   Parent.site = 'http://anonymous@test.com' 
     189        #   Subclass.site # => 'http://anonymous@test.com' 
     190        #   Subclass.site.user = 'david' # => TypeError: can't modify frozen object 
     191        # 
    167192        if defined?(@site) 
    168193          @site 
     
    176201      def site=(site) 
    177202        @connection = nil 
    178         @site = site.nil? ? nil : create_site_uri_from(site) 
     203        if site.nil? 
     204          @site = nil 
     205        else 
     206          @site = create_site_uri_from(site) 
     207          @user = @site.user if @site.user 
     208          @password = @site.password if @site.password 
     209        end 
     210      end 
     211 
     212      # Gets the user for REST HTTP authentication 
     213      def user 
     214        # Not using superclass_delegating_reader. See +site+ for explanation 
     215        if defined?(@user) 
     216          @user 
     217        elsif superclass != Object && superclass.user 
     218          superclass.user.dup.freeze 
     219        end 
     220      end 
     221 
     222      # Sets the user for REST HTTP authentication 
     223      def user=(user) 
     224        @connection = nil 
     225        @user = user 
     226      end 
     227 
     228      # Gets the password for REST HTTP authentication 
     229      def password 
     230        # Not using superclass_delegating_reader. See +site+ for explanation 
     231        if defined?(@password) 
     232          @password 
     233        elsif superclass != Object && superclass.password 
     234          superclass.password.dup.freeze 
     235        end 
     236      end 
     237 
     238      # Sets the password for REST HTTP authentication 
     239      def password=(password) 
     240        @connection = nil 
     241        @password = password 
    179242      end 
    180243 
     
    207270        if defined?(@connection) || superclass == Object 
    208271          @connection = Connection.new(site, format) if refresh || @connection.nil? 
     272          @connection.user = user if user 
     273          @connection.password = password if password 
    209274          @connection 
    210275        else 
  • trunk/activeresource/lib/active_resource/connection.rb

    r8827 r8891  
    5656  # services. 
    5757  class Connection 
    58     attr_reader :site 
     58    attr_reader :site, :user, :password 
    5959    attr_accessor :format 
    6060 
     
    6969    def initialize(site, format = ActiveResource::Formats[:xml]) 
    7070      raise ArgumentError, 'Missing site URI' unless site 
     71      @user = @password = nil 
    7172      self.site = site 
    7273      self.format = format 
     
    7677    def site=(site) 
    7778      @site = site.is_a?(URI) ? site : URI.parse(site) 
     79      @user = @site.user if @site.user 
     80      @password = @site.password if @site.password 
     81    end 
     82 
     83    # Set user for remote service. 
     84    def user=(user) 
     85      @user = user 
     86    end 
     87 
     88    # Set password for remote service. 
     89    def password=(password) 
     90      @password = password 
    7891    end 
    7992 
     
    167180      end 
    168181       
    169       # Sets authorization header; authentication information is pulled from credentials provided with site URI. 
     182      # Sets authorization header 
    170183      def authorization_header 
    171         (@site.user || @site.password ? { 'Authorization' => 'Basic ' + ["#{@site.user}:#{ @site.password}"].pack('m').delete("\r\n") } : {}) 
     184        (@user || @password ? { 'Authorization' => 'Basic ' + ["#{@user}:#{ @password}"].pack('m').delete("\r\n") } : {}) 
    172185      end 
    173186 
  • trunk/activeresource/test/authorization_test.rb

    r8566 r8891  
    4646  end 
    4747   
     48  def test_authorization_header_explicitly_setting_username_and_password 
     49    @authenticated_conn = ActiveResource::Connection.new("http://@localhost") 
     50    @authenticated_conn.user = 'david' 
     51    @authenticated_conn.password = 'test123' 
     52    authorization_header = @authenticated_conn.send!(:authorization_header) 
     53    assert_equal @authorization_request_header['Authorization'], authorization_header['Authorization'] 
     54    authorization = authorization_header["Authorization"].to_s.split 
     55 
     56    assert_equal "Basic", authorization[0] 
     57    assert_equal ["david", "test123"], ActiveSupport::Base64.decode64(authorization[1]).split(":")[0..1] 
     58  end 
     59 
     60  def test_authorization_header_explicitly_setting_username_but_no_password 
     61    @conn = ActiveResource::Connection.new("http://@localhost") 
     62    @conn.user = "david" 
     63    authorization_header = @conn.send!(:authorization_header) 
     64    authorization = authorization_header["Authorization"].to_s.split 
     65 
     66    assert_equal "Basic", authorization[0] 
     67    assert_equal ["david"], ActiveSupport::Base64.decode64(authorization[1]).split(":")[0..1] 
     68  end 
     69 
     70  def test_authorization_header_explicitly_setting_password_but_no_username 
     71    @conn = ActiveResource::Connection.new("http://@localhost") 
     72    @conn.password = "test123" 
     73    authorization_header = @conn.send!(:authorization_header) 
     74    authorization = authorization_header["Authorization"].to_s.split 
     75 
     76    assert_equal "Basic", authorization[0] 
     77    assert_equal ["", "test123"], ActiveSupport::Base64.decode64(authorization[1]).split(":")[0..1] 
     78  end 
     79 
    4880  def test_get 
    4981    david = @authenticated_conn.get("/people/2.xml") 
  • trunk/activeresource/test/base_test.rb

    r8827 r8891  
    4343      mock.head   "/people/2/addresses/1.xml", {}, nil, 404 
    4444    end 
     45 
     46    Person.user = nil 
     47    Person.password = nil 
    4548  end 
    4649 
     
    6972  end 
    7073 
     74  def test_should_accept_setting_user 
     75    Forum.user = 'david' 
     76    assert_equal('david', Forum.user) 
     77    assert_equal('david', Forum.connection.user) 
     78  end 
     79 
     80  def test_should_accept_setting_password 
     81    Forum.password = 'test123' 
     82    assert_equal('test123', Forum.password) 
     83    assert_equal('test123', Forum.connection.password) 
     84  end 
     85 
     86  def test_user_variable_can_be_reset 
     87    actor = Class.new(ActiveResource::Base) 
     88    actor.site = 'http://cinema' 
     89    assert_nil actor.user 
     90    actor.user = 'username' 
     91    actor.user = nil 
     92    assert_nil actor.user 
     93    assert_nil actor.connection.user 
     94  end 
     95 
     96  def test_password_variable_can_be_reset 
     97    actor = Class.new(ActiveResource::Base) 
     98    actor.site = 'http://cinema' 
     99    assert_nil actor.password 
     100    actor.password = 'username' 
     101    actor.password = nil 
     102    assert_nil actor.password 
     103    assert_nil actor.connection.password 
     104  end 
     105 
    71106  def test_site_reader_uses_superclass_site_until_written 
    72107    # Superclass is Object so returns nil. 
     
    104139     
    105140    fruit.site = 'http://market' 
    106     assert_equal fruit.site, apple.site, 'subclass did not adopt changes to parent class' 
     141    assert_equal fruit.site, apple.site, 'subclass did not adopt changes from parent class' 
    107142     
    108143    fruit.site = 'http://supermarket' 
    109     assert_equal fruit.site, apple.site, 'subclass did not adopt changes to parent class'     
    110   end 
    111    
     144    assert_equal fruit.site, apple.site, 'subclass did not adopt changes from parent class' 
     145  end 
     146   
     147  def test_user_reader_uses_superclass_user_until_written 
     148    # Superclass is Object so returns nil. 
     149    assert_nil ActiveResource::Base.user 
     150    assert_nil Class.new(ActiveResource::Base).user 
     151    Person.user = 'anonymous' 
     152 
     153    # Subclass uses superclass user. 
     154    actor = Class.new(Person) 
     155    assert_equal Person.user, actor.user 
     156 
     157    # Subclass returns frozen superclass copy. 
     158    assert !Person.user.frozen? 
     159    assert actor.user.frozen? 
     160 
     161    # Changing subclass user doesn't change superclass user. 
     162    actor.user = 'david' 
     163    assert_not_equal Person.user, actor.user 
     164 
     165    # Changing superclass user doesn't overwrite subclass user. 
     166    Person.user = 'john' 
     167    assert_not_equal Person.user, actor.user 
     168 
     169    # Changing superclass user after subclassing changes subclass user. 
     170    jester = Class.new(actor) 
     171    actor.user = 'john.doe' 
     172    assert_equal actor.user, jester.user 
     173 
     174    # Subclasses are always equal to superclass user when not overridden 
     175    fruit = Class.new(ActiveResource::Base) 
     176    apple = Class.new(fruit) 
     177 
     178    fruit.user = 'manager' 
     179    assert_equal fruit.user, apple.user, 'subclass did not adopt changes from parent class' 
     180 
     181    fruit.user = 'client' 
     182    assert_equal fruit.user, apple.user, 'subclass did not adopt changes from parent class' 
     183  end 
     184 
     185  def test_password_reader_uses_superclass_password_until_written 
     186    # Superclass is Object so returns nil. 
     187    assert_nil ActiveResource::Base.password 
     188    assert_nil Class.new(ActiveResource::Base).password 
     189    Person.password = 'my-password' 
     190 
     191    # Subclass uses superclass password. 
     192    actor = Class.new(Person) 
     193    assert_equal Person.password, actor.password 
     194 
     195    # Subclass returns frozen superclass copy. 
     196    assert !Person.password.frozen? 
     197    assert actor.password.frozen? 
     198 
     199    # Changing subclass password doesn't change superclass password. 
     200    actor.password = 'secret' 
     201    assert_not_equal Person.password, actor.password 
     202 
     203    # Changing superclass password doesn't overwrite subclass password. 
     204    Person.password = 'super-secret' 
     205    assert_not_equal Person.password, actor.password 
     206 
     207    # Changing superclass password after subclassing changes subclass password. 
     208    jester = Class.new(actor) 
     209    actor.password = 'even-more-secret' 
     210    assert_equal actor.password, jester.password 
     211 
     212    # Subclasses are always equal to superclass password when not overridden 
     213    fruit = Class.new(ActiveResource::Base) 
     214    apple = Class.new(fruit) 
     215 
     216    fruit.password = 'mega-secret' 
     217    assert_equal fruit.password, apple.password, 'subclass did not adopt changes from parent class' 
     218 
     219    fruit.password = 'ok-password' 
     220    assert_equal fruit.password, apple.password, 'subclass did not adopt changes from parent class' 
     221  end 
     222 
    112223  def test_updating_baseclass_site_object_wipes_descendent_cached_connection_objects 
    113224    # Subclasses are always equal to superclass site when not overridden     
     
    117228    fruit.site = 'http://market' 
    118229    assert_equal fruit.connection.site, apple.connection.site 
     230    first_connection = apple.connection.object_id 
    119231     
    120232    fruit.site = 'http://supermarket' 
    121     assert_equal fruit.connection.site, apple.connection.site     
     233    assert_equal fruit.connection.site, apple.connection.site 
     234    second_connection = apple.connection.object_id 
     235    assert_not_equal(first_connection, second_connection, 'Connection should be re-created') 
     236  end 
     237 
     238  def test_updating_baseclass_user_wipes_descendent_cached_connection_objects 
     239    # Subclasses are always equal to superclass user when not overridden 
     240    fruit = Class.new(ActiveResource::Base) 
     241    apple = Class.new(fruit) 
     242    fruit.site = 'http://market' 
     243 
     244    fruit.user = 'david' 
     245    assert_equal fruit.connection.user, apple.connection.user 
     246    first_connection = apple.connection.object_id 
     247 
     248    fruit.user = 'john' 
     249    assert_equal fruit.connection.user, apple.connection.user 
     250    second_connection = apple.connection.object_id 
     251    assert_not_equal(first_connection, second_connection, 'Connection should be re-created') 
     252  end 
     253 
     254  def test_updating_baseclass_password_wipes_descendent_cached_connection_objects 
     255    # Subclasses are always equal to superclass password when not overridden 
     256    fruit = Class.new(ActiveResource::Base) 
     257    apple = Class.new(fruit) 
     258    fruit.site = 'http://market' 
     259 
     260    fruit.password = 'secret' 
     261    assert_equal fruit.connection.password, apple.connection.password 
     262    first_connection = apple.connection.object_id 
     263 
     264    fruit.password = 'supersecret' 
     265    assert_equal fruit.connection.password, apple.connection.password 
     266    second_connection = apple.connection.object_id 
     267    assert_not_equal(first_connection, second_connection, 'Connection should be re-created') 
    122268  end 
    123269 
  • trunk/activeresource/test/base/custom_methods_test.rb

    r8566 r8891  
    3333      mock.post   "/people/1/addresses/new/link.xml", {}, { :street => '12345 Street' }.to_xml(:root => 'address'), 201, 'Location' => '/people/1/addresses/2.xml' 
    3434    end 
     35 
     36    Person.user = nil 
     37    Person.password = nil 
    3538  end   
    3639