From edcce115eb4fb964da39da4e15df27959788314d Mon Sep 17 00:00:00 2001 From: Rob Dingwell Date: Thu, 31 May 2018 19:18:51 -0400 Subject: [PATCH 1/5] adding ability to set authentications via access_token and or refresh_token --- lib/fhir_client/client.rb | 37 +++++++++++++++++ test/unit/client_acess_token_test.rb | 59 ++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 test/unit/client_acess_token_test.rb diff --git a/lib/fhir_client/client.rb b/lib/fhir_client/client.rb index 57783e87..55338ec1 100644 --- a/lib/fhir_client/client.rb +++ b/lib/fhir_client/client.rb @@ -144,6 +144,43 @@ def set_oauth2_auth(client, secret, authorize_path, token_path) @client = client.client_credentials.get_token end + # Enable OAuth authentication through the use of access and refresh tokens + # client -- client id + # secret -- client secret + # options -- hash of options + # access_token: Your current access_token, must have atleast an access_token or a refresh_token + # refresh_token: a token that is used to referesh the access token when it + # expires, or if one does not exist but can be obtained from + # the refresh toekn + # authorize_path -- absolute path of authorization endpoint + # token_path -- absolute path of token endpoint + # Addtional options can be passed in as supported byt the OAuth2::AcessToken class + def set_auth_from_token(client, secret, options) + FHIR.logger.info 'Configuring the client to use OAuth2 access token authentication.' + + raise "Must provide an access_token or a refresh_token" if options[:access_token].nil? && options[:refresh_token].nil? + token = options.delete(:access_token) + auto_configure = options.delete(:auto_configure) + client_options = { + authorize_url: options.delete(:authorize_path), + token_url: options.delete(:token_path) + } + client_options = get_oauth2_metadata_from_conformance(false) if auto_configure + # dont set befor call to capability statement, it will fail + @use_oauth2_auth = true + @use_basic_auth = false + @security_headers = {} + + client = OAuth2::Client.new(client, secret, client_options) + + access_token = OAuth2::AccessToken.new(client, token, options) + @client = access_token + if access_token.refresh_token && access_token.expired? + @client = access_token.refresh! + end + @client + end + # Get the OAuth2 server and endpoints from the capability statement # (the server should not require OAuth2 or other special security to access # the capability statement). diff --git a/test/unit/client_acess_token_test.rb b/test/unit/client_acess_token_test.rb new file mode 100644 index 00000000..4f1ab3cb --- /dev/null +++ b/test/unit/client_acess_token_test.rb @@ -0,0 +1,59 @@ +require_relative '../test_helper' + +class ClientAccessTokenTest < Test::Unit::TestCase + + def client + @client ||= FHIR::Client.new("basic-test") + end + + def test_can_configure_client_with_access_token_authentication + + stub_request(:get, /Patient\/example/). + with(headers:{"Authorization"=>"Bearer some token"}). + to_return(body: "{}", headers: {"Content-Type" => "application/json"} ) + client.set_auth_from_token("id","secret", {access_token: "some token", + auto_configure: false}); + + + assert_equal "{}", client.read(FHIR::Patient, "example").response[:body] + + end + + def test_can_configure_client_with_refresh_token_for_authentication + new_token = "{\"access_token\": \"New access Token\"}" + stub_request(:post, /auth\/token/).to_return(body: new_token, headers: {"Content-Type" => "application/json"} ) + stub_request(:get, /Patient\/example/). + with(headers:{"Authorization"=>"Bearer New access Token"}). + to_return(body: "{}", headers: {"Content-Type" => "application/json"} ) + token = client.set_auth_from_token("id","secret", {access_token: "some token", + auto_configure: false, + expires_in: -1, + refresh_token: "My Refresh Token", + token_path: "/auth/token"}); + assert_equal "New access Token", token.token + assert_equal "{}", client.read(FHIR::Patient, "example").response[:body] + end + + def test_must_supply_either_an_access_token_or_a_refresh_token + begin + client.set_auth_from_token("id","secret", {}) + assert false, "Should not be able to configure client without either an access or refresh token" + rescue + assert_equal "Must provide an access_token or a refresh_token", $!.message + end + end + + def test_can_configure_access_token_with_auto_configure + root = File.expand_path '..', File.dirname(File.absolute_path(__FILE__)) + capabilitystatement = File.read(File.join(root, 'fixtures', 'oauth_capability_statement.json')) + stub_request(:get, /metadata/).to_return(body: capabilitystatement) + token = client.set_auth_from_token("id","secret", {access_token: "some token", + auto_configure: true}); + + assert_equal "https://authorize.smarthealthit.org/authorize", token.client.options[:authorize_url] + assert_equal "https://authorize.smarthealthit.org/token", token.client.options[:token_url] + end + + + +end From d8763ce021f7f6dd965c050ad6665a1194d81d84 Mon Sep 17 00:00:00 2001 From: Rob Dingwell Date: Fri, 1 Jun 2018 13:02:05 -0400 Subject: [PATCH 2/5] adding some comments --- test/unit/client_acess_token_test.rb | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/test/unit/client_acess_token_test.rb b/test/unit/client_acess_token_test.rb index 4f1ab3cb..3b571258 100644 --- a/test/unit/client_acess_token_test.rb +++ b/test/unit/client_acess_token_test.rb @@ -7,7 +7,9 @@ def client end def test_can_configure_client_with_access_token_authentication - + # stub out a request for a resource, once configured with the access token + # the auth methed would be a bearer token with the provided access token + # the stub only works on calls that have the correct authentication header stub_request(:get, /Patient\/example/). with(headers:{"Authorization"=>"Bearer some token"}). to_return(body: "{}", headers: {"Content-Type" => "application/json"} ) @@ -21,7 +23,15 @@ def test_can_configure_client_with_access_token_authentication def test_can_configure_client_with_refresh_token_for_authentication new_token = "{\"access_token\": \"New access Token\"}" + # stub request for requesting a new access token from an auth server token endpoint + # this will return the expected new access token stub_request(:post, /auth\/token/).to_return(body: new_token, headers: {"Content-Type" => "application/json"} ) + + # stub out a request for a resource, once configured with the access token + # the auth methed would be a bearer token with the access token retrieved + # from the previous call to get a new access token + # the stub only works on calls that have the correct authentication header + stub_request(:get, /Patient\/example/). with(headers:{"Authorization"=>"Bearer New access Token"}). to_return(body: "{}", headers: {"Content-Type" => "application/json"} ) @@ -30,10 +40,15 @@ def test_can_configure_client_with_refresh_token_for_authentication expires_in: -1, refresh_token: "My Refresh Token", token_path: "/auth/token"}); + assert_equal "New access Token", token.token + # make a call to test the stubbed out method with auth type assert_equal "{}", client.read(FHIR::Patient, "example").response[:body] end + # Need to provider at a minimum an access_token or a refresh_token, it can be + # both but need atleast one of them, otherwise there is nothing to configure + # against def test_must_supply_either_an_access_token_or_a_refresh_token begin client.set_auth_from_token("id","secret", {}) @@ -43,6 +58,8 @@ def test_must_supply_either_an_access_token_or_a_refresh_token end end + # Make sure that we can auto configure the auth and token endpoints from a + # capability statement. def test_can_configure_access_token_with_auto_configure root = File.expand_path '..', File.dirname(File.absolute_path(__FILE__)) capabilitystatement = File.read(File.join(root, 'fixtures', 'oauth_capability_statement.json')) From f141d910901de41db498aae060f759d905bd9a0a Mon Sep 17 00:00:00 2001 From: Rob Dingwell Date: Fri, 1 Jun 2018 16:09:42 -0400 Subject: [PATCH 3/5] updating comment --- lib/fhir_client/client.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/fhir_client/client.rb b/lib/fhir_client/client.rb index 55338ec1..52c491d7 100644 --- a/lib/fhir_client/client.rb +++ b/lib/fhir_client/client.rb @@ -148,13 +148,13 @@ def set_oauth2_auth(client, secret, authorize_path, token_path) # client -- client id # secret -- client secret # options -- hash of options - # access_token: Your current access_token, must have atleast an access_token or a refresh_token - # refresh_token: a token that is used to referesh the access token when it - # expires, or if one does not exist but can be obtained from - # the refresh toekn + # access_token: Current access_token + # refresh_token: a token that is used to obtaine an new access_token when it + # expires or does not currently exist # authorize_path -- absolute path of authorization endpoint # token_path -- absolute path of token endpoint - # Addtional options can be passed in as supported byt the OAuth2::AcessToken class + # auto_configure -- whether or not to configure the oauth endpoints from the servers capability statement + # Addtional options can be passed in as supported by the OAuth2::AcessToken class def set_auth_from_token(client, secret, options) FHIR.logger.info 'Configuring the client to use OAuth2 access token authentication.' From a0b39d112be73c064447481cbc3d698d216d54fa Mon Sep 17 00:00:00 2001 From: Rob Dingwell Date: Fri, 1 Jun 2018 16:12:44 -0400 Subject: [PATCH 4/5] fixing spelling issues in comments --- lib/fhir_client/client.rb | 2 +- test/unit/client_acess_token_test.rb | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/fhir_client/client.rb b/lib/fhir_client/client.rb index 52c491d7..ac7d6309 100644 --- a/lib/fhir_client/client.rb +++ b/lib/fhir_client/client.rb @@ -149,7 +149,7 @@ def set_oauth2_auth(client, secret, authorize_path, token_path) # secret -- client secret # options -- hash of options # access_token: Current access_token - # refresh_token: a token that is used to obtaine an new access_token when it + # refresh_token: a token that is used to obtain an new access_token when it # expires or does not currently exist # authorize_path -- absolute path of authorization endpoint # token_path -- absolute path of token endpoint diff --git a/test/unit/client_acess_token_test.rb b/test/unit/client_acess_token_test.rb index 3b571258..58471ca4 100644 --- a/test/unit/client_acess_token_test.rb +++ b/test/unit/client_acess_token_test.rb @@ -8,7 +8,7 @@ def client def test_can_configure_client_with_access_token_authentication # stub out a request for a resource, once configured with the access token - # the auth methed would be a bearer token with the provided access token + # the auth methed should be a bearer token with the provided access token # the stub only works on calls that have the correct authentication header stub_request(:get, /Patient\/example/). with(headers:{"Authorization"=>"Bearer some token"}). @@ -28,7 +28,7 @@ def test_can_configure_client_with_refresh_token_for_authentication stub_request(:post, /auth\/token/).to_return(body: new_token, headers: {"Content-Type" => "application/json"} ) # stub out a request for a resource, once configured with the access token - # the auth methed would be a bearer token with the access token retrieved + # the auth method should be a bearer token with the access token retrieved # from the previous call to get a new access token # the stub only works on calls that have the correct authentication header @@ -46,8 +46,8 @@ def test_can_configure_client_with_refresh_token_for_authentication assert_equal "{}", client.read(FHIR::Patient, "example").response[:body] end - # Need to provider at a minimum an access_token or a refresh_token, it can be - # both but need atleast one of them, otherwise there is nothing to configure + # Need to provide at a minimum an access_token or a refresh_token, it can be + # both but need at least one of them, otherwise there is nothing to configure # against def test_must_supply_either_an_access_token_or_a_refresh_token begin @@ -59,7 +59,7 @@ def test_must_supply_either_an_access_token_or_a_refresh_token end # Make sure that we can auto configure the auth and token endpoints from a - # capability statement. + # capability statement. def test_can_configure_access_token_with_auto_configure root = File.expand_path '..', File.dirname(File.absolute_path(__FILE__)) capabilitystatement = File.read(File.join(root, 'fixtures', 'oauth_capability_statement.json')) From 3231ecfd02b23f7b9ed2537c7164777680a58ea2 Mon Sep 17 00:00:00 2001 From: Rob Dingwell Date: Tue, 5 Jun 2018 12:03:39 -0400 Subject: [PATCH 5/5] fixing spelling errors, per the usual --- lib/fhir_client/client.rb | 2 +- .../{client_acess_token_test.rb => client_access_token_test.rb} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename test/unit/{client_acess_token_test.rb => client_access_token_test.rb} (100%) diff --git a/lib/fhir_client/client.rb b/lib/fhir_client/client.rb index ac7d6309..a68cbd38 100644 --- a/lib/fhir_client/client.rb +++ b/lib/fhir_client/client.rb @@ -154,7 +154,7 @@ def set_oauth2_auth(client, secret, authorize_path, token_path) # authorize_path -- absolute path of authorization endpoint # token_path -- absolute path of token endpoint # auto_configure -- whether or not to configure the oauth endpoints from the servers capability statement - # Addtional options can be passed in as supported by the OAuth2::AcessToken class + # Addtional options can be passed in as supported by the OAuth2::AccessToken class def set_auth_from_token(client, secret, options) FHIR.logger.info 'Configuring the client to use OAuth2 access token authentication.' diff --git a/test/unit/client_acess_token_test.rb b/test/unit/client_access_token_test.rb similarity index 100% rename from test/unit/client_acess_token_test.rb rename to test/unit/client_access_token_test.rb