Skip to content

Commit

Permalink
FI-2863: Added support for auth_info to fhir_client (#512)
Browse files Browse the repository at this point in the history
* Added support for auth_info to fhir_client

Signed-off-by: Vanessa Fotso <[email protected]>

* Added unit test for auth info

Signed-off-by: Vanessa Fotso <[email protected]>

---------

Signed-off-by: Vanessa Fotso <[email protected]>
  • Loading branch information
vanessuniq committed Jul 3, 2024
1 parent ca84840 commit 6eef3b3
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 16 deletions.
46 changes: 38 additions & 8 deletions dev_suites/dev_auth_info/auth_info_suite.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
13 changes: 6 additions & 7 deletions lib/inferno/dsl/auth_info.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
29 changes: 29 additions & 0 deletions lib/inferno/dsl/fhir_client_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand Down Expand Up @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion lib/inferno/ext/fhir_client.rb
Original file line number Diff line number Diff line change
@@ -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?
Expand Down
74 changes: 74 additions & 0 deletions spec/inferno/dsl/auth_info_spec.rb
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 6eef3b3

Please sign in to comment.