From 973315350cb5359c513d0a63ea125b827946e73c Mon Sep 17 00:00:00 2001 From: lucasklaassen Date: Mon, 1 Oct 2018 11:26:57 -0700 Subject: [PATCH 1/4] Add bearer token authorization header to connection with test --- lib/active_resource/connection.rb | 13 ++++++++++--- test/cases/authorization_test.rb | 22 ++++++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) 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 From 4ec6ee9a2282a06cf3030b00d96051b97d1ba30e Mon Sep 17 00:00:00 2001 From: lucasklaassen Date: Mon, 1 Oct 2018 11:32:08 -0700 Subject: [PATCH 2/4] Added documentation --- README.rdoc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.rdoc b/README.rdoc index 26e7f3225e..9bed088a22 100644 --- a/README.rdoc +++ b/README.rdoc @@ -69,6 +69,16 @@ 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 ==== Protocol From 76253b25959d4058595a075ab3a8dd3387a151c1 Mon Sep 17 00:00:00 2001 From: lucasklaassen Date: Mon, 1 Oct 2018 11:38:26 -0700 Subject: [PATCH 3/4] Update documentation syntax --- README.rdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rdoc b/README.rdoc index 9bed088a22..eac010f9d7 100644 --- a/README.rdoc +++ b/README.rdoc @@ -70,7 +70,7 @@ You can also set any specific HTTP header using the same way. As mentioned abov 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. +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 From 15e9aa3c7d93371ed259c8cd0fb879a5b7978fb6 Mon Sep 17 00:00:00 2001 From: lucasklaassen Date: Mon, 1 Oct 2018 11:42:49 -0700 Subject: [PATCH 4/4] More documentation --- README.rdoc | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.rdoc b/README.rdoc index eac010f9d7..686d9a79ed 100644 --- a/README.rdoc +++ b/README.rdoc @@ -80,6 +80,18 @@ Global Authentication to be used across all subclasses of ActiveResource::Base s 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 Active Resource is built on a standard JSON or XML format for requesting and submitting resources over HTTP. It mirrors the RESTful routing