-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add SMART authorize and token endpoints so that clients can connect v…
…ia SMART. Update wait test to use client id so that it can be used both for the SMART requests and resource api requests
- Loading branch information
1 parent
37bddd8
commit d4a5eb4
Showing
27 changed files
with
273 additions
and
76 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
32 changes: 32 additions & 0 deletions
32
lib/carin_for_blue_button_test_kit/client/v2.0.0/endpoints/authorize_endpoint.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
module CarinForBlueButtonTestKit | ||
module MockAuthorization | ||
class AuthorizeEndpoint < Inferno::DSL::SuiteEndpoint | ||
def test_run_identifier | ||
request.params[:client_id] | ||
end | ||
|
||
def tags | ||
[AUTHORIZE_TAG] | ||
end | ||
|
||
def make_response | ||
if request.params[:redirect_uri].present? | ||
redirect_uri = "#{request.params[:redirect_uri]}?" \ | ||
"code=#{SecureRandom.hex}&" \ | ||
"state=#{request.params[:state]}" | ||
response.status = 302 | ||
response.headers['Location'] = redirect_uri | ||
else | ||
response.status = 400 | ||
response.format = 'application/fhir+json' | ||
response.body = FHIR::OperationOutcome.new( | ||
issue: FHIR::OperationOutcome::Issue.new(severity: 'fatal', code: 'required', | ||
details: FHIR::CodeableConcept.new( | ||
text: 'No redirect_uri provided' | ||
)) | ||
).to_json | ||
end | ||
end | ||
end | ||
end | ||
end |
127 changes: 114 additions & 13 deletions
127
lib/carin_for_blue_button_test_kit/client/v2.0.0/endpoints/token_endpoint.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,125 @@ | ||
require_relative '../tags' | ||
require_relative '../urls' | ||
require_relative '../mock_server' | ||
|
||
module CarinForBlueButtonTestKit | ||
class TokenEndpoint < Inferno::DSL::SuiteEndpoint | ||
include CarinForBlueButtonTestKit::MockServer | ||
module MockAuthorization | ||
AUTHORIZED_PRACTITIONER_ID = 'c4bb-Practitioner'.freeze # Must exist on the FHIR_REFERENCE_SERVER (env var) | ||
|
||
def test_run_identifier | ||
extract_client_id(request) | ||
end | ||
class TokenEndpoint < Inferno::DSL::SuiteEndpoint | ||
include CarinForBlueButtonTestKit::MockServer | ||
|
||
def make_response | ||
token_response(request) | ||
end | ||
def test_run_identifier | ||
extract_client_id | ||
end | ||
|
||
def tags | ||
[AUTH_TAG] | ||
end | ||
def tags | ||
[TOKEN_TAG] | ||
end | ||
|
||
def make_response | ||
client_id = extract_client_id | ||
access_token = client_id | ||
granted_scopes = SUPPORTED_SCOPES & requested_scopes | ||
|
||
response_hash = { access_token:, scope: granted_scopes.join(' '), token_type: 'bearer', | ||
expires_in: 3600 } | ||
|
||
if granted_scopes.include?('openid') | ||
response_hash.merge!(id_token: create_id_token(client_id, | ||
fhir_user: granted_scopes.include?('fhirUser'))) | ||
end | ||
|
||
# fhir_context_input = find_test_input("#{input_group_prefix}_smart_fhir_context") | ||
# begin | ||
# fhir_context = JSON.parse(fhir_context_input) | ||
# rescue StandardError | ||
# fhir_context = nil | ||
# end | ||
# response_hash.merge!(fhirContext: fhir_context) if fhir_context | ||
|
||
# smart_patient_input = find_test_input("#{input_group_prefix}_smart_patient_id") | ||
# response_hash.merge!(patient: smart_patient_input) if smart_patient_input | ||
|
||
response_hash.merge!(patient: '888') | ||
|
||
response.body = response_hash.to_json | ||
response.headers['Cache-Control'] = 'no-store' | ||
response.headers['Pragma'] = 'no-cache' | ||
response.headers['Access-Control-Allow-Origin'] = '*' | ||
response.status = 200 | ||
end | ||
|
||
private | ||
|
||
def extract_client_id | ||
# Public client || confidential client asymmetric || confidential client symmetric | ||
request.params[:client_id] || extract_client_id_from_client_assertion || extract_client_id_from_basic_auth | ||
end | ||
|
||
def extract_client_id_from_client_assertion | ||
encoded_jwt = request.params[:client_assertion] | ||
return unless encoded_jwt.present? | ||
|
||
jwt_payload = | ||
begin | ||
JWT.decode(encoded_jwt, nil, false)&.first # skip signature verification | ||
rescue StandardError | ||
nil | ||
end | ||
|
||
jwt_payload['iss'] || jwt_payload['sub'] if jwt_payload.present? | ||
end | ||
|
||
def input_group_prefix | ||
if test.id.include?('static') | ||
'static' | ||
elsif test.id.include?('adaptive') | ||
'adaptive' | ||
else | ||
'resp' | ||
end | ||
end | ||
|
||
def find_test_input(input_name) | ||
JSON.parse(result.input_json)&.find { |input| input['name'] == input_name }&.dig('value') | ||
end | ||
|
||
def extract_client_id_from_basic_auth | ||
encoded_credentials = request.headers['authorization']&.delete_prefix('Basic ') | ||
return unless encoded_credentials.present? | ||
|
||
decoded_credentials = Base64.decode64(encoded_credentials) | ||
decoded_credentials&.split(':')&.first | ||
end | ||
|
||
def requested_scopes | ||
auth_request = requests_repo.tagged_requests(result.test_session_id, [AUTHORIZE_TAG]).last | ||
return [] unless auth_request | ||
|
||
auth_params = if auth_request.verb.downcase == 'get' | ||
auth_request.query_parameters | ||
else | ||
URI.decode_www_form(auth_request.request_body)&.to_h | ||
end | ||
scope_str = auth_params&.dig('scope') | ||
scope_str ? URI.decode_www_form_component(scope_str).split : [] | ||
end | ||
|
||
def create_id_token(client_id, fhir_user: false) | ||
# No point in mocking an identity provider, just always use known Practitioner as the authorized user | ||
suite_fhir_base_url = request.url.split(TOKEN_PATH).first + BASE_FHIR_PATH | ||
id_token_hash = { | ||
iss: suite_fhir_base_url, | ||
sub: AUTHORIZED_PRACTITIONER_ID, | ||
aud: client_id, | ||
exp: Time.now.to_i + (24 * 60 * 60), # 24 hrs | ||
iat: Time.now.to_i | ||
} | ||
id_token_hash.merge!(fhirUser: "#{suite_fhir_base_url}/Practitioner/#{AUTHORIZED_PRACTITIONER_ID}") if fhir_user | ||
|
||
def update_result | ||
results_repo.update(result.id, result: 'pass') unless test.config.options[:accepts_multiple_requests] | ||
JWT.encode(id_token_hash, RSA_PRIVATE_KEY, 'RS256') | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.