diff --git a/README.rdoc b/README.rdoc index 26e7f3225e..686d9a79ed 100644 --- a/README.rdoc +++ b/README.rdoc @@ -69,6 +69,28 @@ Active Resource supports the token based authentication provided by Rails throug You can also set any specific HTTP header using the same way. As mentioned above, headers are thread-safe, so you can set headers dynamically, even in a multi-threaded environment: ActiveResource::Base.headers['Authorization'] = current_session_api_token + +Global Authentication to be used across all subclasses of ActiveResource::Base should be handled using the ActiveResource::Connection class. + + ActiveResource::Base.connection.auth_type = :bearer + ActiveResource::Base.connection.bearer_token = @bearer_token + + class Person < ActiveResource::Base + self.connection.auth_type = :bearer + self.connection.bearer_token = @bearer_token + end + +ActiveResource supports 2 options for HTTP authentication today. + +1. Basic + + ActiveResource::Connection.new("http://my%40email.com:%31%32%33@localhost") + # username: my@email.com password: 123 + +2. Bearer Token + + ActiveResource::Base.connection.auth_type = :bearer + ActiveResource::Base.connection.bearer_token = @bearer_token ==== Protocol diff --git a/lib/active_resource/connection.rb b/lib/active_resource/connection.rb index a10d530b0f..82cc8cc311 100644 --- a/lib/active_resource/connection.rb +++ b/lib/active_resource/connection.rb @@ -20,7 +20,7 @@ class Connection :head => 'Accept' } - attr_reader :site, :user, :password, :auth_type, :timeout, :open_timeout, :read_timeout, :proxy, :ssl_options + attr_reader :site, :user, :password, :bearer_token, :auth_type, :timeout, :open_timeout, :read_timeout, :proxy, :ssl_options attr_accessor :format, :logger class << self @@ -33,7 +33,7 @@ def requests # attribute to the URI for the remote resource service. def initialize(site, format = ActiveResource::Formats::JsonFormat, logger: nil) raise ArgumentError, 'Missing site URI' unless site - @proxy = @user = @password = nil + @proxy = @user = @password = @bearer_token = nil self.site = site self.format = format self.logger = logger @@ -62,6 +62,11 @@ def password=(password) @password = password end + # Sets the bearer token for remote service. + def bearer_token=(bearer_token) + @bearer_token = bearer_token + end + # Sets the auth type for remote service. def auth_type=(auth_type) @auth_type = legitimize_auth_type(auth_type) @@ -240,6 +245,8 @@ def authorization_header(http_method, uri) else { 'Authorization' => 'Basic ' + ["#{@user}:#{@password}"].pack('m').delete("\r\n") } end + elsif @bearer_token + { 'Authorization' => "Bearer #{@bearer_token}" } else {} end @@ -294,7 +301,7 @@ def http_format_header(http_method) def legitimize_auth_type(auth_type) return :basic if auth_type.nil? auth_type = auth_type.to_sym - auth_type.in?([:basic, :digest]) ? auth_type : :basic + auth_type.in?([:basic, :digest, :bearer]) ? auth_type : :basic end end end diff --git a/test/cases/authorization_test.rb b/test/cases/authorization_test.rb index 62a7c2c5cb..829ba1c87b 100644 --- a/test/cases/authorization_test.rb +++ b/test/cases/authorization_test.rb @@ -9,6 +9,8 @@ def setup @david = { :person => { :id => 2, :name => 'David' } }.to_json @authenticated_conn = ActiveResource::Connection.new("http://david:test123@localhost") @basic_authorization_request_header = { 'Authorization' => 'Basic ZGF2aWQ6dGVzdDEyMw==' } + @jwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' + @bearer_token_authorization_request_header = { 'Authorization' => "Bearer #{@jwt}" } end private @@ -25,6 +27,7 @@ def setup ActiveResource::HttpMock.respond_to do |mock| mock.get "/people/2.json", @basic_authorization_request_header, @david mock.get "/people/1.json", @basic_authorization_request_header, nil, 401, { 'WWW-Authenticate' => 'i_should_be_ignored' } + mock.get "/people/3.json", @bearer_token_authorization_request_header, @david mock.put "/people/2.json", @basic_authorization_request_header, nil, 204 mock.delete "/people/2.json", @basic_authorization_request_header, nil, 200 mock.post "/people/2/addresses.json", @basic_authorization_request_header, nil, 201, 'Location' => '/people/1/addresses/5' @@ -148,6 +151,25 @@ def test_authorization_header_if_credentials_supplied_and_auth_type_is_basic assert_equal ["david", "test123"], ::Base64.decode64(authorization[1]).split(":")[0..1] end + def test_authorization_header_explicitly_setting_jwt_and_auth_type_is_bearer + @conn = ActiveResource::Connection.new("http://localhost") + @conn.auth_type = :bearer + @conn.bearer_token = @jwt + authorization_header = @conn.__send__(:authorization_header, :get, URI.parse('/people/3.json')) + assert_equal @bearer_token_authorization_request_header['Authorization'], authorization_header['Authorization'] + authorization = authorization_header["Authorization"].to_s.split + + assert_equal "Bearer", authorization[0] + assert_equal @jwt, authorization[1] + end + + def test_authorization_header_if_no_jwt_and_auth_type_is_bearer + @conn = ActiveResource::Connection.new("http://localhost") + @conn.auth_type = :bearer + authorization_header = @conn.__send__(:authorization_header, :get, URI.parse('/people/3.json')) + assert_nil authorization_header['Authorization'] + end + def test_client_nonce_is_not_nil assert_not_nil ActiveResource::Connection.new("http://david:test123@localhost").send(:client_nonce) end