Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Access token auth #91

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions lib/fhir_client/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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: Current access_token
# 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
# 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::AccessToken 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).
Expand Down
76 changes: 76 additions & 0 deletions test/unit/client_access_token_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
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 out a request for a resource, once configured with the 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"}).
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 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 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

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
# make a call to test the stubbed out method with auth type
assert_equal "{}", client.read(FHIR::Patient, "example").response[:body]
end

# 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
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

# 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'))
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