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

Ticket #11112: active_resouce_accepts_user_and_password.2.diff

File active_resouce_accepts_user_and_password.2.diff, 15.6 kB (added by ernesto.jimenez, 7 months ago)

Improved tests and added explanation about why superclass_delegating_reader is not suitable for our delegated attributes

  • activeresource/test/authorization_test.rb

    old new  
    4545    assert_equal ["", "test123"], ActiveSupport::Base64.decode64(authorization[1]).split(":")[0..1] 
    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") 
    5082    assert_equal "David", david["name"] 
  • activeresource/test/base/custom_methods_test.rb

    old new  
    3232      mock.put    "/people/1/addresses/sort.xml?by=name", {}, nil, 204 
    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 
    3740  def teardown 
  • activeresource/test/base_test.rb

    old new  
    4242      mock.head   "/people/1/addresses/2.xml", {}, nil, 404 
    4343      mock.head   "/people/2/addresses/1.xml", {}, nil, 404 
    4444    end 
     45 
     46    Person.user = nil 
     47    Person.password = nil 
    4548  end 
    4649 
    4750 
     
    6871    assert_nil actor.site     
    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. 
    73108    assert_nil ActiveResource::Base.site 
     
    103138    apple = Class.new(fruit) 
    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'     
     144    assert_equal fruit.site, apple.site, 'subclass did not adopt changes from parent class'     
    110145  end 
    111146   
     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     
    114225    fruit = Class.new(ActiveResource::Base) 
     
    116227     
    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') 
    122236  end 
    123237 
     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') 
     268  end 
     269 
    124270  def test_collection_name 
    125271    assert_equal "people", Person.collection_name 
    126272  end 
  • activeresource/lib/active_resource/connection.rb

    old new  
    5555  # This class is used by ActiveResource::Base to interface with REST 
    5656  # services. 
    5757  class Connection 
    58     attr_reader :site 
     58    attr_reader :site, :user, :password 
    5959    attr_accessor :format 
    6060 
    6161    class << self 
     
    6868    # attribute to the URI for the remote resource service. 
    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 
    7374    end 
     
    7576    # Set URI for remote service. 
    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 
    7881    end 
    7982 
     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 
     91    end 
     92 
    8093    # Execute a GET request. 
    8194    # Used to get (find) resources. 
    8295    def get(path, headers = {}) 
     
    166179        authorization_header.update(default_header).update(headers) 
    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 
    174187      def logger #:nodoc: 
  • activeresource/lib/active_resource/base.rb

    old new  
    8585  # == Authentication 
    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. 
     88  # HTTP authentication.  Authentication can be specified by: 
     89  # * putting the credentials in the +site+ variable. 
    9090  #  
    91   #   class Person < ActiveResource::Base 
    92   #     self.site = "http://ryan:password@api.people.com:3000/" 
    93   #   end 
     91  #    class Person < ActiveResource::Base 
     92  #      self.site = "http://ryan:password@api.people.com:3000/" 
     93  #    end 
    9494  #  
     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: when specified in the +site+ variable, credentials such as email are not accepted 
     107  # because of restrictions from URI library. 
     108  # 
    98109  # == Errors & Validation 
    99110  # 
    100111  # Error handling and validation is handled in much the same manner as you're used to seeing in 
     
    164175      # Gets the URI of the REST resources to map for this class.  The site variable is required  
    165176      # ActiveResource's mapping to work. 
    166177      def site 
     178        # Not using superclass_delegating_reader because don't want subclasses to modify superclass instance 
     179        # 
     180        # With superclass_delegating_reader 
     181        # 
     182        #   Parent.site = 'http://anonymous@test.com' 
     183        #   Subclass.site # => 'http://anonymous@test.com' 
     184        #   Subclass.site.user = 'david' 
     185        #   Parent.site # => 'http://david@test.com' 
     186        # 
     187        # Without superclass_delegating_reader (expected behaviour) 
     188        # 
     189        #   Parent.site = 'http://anonymous@test.com' 
     190        #   Subclass.site # => 'http://anonymous@test.com' 
     191        #   Subclass.site.user = 'david' # => TypeError: can't modify frozen object 
     192        # 
    167193        if defined?(@site) 
    168194          @site 
    169195        elsif superclass != Object && superclass.site 
     
    175201      # The site variable is required ActiveResource's mapping to work. 
    176202      def site=(site) 
    177203        @connection = nil 
    178         @site = site.nil? ? nil : create_site_uri_from(site) 
     204        if site.nil? 
     205          @site = nil 
     206        else 
     207          @site = create_site_uri_from(site) 
     208          @user = @site.user if @site.user 
     209          @password = @site.password if @site.password 
     210        end 
    179211      end 
    180212 
     213      # Gets the user for REST HTTP authentication 
     214      def user 
     215        # Not using superclass_delegating_reader. See +site+ for explanation 
     216        if defined?(@user) 
     217          @user 
     218        elsif superclass != Object && superclass.user 
     219          superclass.user.dup.freeze 
     220        end 
     221      end 
     222 
     223      # Sets the user for REST HTTP authentication 
     224      def user=(user) 
     225        @connection = nil 
     226        @user = user 
     227      end 
     228 
     229      # Gets the password for REST HTTP authentication 
     230      def password 
     231        # Not using superclass_delegating_reader. See +site+ for explanation 
     232        if defined?(@password) 
     233          @password 
     234        elsif superclass != Object && superclass.password 
     235          superclass.password.dup.freeze 
     236        end 
     237      end 
     238 
     239      # Sets the password for REST HTTP authentication 
     240      def password=(password) 
     241        @connection = nil 
     242        @password = password 
     243      end 
     244 
    181245      # Sets the format that attributes are sent and received in from a mime type reference. Example: 
    182246      # 
    183247      #   Person.format = :json 
     
    206270      def connection(refresh = false) 
    207271        if defined?(@connection) || superclass == Object 
    208272          @connection = Connection.new(site, format) if refresh || @connection.nil? 
     273          @connection.user = user if user 
     274          @connection.password = password if password 
    209275          @connection 
    210276        else 
    211277          superclass.connection