diff --git a/dev_suites/dev_auth_info/auth_info_suite.rb b/dev_suites/dev_auth_info/auth_info_suite.rb index 6707492f4..4dc3ca615 100644 --- a/dev_suites/dev_auth_info/auth_info_suite.rb +++ b/dev_suites/dev_auth_info/auth_info_suite.rb @@ -121,6 +121,8 @@ class Suite < Inferno::TestSuite id :auth_info title 'AuthInfo Suite' + URL = 'https://inferno.healthit.gov/reference-server/r4'.freeze + group do id :auth_info_demo title 'Auth Input Demo' @@ -252,11 +254,18 @@ class Suite < Inferno::TestSuite ] }, default: AuthInfoConstants.public_access_default.to_json + + fhir_client do + url URL + auth_info :public_access_auth_info + end run do + auth_info = fhir_client.auth_info AuthInfoConstants.public_access_default.each do |key, original_value| - received_value = public_access_auth_info.send(key) + received_value = auth_info.send(key) assert received_value == original_value, - "Expected `#{key}` to equal `#{original_value}`, but received `#{received_value}`" + "Expected fhir_client auth info `#{key}` to equal `#{original_value}`, " \ + "but received `#{received_value}`" end end end @@ -277,11 +286,18 @@ class Suite < Inferno::TestSuite ] }, default: AuthInfoConstants.symmetric_confidential_access_default.to_json + + fhir_client do + url URL + auth_info :symmetric_access_auth_info + end run do + auth_info = fhir_client.auth_info AuthInfoConstants.symmetric_confidential_access_default.each do |key, original_value| - received_value = symmetric_access_auth_info.send(key) + received_value = auth_info.send(key) assert received_value == original_value, - "Expected `#{key}` to equal `#{original_value}`, but received `#{received_value}`" + "Expected fhir_client auth info `#{key}` to equal `#{original_value}`, " \ + "but received `#{received_value}`" end end end @@ -302,13 +318,20 @@ class Suite < Inferno::TestSuite ] }, default: AuthInfoConstants.asymmetric_confidential_access_default.to_json + + fhir_client do + url URL + auth_info :asymmetric_access_auth_info + end run do + auth_info = fhir_client.auth_info AuthInfoConstants.asymmetric_confidential_access_default.each do |key, original_value| next if key == :jwks - received_value = asymmetric_access_auth_info.send(key) + received_value = auth_info.send(key) assert received_value == original_value, - "Expected `#{key}` to equal `#{original_value}`, but received `#{received_value}`" + "Expected fhir_client auth info `#{key}` to equal `#{original_value}`, " \ + "but received `#{received_value}`" end end end @@ -329,13 +352,20 @@ class Suite < Inferno::TestSuite ] }, default: AuthInfoConstants.backend_services_access_default.to_json + + fhir_client do + url URL + auth_info :backend_services_access_auth_info + end run do + auth_info = fhir_client.auth_info AuthInfoConstants.backend_services_access_default.each do |key, original_value| next if key == :jwks - received_value = backend_services_access_auth_info.send(key) + received_value = auth_info.send(key) assert received_value == original_value, - "Expected `#{key}` to equal `#{original_value}`, but received `#{received_value}`" + "Expected fhir_client auth info `#{key}` to equal `#{original_value}`, " \ + "but received `#{received_value}`" end end end diff --git a/lib/inferno/dsl/auth_info.rb b/lib/inferno/dsl/auth_info.rb index 534add5ff..1f89f3385 100644 --- a/lib/inferno/dsl/auth_info.rb +++ b/lib/inferno/dsl/auth_info.rb @@ -19,7 +19,7 @@ module DSL # to normal inputs. # # The AuthInfo input type supports two different modes in the UI. Different - # fields will be presented to the user dependengi on which mode is selected. + # fields will be presented to the user depending on which mode is selected. # - `auth` - This presents the inputs needed to perform authorization, and # is appropriate to use as an input to test groups which perform # authorization @@ -161,13 +161,12 @@ def to_s # @private def add_to_client(client) - # TODO - # client.auth = self - # self.client = client + client.auth_info = self + self.client = client + # TODO: do we want to perform authorization if no access_token or rely on SMART/ other auth tests? + return unless access_token.present? - # return unless access_token.present? - - # client.set_bearer_token(access_token) + client.set_bearer_token(access_token) end end end diff --git a/lib/inferno/dsl/fhir_client_builder.rb b/lib/inferno/dsl/fhir_client_builder.rb index d6af9aff5..f723e0821 100644 --- a/lib/inferno/dsl/fhir_client_builder.rb +++ b/lib/inferno/dsl/fhir_client_builder.rb @@ -20,6 +20,20 @@ module DSL # url :url # bearer_token :access_token # end + # + # @example + # input :url + # input :fhir_auth, + # type: :auth_info, + # options: { + # mode: 'access' + # } + # + # fhir_client do + # url :url + # headers 'My-Custom_header' => 'CUSTOM_HEADER_VALUE' + # auth_info :fhir_auth + # end class FHIRClientBuilder attr_accessor :runnable @@ -33,6 +47,7 @@ def build(runnable, block) client.default_json client.set_bearer_token bearer_token if bearer_token oauth_credentials&.add_to_client(client) + auth_info&.add_to_client(client) end end @@ -80,6 +95,20 @@ def oauth_credentials(oauth_credentials = nil) end end + # Define auth info for a client. Auth info contains info needed for client + # to perform authorization and refresh access token when necessary + # + # @param auth_info [Inferno::DSL::AuthInfo, Symbol] + # @return [void] + def auth_info(auth_info = nil) + @auth_info ||= + if auth_info.is_a? Symbol + runnable.send(auth_info) + else + auth_info + end + end + # Define custom headers for a client # # @param headers [Hash] diff --git a/lib/inferno/ext/fhir_client.rb b/lib/inferno/ext/fhir_client.rb index 6b45c9dd8..1963eecbc 100644 --- a/lib/inferno/ext/fhir_client.rb +++ b/lib/inferno/ext/fhir_client.rb @@ -1,6 +1,6 @@ module FHIR class Client - attr_accessor :oauth_credentials + attr_accessor :oauth_credentials, :auth_info def need_to_refresh? oauth_credentials&.need_to_refresh? diff --git a/spec/inferno/dsl/auth_info_spec.rb b/spec/inferno/dsl/auth_info_spec.rb new file mode 100644 index 000000000..cf3399e4c --- /dev/null +++ b/spec/inferno/dsl/auth_info_spec.rb @@ -0,0 +1,74 @@ +RSpec.describe Inferno::DSL::AuthInfo do + let(:full_params) do + { + access_token: 'ACCESS_TOKEN', + refresh_token: 'REFRESH_TOKEN', + issue_time: Time.now.iso8601, + expires_in: 3600, + token_url: 'http://example.com/token', + client_id: 'CLIENT_ID', + client_secret: 'CLIENT_SECRET', + auth_url: 'http://example.com/authorization', + requested_scopes: 'launch/patient openid fhirUser patient/*.*', + pkce_support: 'enabled', + pkce_code_challenge_method: 'S256', + auth_request_method: 'POST', + use_discovery: 'false', + name: 'NAME' + } + end + let(:auth_info) { described_class.new(full_params) } + let(:client) { FHIR::Client.new('http://example.com') } + + describe '.new' do + it 'raises an error if an invalid key is provided' do + expect { described_class.new(bad_key: 'abc') }.to( + raise_error( + Inferno::Exceptions::UnknownAttributeException, /bad_key/ + ) + ) + end + end + + describe '#add_to_client' do + it 'sets the auth info on the client' do + auth_info.add_to_client(client) + + expect(client.auth_info).to eq(auth_info) + end + + context 'when a bearer token is present' do + it 'sets the bearer token on the client' do + auth_info.add_to_client(client) + + expect(client.security_headers['Authorization']).to eq("Bearer #{auth_info.access_token}") + end + end + + context 'when no bearer token is present' do + it 'does not set the bearer token on the client' do + client.set_bearer_token('BEARER_TOKEN') + auth_info.access_token = nil + auth_info.add_to_client(client) + + expect(client.security_headers['Authorization']).to eq('Bearer BEARER_TOKEN') + end + end + end + + describe '#to_hash' do + it 'generates a hash containing all present attributes' do + hash = { access_token: 'TOKEN', client_id: 'CLIENT_ID' } + + expect(described_class.new(hash).to_hash).to include(hash) + end + end + + describe '#to_s' do + it 'generates a JSON string containing all present attributes' do + hash = { access_token: 'TOKEN', client_id: 'CLIENT_ID' } + + expect(JSON.parse(described_class.new(hash).to_s)).to include(hash.stringify_keys) + end + end +end