From 48b02c7e23da582c42ad3078fd66138656c5a830 Mon Sep 17 00:00:00 2001 From: Vanessa Fotso Date: Fri, 3 Nov 2023 01:49:00 -0400 Subject: [PATCH 1/9] Extended smart discovery and standalone launch tests to check supports for required smart capabilities and scopes Signed-off-by: Vanessa Fotso --- .../v1.1.0/c4bb_smart_launch_group.rb | 59 +++++++ .../c4bb_smart_launch/smart_scopes_test.rb | 53 ++++++ .../well_known_capabilities_test.rb | 51 ++++++ .../well_known_supported_scopes_test.rb | 51 ++++++ .../c4bb_smart_launch_group.rb | 143 ++++++++++++++++ .../c4bb_smart_launch/smart_scopes_test.rb | 53 ++++++ .../well_known_capabilities_test.rb | 48 ++++++ .../well_known_supported_scopes_test.rb | 51 ++++++ .../v2.0.0/c4bb_smart_launch_group.rb | 152 ++++++++++++++++++ 9 files changed, 661 insertions(+) create mode 100644 lib/carin_for_blue_button_test_kit/custom_groups/v1.1.0/c4bb_smart_launch_group.rb create mode 100644 lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch/smart_scopes_test.rb create mode 100644 lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch/well_known_capabilities_test.rb create mode 100644 lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch/well_known_supported_scopes_test.rb create mode 100644 lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch_group.rb create mode 100644 lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch/smart_scopes_test.rb create mode 100644 lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch/well_known_capabilities_test.rb create mode 100644 lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch/well_known_supported_scopes_test.rb create mode 100644 lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch_group.rb diff --git a/lib/carin_for_blue_button_test_kit/custom_groups/v1.1.0/c4bb_smart_launch_group.rb b/lib/carin_for_blue_button_test_kit/custom_groups/v1.1.0/c4bb_smart_launch_group.rb new file mode 100644 index 00000000..bc26b20a --- /dev/null +++ b/lib/carin_for_blue_button_test_kit/custom_groups/v1.1.0/c4bb_smart_launch_group.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +module CarinForBlueButtonTestKit + module CARIN4BBV110 + class C4BBSMARTLaunchGroup < Inferno::TestGroup + id :c4bb_v110_smart_launch + title 'SMART App Launch' + description %( + # Background + Applications authorize to gain access to a patient record using the + [SMART App Launch + Protocol](http://hl7.org/fhir/smart-app-launch/1.0.0/)'s standalone + launch sequence. + + # Testing Methodology + These tests first access the server's SMART discovery endpoints and + verify that they are available and that the server advertises support + for the required SMART capabilities and required authorization scopes. + They then perform a standalone launch to obtain an access token which + can be used by the remaining tests to access patient data. + ) + + group from: :smart_discovery, + run_as_group: true + + group from: :smart_standalone_launch, + run_as_group: true, + config: { + outputs: { + patient_id: { name: :patient_ids }, + smart_credentials: { name: :smart_credentials } + } + }, + description: %( + # Background + + The [Standalone + Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) + allows an app, like Inferno, to be launched independent of an + existing EHR session. It is one of the two launch methods described in + the SMART App Launch Framework alongside EHR Launch. The app will + request authorization for the provided scope from the authorization + endpoint, ultimately receiving an authorization token which can be used + to gain access to resources on the FHIR server. + + # Test Methodology + + Inferno will redirect the user to the authorization endpoint so that + they can provide any required credentials and authorize the application. + Upon successful authorization, Inferno will exchange the authorization + code provided for an access token. + + For more information on the #{title}: + + * [Standalone Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) + ) + end + end +end diff --git a/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch/smart_scopes_test.rb b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch/smart_scopes_test.rb new file mode 100644 index 00000000..15f50164 --- /dev/null +++ b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch/smart_scopes_test.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +module CarinForBlueButtonTestKit + module CARIN4BBV200DEVNONFINANCIAL + class SmartScopesTest < Inferno::Test + id :c4bb_v200devnonfinancial_smart_scopes + title 'Server supports the required authorization scopes.' + description %( + All required scopes requested are expected to be granted. + Server SHALL support, at a minimum, the following requested authorization scopes: + * `openid` + * `fhirUser` + * `launch/patient` + * `patient/ExplanationOfBenefit.read` + * `patient/Coverage.read` + * `patient/Patient.read` + * `patient/Organization.read` + * `patient/Practitioner.read` + * `user/ExplanationOfBenefit.read` + * `user/Coverage.read` + * `user/Patient.read` + * `user/Organization.read` + * `user/Practitioner.read` + ) + input :requested_scopes, :received_scopes + uses_request :token + + def required_scopes + config.options[:required_scopes] + end + + run do + skip_if request.status != 200, 'Token exchange was unsuccessful' + [ + { + scopes: requested_scopes, + received_or_requested: 'requested' + }, + { + scopes: received_scopes, + received_or_requested: 'received' + } + ].each do |metadata| + scopes = metadata[:scopes].split + received_or_requested = metadata[:received_or_requested] + missing_scopes = required_scopes - scopes + assert missing_scopes.empty?, + "Required scopes were not #{received_or_requested}: #{missing_scopes.join(', ')}" + end + end + end + end +end diff --git a/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch/well_known_capabilities_test.rb b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch/well_known_capabilities_test.rb new file mode 100644 index 00000000..2350b5d8 --- /dev/null +++ b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch/well_known_capabilities_test.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module CarinForBlueButtonTestKit + module CARIN4BBV200DEVNONFINANCIAL + class WellKnownCapabilitiesTest < Inferno::Test + id :c4bb_v200devnonfinancial_smart_capabilities + title 'Server Well-known configuration declares support for the required SMART capabilities' + description %( + Servers SHALL support the following [SMART on FHIR + capabilities](https://hl7.org/fhir/us/carin-bb/Security_And_Privacy_Considerations.html#authentication-and-authorization-requirements): + + * `launch-standalone` + * `client-public` + * `client-confidential-symmetric` + * `sso-openid-connect` + * `context-standalone-patient` + * `permission-offline` + * `permission-patient` + * `permission-user` + ) + + input :well_known_configuration + + run do + skip_if well_known_configuration.blank?, 'No SMART well-known configuration received' + + assert_valid_json(well_known_configuration) + + advertised_capabilities = JSON.parse(well_known_configuration)['capabilities'] + + assert advertised_capabilities.is_a?(Array), + "Expected `capabilities` field to be an Array, but found #{advertised_capabilities.class.name}" + + required_capabilities = config.options[:required_capabilities] || [] + + missing_capabilities = required_capabilities - advertised_capabilities + + missing_capabilities_string = + missing_capabilities + .map { |capability| "\n* `#{capability}`" } + .join + + assert missing_capabilities.empty?, + " + Server did not advertise support for the following required capabilities: + #{missing_capabilities_string} + " + end + end + end +end diff --git a/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch/well_known_supported_scopes_test.rb b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch/well_known_supported_scopes_test.rb new file mode 100644 index 00000000..1442c19b --- /dev/null +++ b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch/well_known_supported_scopes_test.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module CarinForBlueButtonTestKit + module CARIN4BBV200DEVNONFINANCIAL + class WellKnownSupportedScopesTest < Inferno::Test + id :c4bb_v200devnonfinancial_wellknown_supported_scopes + title 'Server Well-known configuration declares support for the required authorization scopes' + description %( + Server SHALL support, at a minimum, the following requested authorization scopes: + * `openid` + * `fhirUser` + * `launch/patient` + * `patient/ExplanationOfBenefit.read` + * `patient/Coverage.read` + * `patient/Patient.read` + * `patient/Organization.read` + * `patient/Practitioner.read` + * `user/ExplanationOfBenefit.read` + * `user/Coverage.read` + * `user/Patient.read` + * `user/Organization.read` + * `user/Practitioner.read` + ) + + input :well_known_configuration + + run do + skip_if well_known_configuration.blank?, 'No SMART well-known configuration received' + + assert_valid_json(well_known_configuration) + + supported_scopes = JSON.parse(well_known_configuration)['scopes_supported'] + + assert !supported_scopes.nil?, 'Well-known configuration does not include scopes_supported' + assert supported_scopes.is_a?(Array), + "Expected `scopes_supported` field to be an Array, but found #{supported_scopes.class.name}" + + required_scopes = config.options[:required_scopes] || [] + missing_scopes = required_scopes - supported_scopes + + missing_scopes_string = + missing_scopes + .map { |scope| "\n* `#{scope}`" } + .join + + assert missing_scopes.empty?, + "Server did not advertise support for the following required scopes: #{missing_scopes_string}" + end + end + end +end diff --git a/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch_group.rb b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch_group.rb new file mode 100644 index 00000000..e699bfc9 --- /dev/null +++ b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch_group.rb @@ -0,0 +1,143 @@ +# frozen_string_literal: true + +require_relative 'c4bb_smart_launch/well_known_capabilities_test' +require_relative 'c4bb_smart_launch/well_known_supported_scopes_test' +require_relative 'c4bb_smart_launch/smart_scopes_test' + +module CarinForBlueButtonTestKit + module CARIN4BBV200DEVNONFINANCIAL + class C4BBSMARTLaunchGroup < Inferno::TestGroup + id :c4bb_v200devnonfinancial_smart_launch + title 'SMART App Launch' + description %( + # Background + Applications authorize to gain access to a patient record using the + [SMART App Launch + Protocol](http://hl7.org/fhir/smart-app-launch/1.0.0/)'s standalone + launch sequence. + + # Testing Methodology + These tests first access the server's SMART discovery endpoints and + verify that they are available and that the server advertises support + for the required SMART capabilities and required authorization scopes. + They then perform a standalone launch to obtain an access token which + can be used by the remaining tests to access patient data. + ) + + group from: :smart_discovery do + run_as_group + + test from: :c4bb_v200devnonfinancial_smart_capabilities do + config( + options: { + required_capabilities: %w[ + launch-standalone + client-public + client-confidential-symmetric + sso-openid-connect + context-standalone-patient + permission-offline + permission-patient + permission-user + ] + } + ) + end + + test from: :c4bb_v200devnonfinancial_wellknown_supported_scopes do + config( + options: { + required_scopes: %w[ + openid + fhirUser + launch/patient + patient/ExplanationOfBenefit.read + patient/Coverage.read + patient/Patient.read + patient/Organization.read + patient/Practitioner.read + user/ExplanationOfBenefit.read + user/Coverage.read + user/Patient.read + user/Organization.read + user/Practitioner.read + ] + } + ) + end + end + + group from: :smart_standalone_launch do + run_as_group + description %( + # Background + + The [Standalone + Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) + allows an app, like Inferno, to be launched independent of an + existing EHR session. It is one of the two launch methods described in + the SMART App Launch Framework alongside EHR Launch. The app will + request authorization for the provided scope from the authorization + endpoint, ultimately receiving an authorization token which can be used + to gain access to resources on the FHIR server. + + # Test Methodology + + Inferno will redirect the user to the authorization endpoint so that + they can provide any required credentials and authorize the application. + Upon successful authorization, Inferno will exchange the authorization + code provided for an access token. + + For more information on the #{title}: + + * [Standalone Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) + ) + + config( + inputs: { + requested_scopes: { + default: %( + launch/patient openid fhirUser + patient/ExplanationOfBenefit.read patient/Coverage.read + patient/Patient.read patient/Organization.read + patient/Practitioner.read user/ExplanationOfBenefit.read + user/Coverage.read user/Patient.read + user/Organization.read user/Practitioner.read + ).gsub(/\s{2,}/, ' ').strip + } + }, + outputs: { + patient_id: { name: :patient_ids }, + smart_credentials: { name: :smart_credentials } + } + ) + + test from: :c4bb_v200devnonfinancial_smart_scopes do + config( + inputs: { + requested_scopes: { name: :standalone_requested_scopes }, + received_scopes: { name: :standalone_received_scopes } + }, + options: { + required_scopes: %w[ + openid + fhirUser + launch/patient + patient/ExplanationOfBenefit.read + patient/Coverage.read + patient/Patient.read + patient/Organization.read + patient/Practitioner.read + user/ExplanationOfBenefit.read + user/Coverage.read + user/Patient.read + user/Organization.read + user/Practitioner.read + ] + } + ) + end + end + end + end +end diff --git a/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch/smart_scopes_test.rb b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch/smart_scopes_test.rb new file mode 100644 index 00000000..b190f5b3 --- /dev/null +++ b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch/smart_scopes_test.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +module CarinForBlueButtonTestKit + module CARIN4BBV200 + class SmartScopesTest < Inferno::Test + id :c4bb_v200_smart_scopes + title 'Server supports the required authorization scopes.' + description %( + All required scopes requested are expected to be granted. + Server SHALL support, at a minimum, the following requested authorization scopes: + * `openid` + * `fhirUser` + * `launch/patient` + * `patient/ExplanationOfBenefit.read` + * `patient/Coverage.read` + * `patient/Patient.read` + * `patient/Organization.read` + * `patient/Practitioner.read` + * `user/ExplanationOfBenefit.read` + * `user/Coverage.read` + * `user/Patient.read` + * `user/Organization.read` + * `user/Practitioner.read` + ) + input :requested_scopes, :received_scopes + uses_request :token + + def required_scopes + config.options[:required_scopes] + end + + run do + skip_if request.status != 200, 'Token exchange was unsuccessful' + [ + { + scopes: requested_scopes, + received_or_requested: 'requested' + }, + { + scopes: received_scopes, + received_or_requested: 'received' + } + ].each do |metadata| + scopes = metadata[:scopes].split + received_or_requested = metadata[:received_or_requested] + missing_scopes = required_scopes - scopes + assert missing_scopes.empty?, + "Required scopes were not #{received_or_requested}: #{missing_scopes.join(', ')}" + end + end + end + end +end diff --git a/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch/well_known_capabilities_test.rb b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch/well_known_capabilities_test.rb new file mode 100644 index 00000000..f548d0be --- /dev/null +++ b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch/well_known_capabilities_test.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module CarinForBlueButtonTestKit + module CARIN4BBV200 + class WellKnownCapabilitiesTest < Inferno::Test + id :c4bb_v200_smart_capabilities + title 'Server Well-known configuration declares support for the required SMART capabilities' + description %( + Servers SHALL support the following [SMART on FHIR + capabilities](https://hl7.org/fhir/us/carin-bb/Security_And_Privacy_Considerations.html#authentication-and-authorization-requirements): + + * `launch-standalone` + * `client-public` + * `client-confidential-symmetric` + * `sso-openid-connect` + * `context-standalone-patient` + * `permission-offline` + * `permission-patient` + * `permission-user` + ) + + input :well_known_configuration + + run do + skip_if well_known_configuration.blank?, 'No SMART well-known configuration received' + + assert_valid_json(well_known_configuration) + + advertised_capabilities = JSON.parse(well_known_configuration)['capabilities'] + + assert advertised_capabilities.is_a?(Array), + "Expected `capabilities` field to be an Array, but found #{advertised_capabilities.class.name}" + + required_capabilities = config.options[:required_capabilities] || [] + + missing_capabilities = required_capabilities - advertised_capabilities + + missing_capabilities_string = + missing_capabilities + .map { |capability| "\n* `#{capability}`" } + .join + + assert missing_capabilities.empty?, + "Server did not advertise support for the following required capabilities: #{missing_capabilities_string}" + end + end + end +end diff --git a/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch/well_known_supported_scopes_test.rb b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch/well_known_supported_scopes_test.rb new file mode 100644 index 00000000..cf26168f --- /dev/null +++ b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch/well_known_supported_scopes_test.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module CarinForBlueButtonTestKit + module CARIN4BBV200 + class WellKnownSupportedScopesTest < Inferno::Test + id :c4bb_v200_wellknown_supported_scopes + title 'Server Well-known configuration declares support for the required authorization scopes' + description %( + Server SHALL support, at a minimum, the following requested authorization scopes: + * `openid` + * `fhirUser` + * `launch/patient` + * `patient/ExplanationOfBenefit.read` + * `patient/Coverage.read` + * `patient/Patient.read` + * `patient/Organization.read` + * `patient/Practitioner.read` + * `user/ExplanationOfBenefit.read` + * `user/Coverage.read` + * `user/Patient.read` + * `user/Organization.read` + * `user/Practitioner.read` + ) + + input :well_known_configuration + + run do + skip_if well_known_configuration.blank?, 'No SMART well-known configuration received' + + assert_valid_json(well_known_configuration) + + supported_scopes = JSON.parse(well_known_configuration)['scopes_supported'] + + assert !supported_scopes.nil?, 'Well-known configuration does not include scopes_supported' + assert supported_scopes.is_a?(Array), + "Expected `scopes_supported` field to be an Array, but found #{supported_scopes.class.name}" + + required_scopes = config.options[:required_scopes] || [] + missing_scopes = required_scopes - supported_scopes + + missing_scopes_string = + missing_scopes + .map { |scope| "\n* `#{scope}`" } + .join + + assert missing_scopes.empty?, + "Server did not advertise support for the following required scopes: #{missing_scopes_string}" + end + end + end +end diff --git a/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch_group.rb b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch_group.rb new file mode 100644 index 00000000..31bd89a7 --- /dev/null +++ b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch_group.rb @@ -0,0 +1,152 @@ +# frozen_string_literal: true + +require_relative 'c4bb_smart_launch/well_known_capabilities_test' +require_relative 'c4bb_smart_launch/well_known_supported_scopes_test' +require_relative 'c4bb_smart_launch/smart_scopes_test' + +module CarinForBlueButtonTestKit + module CARIN4BBV200 + class C4BBSMARTLaunchGroup < Inferno::TestGroup + id :c4bb_v200_smart_launch + title 'SMART App Launch' + description %( + # Background + Applications authorize to gain access to a patient record using the + [SMART App Launch + Protocol](http://hl7.org/fhir/smart-app-launch/1.0.0/)'s standalone + launch sequence. + + # Testing Methodology + These tests first access the server's SMART discovery endpoints and + verify that they are available and that the server advertises support + for the required SMART capabilities and required authorization scopes. + They then perform a standalone launch to obtain an access token which + can be used by the remaining tests to access patient data. + ) + input_order :url, + :standalone_client_id, + :standalone_client_secret, + :standalone_requested_scopes, + :use_pkce, + :pkce_code_challenge_method, + :standalone_authorization_method, + :client_auth_type, + :client_auth_encryption_method + + group from: :smart_discovery do + run_as_group + + test from: :c4bb_v200_smart_capabilities do + config( + options: { + required_capabilities: %w[ + launch-standalone + client-public + client-confidential-symmetric + sso-openid-connect + context-standalone-patient + permission-offline + permission-patient + permission-user + ] + } + ) + end + + test from: :c4bb_v200_wellknown_supported_scopes do + config( + options: { + required_scopes: %w[ + openid + fhirUser + launch/patient + patient/ExplanationOfBenefit.read + patient/Coverage.read + patient/Patient.read + patient/Organization.read + patient/Practitioner.read + user/ExplanationOfBenefit.read + user/Coverage.read + user/Patient.read + user/Organization.read + user/Practitioner.read + ] + } + ) + end + end + + group from: :smart_standalone_launch do + run_as_group + description %( + # Background + + The [Standalone + Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) + allows an app, like Inferno, to be launched independent of an + existing EHR session. It is one of the two launch methods described in + the SMART App Launch Framework alongside EHR Launch. The app will + request authorization for the provided scope from the authorization + endpoint, ultimately receiving an authorization token which can be used + to gain access to resources on the FHIR server. + + # Test Methodology + + Inferno will redirect the user to the authorization endpoint so that + they can provide any required credentials and authorize the application. + Upon successful authorization, Inferno will exchange the authorization + code provided for an access token. + + For more information on the #{title}: + + * [Standalone Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) + ) + + config( + inputs: { + requested_scopes: { + default: %( + launch/patient openid fhirUser + patient/ExplanationOfBenefit.read patient/Coverage.read + patient/Patient.read patient/Organization.read + patient/Practitioner.read user/ExplanationOfBenefit.read + user/Coverage.read user/Patient.read + user/Organization.read user/Practitioner.read + ).gsub(/\s{2,}/, ' ').strip + } + }, + outputs: { + patient_id: { name: :patient_ids }, + smart_credentials: { name: :smart_credentials } + } + ) + + test from: :c4bb_v200_smart_scopes do + config( + inputs: { + requested_scopes: { name: :standalone_requested_scopes }, + received_scopes: { name: :standalone_received_scopes } + }, + options: { + required_scopes: %w[ + openid + fhirUser + launch/patient + patient/ExplanationOfBenefit.read + patient/Coverage.read + patient/Patient.read + patient/Organization.read + patient/Practitioner.read + user/ExplanationOfBenefit.read + user/Coverage.read + user/Patient.read + user/Organization.read + user/Practitioner.read + ] + } + ) + end + end + end + end +end From 123998913c87b882f48fb25d0fb5c93425fadc17 Mon Sep 17 00:00:00 2001 From: Vanessa Fotso Date: Fri, 3 Nov 2023 01:51:02 -0400 Subject: [PATCH 2/9] Updated the generator to properly add the smart app group/test to suites Signed-off-by: Vanessa Fotso --- .../generated/v1.1.0/c4bb_test_suite.rb | 38 ++------------- .../c4bb_test_suite.rb | 40 ++-------------- .../generated/v2.0.0/c4bb_test_suite.rb | 38 ++------------- .../generator/suite_generator.rb | 7 +++ .../generator/templates/suite.rb.erb | 46 +++---------------- 5 files changed, 24 insertions(+), 145 deletions(-) diff --git a/lib/carin_for_blue_button_test_kit/generated/v1.1.0/c4bb_test_suite.rb b/lib/carin_for_blue_button_test_kit/generated/v1.1.0/c4bb_test_suite.rb index ffd7cfe2..3f1c8987 100644 --- a/lib/carin_for_blue_button_test_kit/generated/v1.1.0/c4bb_test_suite.rb +++ b/lib/carin_for_blue_button_test_kit/generated/v1.1.0/c4bb_test_suite.rb @@ -3,6 +3,7 @@ require_relative '../../version' require_relative '../../capability_statement/capability_statement_group' +require_relative '../../custom_groups/v1.1.0/c4bb_smart_launch_group' require_relative 'patient_group' require_relative 'coverage_group' @@ -55,46 +56,13 @@ def self.metadata type: :oauth_credentials, optional: true - # SMART Test Suite requirement - group from: :smart_discovery, - run_as_group: true - group from: :smart_standalone_launch, - run_as_group: true, - config: { - outputs: { - patient_id: { name: :patient_ids }, - smart_credentials: { name: :smart_credentials } - } - }, - description: %( - # Background - - The [Standalone - Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) - allows an app, like Inferno, to be launched independent of an - existing EHR session. It is one of the two launch methods described in - the SMART App Launch Framework alongside EHR Launch. The app will - request authorization for the provided scope from the authorization - endpoint, ultimately receiving an authorization token which can be used - to gain access to resources on the FHIR server. - - # Test Methodology - - Inferno will redirect the user to the authorization endpoint so that - they can provide any required credentials and authorize the application. - Upon successful authorization, Inferno will exchange the authorization - code provided for an access token. - - For more information on the #{title}: - - * [Standalone Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) - ) - fhir_client do url :url oauth_credentials :smart_credentials end + group from: :c4bb_v110_smart_launch + group from: :capability_statement_group group from: :c4bb_v110_patient diff --git a/lib/carin_for_blue_button_test_kit/generated/v2.0.0-dev-nonfinancial/c4bb_test_suite.rb b/lib/carin_for_blue_button_test_kit/generated/v2.0.0-dev-nonfinancial/c4bb_test_suite.rb index ca948f01..ab4ddb08 100644 --- a/lib/carin_for_blue_button_test_kit/generated/v2.0.0-dev-nonfinancial/c4bb_test_suite.rb +++ b/lib/carin_for_blue_button_test_kit/generated/v2.0.0-dev-nonfinancial/c4bb_test_suite.rb @@ -3,6 +3,7 @@ require_relative '../../version' require_relative '../../capability_statement/capability_statement_group' +require_relative '../../custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch_group' require_relative 'patient_group' require_relative 'coverage_group' @@ -29,7 +30,7 @@ class C4BBTestKit < Inferno::TestSuite The CARIN for Blue Button test suite validates system conformance to the HL7® FHIR® [CARIN for Blue Button® Implementation Guide](http://hl7.org/fhir/us/carin-bb/history.html). - Development build for the [non-financial branch](https://build.fhir.org/ig/HL7/carin-bb/branches/non-financial/). + Development build for the [non-financial branch](https://build.fhir.org/ig/HL7/carin-bb/branches/non-financial/). ) version VERSION @@ -64,46 +65,13 @@ def self.metadata type: :oauth_credentials, optional: true - # SMART Test Suite requirement - group from: :smart_discovery, - run_as_group: true - group from: :smart_standalone_launch, - run_as_group: true, - config: { - outputs: { - patient_id: { name: :patient_ids }, - smart_credentials: { name: :smart_credentials } - } - }, - description: %( - # Background - - The [Standalone - Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) - allows an app, like Inferno, to be launched independent of an - existing EHR session. It is one of the two launch methods described in - the SMART App Launch Framework alongside EHR Launch. The app will - request authorization for the provided scope from the authorization - endpoint, ultimately receiving an authorization token which can be used - to gain access to resources on the FHIR server. - - # Test Methodology - - Inferno will redirect the user to the authorization endpoint so that - they can provide any required credentials and authorize the application. - Upon successful authorization, Inferno will exchange the authorization - code provided for an access token. - - For more information on the #{title}: - - * [Standalone Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) - ) - fhir_client do url :url oauth_credentials :smart_credentials end + group from: :c4bb_v200devnonfinancial_smart_launch + group from: :capability_statement_group group from: :c4bb_v200devnonfinancial_patient diff --git a/lib/carin_for_blue_button_test_kit/generated/v2.0.0/c4bb_test_suite.rb b/lib/carin_for_blue_button_test_kit/generated/v2.0.0/c4bb_test_suite.rb index d953abeb..4cdc6f5d 100644 --- a/lib/carin_for_blue_button_test_kit/generated/v2.0.0/c4bb_test_suite.rb +++ b/lib/carin_for_blue_button_test_kit/generated/v2.0.0/c4bb_test_suite.rb @@ -3,6 +3,7 @@ require_relative '../../version' require_relative '../../capability_statement/capability_statement_group' +require_relative '../../custom_groups/v2.0.0/c4bb_smart_launch_group' require_relative 'patient_group' require_relative 'coverage_group' @@ -57,46 +58,13 @@ def self.metadata type: :oauth_credentials, optional: true - # SMART Test Suite requirement - group from: :smart_discovery, - run_as_group: true - group from: :smart_standalone_launch, - run_as_group: true, - config: { - outputs: { - patient_id: { name: :patient_ids }, - smart_credentials: { name: :smart_credentials } - } - }, - description: %( - # Background - - The [Standalone - Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) - allows an app, like Inferno, to be launched independent of an - existing EHR session. It is one of the two launch methods described in - the SMART App Launch Framework alongside EHR Launch. The app will - request authorization for the provided scope from the authorization - endpoint, ultimately receiving an authorization token which can be used - to gain access to resources on the FHIR server. - - # Test Methodology - - Inferno will redirect the user to the authorization endpoint so that - they can provide any required credentials and authorize the application. - Upon successful authorization, Inferno will exchange the authorization - code provided for an access token. - - For more information on the #{title}: - - * [Standalone Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) - ) - fhir_client do url :url oauth_credentials :smart_credentials end + group from: :c4bb_v200_smart_launch + group from: :capability_statement_group group from: :c4bb_v200_patient diff --git a/lib/carin_for_blue_button_test_kit/generator/suite_generator.rb b/lib/carin_for_blue_button_test_kit/generator/suite_generator.rb index c277db1e..54502cd8 100644 --- a/lib/carin_for_blue_button_test_kit/generator/suite_generator.rb +++ b/lib/carin_for_blue_button_test_kit/generator/suite_generator.rb @@ -89,6 +89,13 @@ def capability_statement_group_id "c4bb_#{ig_metadata.reformatted_version}_capability_statement" end + def smart_launch_file_name + "../../custom_groups/#{ig_metadata.ig_version}/c4bb_smart_launch_group" + end + + def smart_launch_group_id + "c4bb_#{ig_metadata.reformatted_version}_smart_launch" + end end end end diff --git a/lib/carin_for_blue_button_test_kit/generator/templates/suite.rb.erb b/lib/carin_for_blue_button_test_kit/generator/templates/suite.rb.erb index e14696bc..e0a512ed 100644 --- a/lib/carin_for_blue_button_test_kit/generator/templates/suite.rb.erb +++ b/lib/carin_for_blue_button_test_kit/generator/templates/suite.rb.erb @@ -3,6 +3,7 @@ require 'smart_app_launch_test_kit' require_relative '../../version' require_relative '../../capability_statement/capability_statement_group' +require_relative '<%= smart_launch_file_name %>' <% group_file_list.each do |file_name| %>require_relative '<%= file_name %>' <% end %> @@ -11,11 +12,11 @@ module CarinForBlueButtonTestKit class <%= class_name %> < Inferno::TestSuite title '<%= title %>' description %( - The CARIN for Blue Button test suite validates system conformance to the HL7® FHIR® [CARIN for Blue Button® Implementation Guide](<%=ig_link %>). + The CARIN for Blue Button test suite validates system conformance to the HL7® FHIR® [CARIN for Blue Button® Implementation Guide](<%= ig_link %>). - <% if module_name.include? ('FINANCIAL')%> - Development build for the [non-financial branch](https://build.fhir.org/ig/HL7/carin-bb/branches/non-financial/). - <% end%> + <% if module_name.include? ("FINANCIAL") %> + Development build for the [non-financial branch](https://build.fhir.org/ig/HL7/carin-bb/branches/non-financial/). + <% end %> ) version VERSION links [ @@ -49,46 +50,13 @@ module CarinForBlueButtonTestKit type: :oauth_credentials, optional: true - # SMART Test Suite requirement - group from: :smart_discovery, - run_as_group: true - group from: :smart_standalone_launch, - run_as_group: true, - config: { - outputs: { - patient_id: { name: :patient_ids }, - smart_credentials: { name: :smart_credentials } - } - }, - description: %( - # Background - - The [Standalone - Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) - allows an app, like Inferno, to be launched independent of an - existing EHR session. It is one of the two launch methods described in - the SMART App Launch Framework alongside EHR Launch. The app will - request authorization for the provided scope from the authorization - endpoint, ultimately receiving an authorization token which can be used - to gain access to resources on the FHIR server. - - # Test Methodology - - Inferno will redirect the user to the authorization endpoint so that - they can provide any required credentials and authorize the application. - Upon successful authorization, Inferno will exchange the authorization - code provided for an access token. - - For more information on the #{title}: - - * [Standalone Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) - ) - fhir_client do url :url oauth_credentials :smart_credentials end + group from: :<%= smart_launch_group_id %> + group from: :capability_statement_group <% group_id_list.each do |id| %> group from: :<%= id %><% end %> From 315d2f85027ae8846204dedff1fe75f5ccf0eef8 Mon Sep 17 00:00:00 2001 From: Vanessa Fotso Date: Fri, 3 Nov 2023 03:03:24 -0400 Subject: [PATCH 3/9] Adding specs for the extended smart capabilities Signed-off-by: Vanessa Fotso --- .../v200_smart_scopes_test_spec.rb | 68 ++++++++++++++++++ .../v200_well_known_capabilities_test_spec.rb | 65 +++++++++++++++++ ...0_well_known_supported_scopes_test_spec.rb | 70 +++++++++++++++++++ 3 files changed, 203 insertions(+) create mode 100644 spec/carin_for_blue_button/v200_smart_scopes_test_spec.rb create mode 100644 spec/carin_for_blue_button/v200_well_known_capabilities_test_spec.rb create mode 100644 spec/carin_for_blue_button/v200_well_known_supported_scopes_test_spec.rb diff --git a/spec/carin_for_blue_button/v200_smart_scopes_test_spec.rb b/spec/carin_for_blue_button/v200_smart_scopes_test_spec.rb new file mode 100644 index 00000000..4c718183 --- /dev/null +++ b/spec/carin_for_blue_button/v200_smart_scopes_test_spec.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +RSpec.describe CarinForBlueButtonTestKit::CARIN4BBV200::SmartScopesTest do + def run(runnable, inputs = {}) + test_run_params = { test_session_id: test_session.id }.merge(runnable.reference_hash) + test_run = Inferno::Repositories::TestRuns.new.create(test_run_params) + inputs.each do |name, value| + session_data_repo.save( + test_session_id: test_session.id, + name:, + value:, + type: runnable.config.input_type(name) + ) + end + Inferno::TestRunner.new(test_session:, test_run:).run(runnable) + end + + let(:test) { described_class } + let(:test_session) { repo_create(:test_session, test_suite_id: 'c4bb_v200') } + let(:session_data_repo) { Inferno::Repositories::SessionData.new } + let(:required_scopes) do + %w[ + openid + fhirUser + launch/patient + patient/ExplanationOfBenefit.read + patient/Coverage.read + patient/Patient.read + patient/Organization.read + patient/Practitioner.read + user/ExplanationOfBenefit.read + user/Coverage.read + user/Patient.read + user/Organization.read + user/Practitioner.read + ] + end + + before do + repo_create(:request, test_session_id: test_session.id, name: :token) + allow_any_instance_of(test).to receive(:required_scopes).and_return(required_scopes) + end + + context 'with received scopes' do + let(:requested_scopes) { required_scopes.join(' ') } + + it 'passes if the received scopes grant access to all required resource types' do + result = run(test, requested_scopes:, received_scopes: required_scopes.join(' ')) + + expect(result.result).to eq('pass') + end + + it 'fails if the received scopes do not grant access to all required resource types' do + result = run(test, requested_scopes:, received_scopes: 'patient/Patient.read') + + expect(result.result).to eq('fail') + end + end + + context 'with requested scopes' do + it 'fails if a required scope was not requested' do + result = run(test, requested_scopes: 'online_access launch') + + expect(result.result).to eq('fail') + expect(result.result_message).to include('Required scopes were not requested: ') + end + end +end diff --git a/spec/carin_for_blue_button/v200_well_known_capabilities_test_spec.rb b/spec/carin_for_blue_button/v200_well_known_capabilities_test_spec.rb new file mode 100644 index 00000000..27d0b166 --- /dev/null +++ b/spec/carin_for_blue_button/v200_well_known_capabilities_test_spec.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +RSpec.describe CarinForBlueButtonTestKit::CARIN4BBV200DEVNONFINANCIAL::WellKnownCapabilitiesTest do + def run(runnable, inputs = {}) + test_run_params = { test_session_id: test_session.id }.merge(runnable.reference_hash) + test_run = Inferno::Repositories::TestRuns.new.create(test_run_params) + inputs.each do |name, value| + session_data_repo.save( + test_session_id: test_session.id, + name:, + value:, + type: runnable.config.input_type(name) + ) + end + Inferno::TestRunner.new(test_session:, test_run:).run(runnable) + end + + let(:test) { described_class } + let(:test_session) { repo_create(:test_session, test_suite_id: 'c4bb_v200') } + let(:session_data_repo) { Inferno::Repositories::SessionData.new } + let(:well_known_config) do + { + capabilities: required_capabilities + } + end + let(:well_known_config_bad) do + { + capabilities: [] + } + end + let(:required_capabilities) do + %w[ + launch-standalone + client-public + client-confidential-symmetric + sso-openid-connect + context-standalone-patient + permission-offline + permission-patient + permission-user + ] + end + let(:test_config) do + Inferno::DSL::Configurable::Configuration.new(options: { required_capabilities: }) + end + before do + allow_any_instance_of(test).to(receive(:config).and_return(test_config)) + end + + it 'passes if all required capabilities are advertised in the Well-known configuration`' do + inputs = { well_known_configuration: JSON.generate(well_known_config) } + + result = run(test, inputs) + + expect(result.result).to eq('pass') + end + + it 'fails if all required capabilities are not advertised in the Well-known configuration`' do + inputs = { well_known_configuration: JSON.generate(well_known_config_bad) } + + result = run(test, inputs) + + expect(result.result).to eq('fail') + end +end diff --git a/spec/carin_for_blue_button/v200_well_known_supported_scopes_test_spec.rb b/spec/carin_for_blue_button/v200_well_known_supported_scopes_test_spec.rb new file mode 100644 index 00000000..f0f2e421 --- /dev/null +++ b/spec/carin_for_blue_button/v200_well_known_supported_scopes_test_spec.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +RSpec.describe CarinForBlueButtonTestKit::CARIN4BBV200::WellKnownSupportedScopesTest do + def run(runnable, inputs = {}) + test_run_params = { test_session_id: test_session.id }.merge(runnable.reference_hash) + test_run = Inferno::Repositories::TestRuns.new.create(test_run_params) + inputs.each do |name, value| + session_data_repo.save( + test_session_id: test_session.id, + name:, + value:, + type: runnable.config.input_type(name) + ) + end + Inferno::TestRunner.new(test_session:, test_run:).run(runnable) + end + + let(:test) { described_class } + let(:test_session) { repo_create(:test_session, test_suite_id: 'c4bb_v200') } + let(:session_data_repo) { Inferno::Repositories::SessionData.new } + let(:well_known_config) do + { + scopes_supported: required_scopes + } + end + let(:well_known_config_bad) do + { + scopes_supported: [] + } + end + let(:required_scopes) do + %w[ + openid + fhirUser + launch/patient + patient/ExplanationOfBenefit.read + patient/Coverage.read + patient/Patient.read + patient/Organization.read + patient/Practitioner.read + user/ExplanationOfBenefit.read + user/Coverage.read + user/Patient.read + user/Organization.read + user/Practitioner.read + ] + end + let(:test_config) do + Inferno::DSL::Configurable::Configuration.new(options: { required_scopes: }) + end + before do + allow_any_instance_of(test).to(receive(:config).and_return(test_config)) + end + + it 'passes if all required scopes are advertised in the Well-known configuration`' do + inputs = { well_known_configuration: JSON.generate(well_known_config) } + + result = run(test, inputs) + + expect(result.result).to eq('pass') + end + + it 'fails if all required scopes are not advertised in the Well-known configuration`' do + inputs = { well_known_configuration: JSON.generate(well_known_config_bad) } + + result = run(test, inputs) + + expect(result.result).to eq('fail') + end +end From 1ec5ec24a9a561fdb8e15255d0f7026d3dc0de27 Mon Sep 17 00:00:00 2001 From: Vanessa Fotso Date: Fri, 3 Nov 2023 01:49:00 -0400 Subject: [PATCH 4/9] Extended smart discovery and standalone launch tests to check supports for required smart capabilities and scopes Signed-off-by: Vanessa Fotso --- .../v1.1.0/c4bb_smart_launch_group.rb | 59 +++++++ .../c4bb_smart_launch/smart_scopes_test.rb | 53 ++++++ .../well_known_capabilities_test.rb | 51 ++++++ .../well_known_supported_scopes_test.rb | 51 ++++++ .../c4bb_smart_launch_group.rb | 143 ++++++++++++++++ .../c4bb_smart_launch/smart_scopes_test.rb | 53 ++++++ .../well_known_capabilities_test.rb | 48 ++++++ .../well_known_supported_scopes_test.rb | 51 ++++++ .../v2.0.0/c4bb_smart_launch_group.rb | 152 ++++++++++++++++++ 9 files changed, 661 insertions(+) create mode 100644 lib/carin_for_blue_button_test_kit/custom_groups/v1.1.0/c4bb_smart_launch_group.rb create mode 100644 lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch/smart_scopes_test.rb create mode 100644 lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch/well_known_capabilities_test.rb create mode 100644 lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch/well_known_supported_scopes_test.rb create mode 100644 lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch_group.rb create mode 100644 lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch/smart_scopes_test.rb create mode 100644 lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch/well_known_capabilities_test.rb create mode 100644 lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch/well_known_supported_scopes_test.rb create mode 100644 lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch_group.rb diff --git a/lib/carin_for_blue_button_test_kit/custom_groups/v1.1.0/c4bb_smart_launch_group.rb b/lib/carin_for_blue_button_test_kit/custom_groups/v1.1.0/c4bb_smart_launch_group.rb new file mode 100644 index 00000000..bc26b20a --- /dev/null +++ b/lib/carin_for_blue_button_test_kit/custom_groups/v1.1.0/c4bb_smart_launch_group.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +module CarinForBlueButtonTestKit + module CARIN4BBV110 + class C4BBSMARTLaunchGroup < Inferno::TestGroup + id :c4bb_v110_smart_launch + title 'SMART App Launch' + description %( + # Background + Applications authorize to gain access to a patient record using the + [SMART App Launch + Protocol](http://hl7.org/fhir/smart-app-launch/1.0.0/)'s standalone + launch sequence. + + # Testing Methodology + These tests first access the server's SMART discovery endpoints and + verify that they are available and that the server advertises support + for the required SMART capabilities and required authorization scopes. + They then perform a standalone launch to obtain an access token which + can be used by the remaining tests to access patient data. + ) + + group from: :smart_discovery, + run_as_group: true + + group from: :smart_standalone_launch, + run_as_group: true, + config: { + outputs: { + patient_id: { name: :patient_ids }, + smart_credentials: { name: :smart_credentials } + } + }, + description: %( + # Background + + The [Standalone + Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) + allows an app, like Inferno, to be launched independent of an + existing EHR session. It is one of the two launch methods described in + the SMART App Launch Framework alongside EHR Launch. The app will + request authorization for the provided scope from the authorization + endpoint, ultimately receiving an authorization token which can be used + to gain access to resources on the FHIR server. + + # Test Methodology + + Inferno will redirect the user to the authorization endpoint so that + they can provide any required credentials and authorize the application. + Upon successful authorization, Inferno will exchange the authorization + code provided for an access token. + + For more information on the #{title}: + + * [Standalone Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) + ) + end + end +end diff --git a/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch/smart_scopes_test.rb b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch/smart_scopes_test.rb new file mode 100644 index 00000000..15f50164 --- /dev/null +++ b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch/smart_scopes_test.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +module CarinForBlueButtonTestKit + module CARIN4BBV200DEVNONFINANCIAL + class SmartScopesTest < Inferno::Test + id :c4bb_v200devnonfinancial_smart_scopes + title 'Server supports the required authorization scopes.' + description %( + All required scopes requested are expected to be granted. + Server SHALL support, at a minimum, the following requested authorization scopes: + * `openid` + * `fhirUser` + * `launch/patient` + * `patient/ExplanationOfBenefit.read` + * `patient/Coverage.read` + * `patient/Patient.read` + * `patient/Organization.read` + * `patient/Practitioner.read` + * `user/ExplanationOfBenefit.read` + * `user/Coverage.read` + * `user/Patient.read` + * `user/Organization.read` + * `user/Practitioner.read` + ) + input :requested_scopes, :received_scopes + uses_request :token + + def required_scopes + config.options[:required_scopes] + end + + run do + skip_if request.status != 200, 'Token exchange was unsuccessful' + [ + { + scopes: requested_scopes, + received_or_requested: 'requested' + }, + { + scopes: received_scopes, + received_or_requested: 'received' + } + ].each do |metadata| + scopes = metadata[:scopes].split + received_or_requested = metadata[:received_or_requested] + missing_scopes = required_scopes - scopes + assert missing_scopes.empty?, + "Required scopes were not #{received_or_requested}: #{missing_scopes.join(', ')}" + end + end + end + end +end diff --git a/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch/well_known_capabilities_test.rb b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch/well_known_capabilities_test.rb new file mode 100644 index 00000000..2350b5d8 --- /dev/null +++ b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch/well_known_capabilities_test.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module CarinForBlueButtonTestKit + module CARIN4BBV200DEVNONFINANCIAL + class WellKnownCapabilitiesTest < Inferno::Test + id :c4bb_v200devnonfinancial_smart_capabilities + title 'Server Well-known configuration declares support for the required SMART capabilities' + description %( + Servers SHALL support the following [SMART on FHIR + capabilities](https://hl7.org/fhir/us/carin-bb/Security_And_Privacy_Considerations.html#authentication-and-authorization-requirements): + + * `launch-standalone` + * `client-public` + * `client-confidential-symmetric` + * `sso-openid-connect` + * `context-standalone-patient` + * `permission-offline` + * `permission-patient` + * `permission-user` + ) + + input :well_known_configuration + + run do + skip_if well_known_configuration.blank?, 'No SMART well-known configuration received' + + assert_valid_json(well_known_configuration) + + advertised_capabilities = JSON.parse(well_known_configuration)['capabilities'] + + assert advertised_capabilities.is_a?(Array), + "Expected `capabilities` field to be an Array, but found #{advertised_capabilities.class.name}" + + required_capabilities = config.options[:required_capabilities] || [] + + missing_capabilities = required_capabilities - advertised_capabilities + + missing_capabilities_string = + missing_capabilities + .map { |capability| "\n* `#{capability}`" } + .join + + assert missing_capabilities.empty?, + " + Server did not advertise support for the following required capabilities: + #{missing_capabilities_string} + " + end + end + end +end diff --git a/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch/well_known_supported_scopes_test.rb b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch/well_known_supported_scopes_test.rb new file mode 100644 index 00000000..1442c19b --- /dev/null +++ b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch/well_known_supported_scopes_test.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module CarinForBlueButtonTestKit + module CARIN4BBV200DEVNONFINANCIAL + class WellKnownSupportedScopesTest < Inferno::Test + id :c4bb_v200devnonfinancial_wellknown_supported_scopes + title 'Server Well-known configuration declares support for the required authorization scopes' + description %( + Server SHALL support, at a minimum, the following requested authorization scopes: + * `openid` + * `fhirUser` + * `launch/patient` + * `patient/ExplanationOfBenefit.read` + * `patient/Coverage.read` + * `patient/Patient.read` + * `patient/Organization.read` + * `patient/Practitioner.read` + * `user/ExplanationOfBenefit.read` + * `user/Coverage.read` + * `user/Patient.read` + * `user/Organization.read` + * `user/Practitioner.read` + ) + + input :well_known_configuration + + run do + skip_if well_known_configuration.blank?, 'No SMART well-known configuration received' + + assert_valid_json(well_known_configuration) + + supported_scopes = JSON.parse(well_known_configuration)['scopes_supported'] + + assert !supported_scopes.nil?, 'Well-known configuration does not include scopes_supported' + assert supported_scopes.is_a?(Array), + "Expected `scopes_supported` field to be an Array, but found #{supported_scopes.class.name}" + + required_scopes = config.options[:required_scopes] || [] + missing_scopes = required_scopes - supported_scopes + + missing_scopes_string = + missing_scopes + .map { |scope| "\n* `#{scope}`" } + .join + + assert missing_scopes.empty?, + "Server did not advertise support for the following required scopes: #{missing_scopes_string}" + end + end + end +end diff --git a/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch_group.rb b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch_group.rb new file mode 100644 index 00000000..e699bfc9 --- /dev/null +++ b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch_group.rb @@ -0,0 +1,143 @@ +# frozen_string_literal: true + +require_relative 'c4bb_smart_launch/well_known_capabilities_test' +require_relative 'c4bb_smart_launch/well_known_supported_scopes_test' +require_relative 'c4bb_smart_launch/smart_scopes_test' + +module CarinForBlueButtonTestKit + module CARIN4BBV200DEVNONFINANCIAL + class C4BBSMARTLaunchGroup < Inferno::TestGroup + id :c4bb_v200devnonfinancial_smart_launch + title 'SMART App Launch' + description %( + # Background + Applications authorize to gain access to a patient record using the + [SMART App Launch + Protocol](http://hl7.org/fhir/smart-app-launch/1.0.0/)'s standalone + launch sequence. + + # Testing Methodology + These tests first access the server's SMART discovery endpoints and + verify that they are available and that the server advertises support + for the required SMART capabilities and required authorization scopes. + They then perform a standalone launch to obtain an access token which + can be used by the remaining tests to access patient data. + ) + + group from: :smart_discovery do + run_as_group + + test from: :c4bb_v200devnonfinancial_smart_capabilities do + config( + options: { + required_capabilities: %w[ + launch-standalone + client-public + client-confidential-symmetric + sso-openid-connect + context-standalone-patient + permission-offline + permission-patient + permission-user + ] + } + ) + end + + test from: :c4bb_v200devnonfinancial_wellknown_supported_scopes do + config( + options: { + required_scopes: %w[ + openid + fhirUser + launch/patient + patient/ExplanationOfBenefit.read + patient/Coverage.read + patient/Patient.read + patient/Organization.read + patient/Practitioner.read + user/ExplanationOfBenefit.read + user/Coverage.read + user/Patient.read + user/Organization.read + user/Practitioner.read + ] + } + ) + end + end + + group from: :smart_standalone_launch do + run_as_group + description %( + # Background + + The [Standalone + Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) + allows an app, like Inferno, to be launched independent of an + existing EHR session. It is one of the two launch methods described in + the SMART App Launch Framework alongside EHR Launch. The app will + request authorization for the provided scope from the authorization + endpoint, ultimately receiving an authorization token which can be used + to gain access to resources on the FHIR server. + + # Test Methodology + + Inferno will redirect the user to the authorization endpoint so that + they can provide any required credentials and authorize the application. + Upon successful authorization, Inferno will exchange the authorization + code provided for an access token. + + For more information on the #{title}: + + * [Standalone Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) + ) + + config( + inputs: { + requested_scopes: { + default: %( + launch/patient openid fhirUser + patient/ExplanationOfBenefit.read patient/Coverage.read + patient/Patient.read patient/Organization.read + patient/Practitioner.read user/ExplanationOfBenefit.read + user/Coverage.read user/Patient.read + user/Organization.read user/Practitioner.read + ).gsub(/\s{2,}/, ' ').strip + } + }, + outputs: { + patient_id: { name: :patient_ids }, + smart_credentials: { name: :smart_credentials } + } + ) + + test from: :c4bb_v200devnonfinancial_smart_scopes do + config( + inputs: { + requested_scopes: { name: :standalone_requested_scopes }, + received_scopes: { name: :standalone_received_scopes } + }, + options: { + required_scopes: %w[ + openid + fhirUser + launch/patient + patient/ExplanationOfBenefit.read + patient/Coverage.read + patient/Patient.read + patient/Organization.read + patient/Practitioner.read + user/ExplanationOfBenefit.read + user/Coverage.read + user/Patient.read + user/Organization.read + user/Practitioner.read + ] + } + ) + end + end + end + end +end diff --git a/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch/smart_scopes_test.rb b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch/smart_scopes_test.rb new file mode 100644 index 00000000..b190f5b3 --- /dev/null +++ b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch/smart_scopes_test.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +module CarinForBlueButtonTestKit + module CARIN4BBV200 + class SmartScopesTest < Inferno::Test + id :c4bb_v200_smart_scopes + title 'Server supports the required authorization scopes.' + description %( + All required scopes requested are expected to be granted. + Server SHALL support, at a minimum, the following requested authorization scopes: + * `openid` + * `fhirUser` + * `launch/patient` + * `patient/ExplanationOfBenefit.read` + * `patient/Coverage.read` + * `patient/Patient.read` + * `patient/Organization.read` + * `patient/Practitioner.read` + * `user/ExplanationOfBenefit.read` + * `user/Coverage.read` + * `user/Patient.read` + * `user/Organization.read` + * `user/Practitioner.read` + ) + input :requested_scopes, :received_scopes + uses_request :token + + def required_scopes + config.options[:required_scopes] + end + + run do + skip_if request.status != 200, 'Token exchange was unsuccessful' + [ + { + scopes: requested_scopes, + received_or_requested: 'requested' + }, + { + scopes: received_scopes, + received_or_requested: 'received' + } + ].each do |metadata| + scopes = metadata[:scopes].split + received_or_requested = metadata[:received_or_requested] + missing_scopes = required_scopes - scopes + assert missing_scopes.empty?, + "Required scopes were not #{received_or_requested}: #{missing_scopes.join(', ')}" + end + end + end + end +end diff --git a/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch/well_known_capabilities_test.rb b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch/well_known_capabilities_test.rb new file mode 100644 index 00000000..f548d0be --- /dev/null +++ b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch/well_known_capabilities_test.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module CarinForBlueButtonTestKit + module CARIN4BBV200 + class WellKnownCapabilitiesTest < Inferno::Test + id :c4bb_v200_smart_capabilities + title 'Server Well-known configuration declares support for the required SMART capabilities' + description %( + Servers SHALL support the following [SMART on FHIR + capabilities](https://hl7.org/fhir/us/carin-bb/Security_And_Privacy_Considerations.html#authentication-and-authorization-requirements): + + * `launch-standalone` + * `client-public` + * `client-confidential-symmetric` + * `sso-openid-connect` + * `context-standalone-patient` + * `permission-offline` + * `permission-patient` + * `permission-user` + ) + + input :well_known_configuration + + run do + skip_if well_known_configuration.blank?, 'No SMART well-known configuration received' + + assert_valid_json(well_known_configuration) + + advertised_capabilities = JSON.parse(well_known_configuration)['capabilities'] + + assert advertised_capabilities.is_a?(Array), + "Expected `capabilities` field to be an Array, but found #{advertised_capabilities.class.name}" + + required_capabilities = config.options[:required_capabilities] || [] + + missing_capabilities = required_capabilities - advertised_capabilities + + missing_capabilities_string = + missing_capabilities + .map { |capability| "\n* `#{capability}`" } + .join + + assert missing_capabilities.empty?, + "Server did not advertise support for the following required capabilities: #{missing_capabilities_string}" + end + end + end +end diff --git a/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch/well_known_supported_scopes_test.rb b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch/well_known_supported_scopes_test.rb new file mode 100644 index 00000000..cf26168f --- /dev/null +++ b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch/well_known_supported_scopes_test.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module CarinForBlueButtonTestKit + module CARIN4BBV200 + class WellKnownSupportedScopesTest < Inferno::Test + id :c4bb_v200_wellknown_supported_scopes + title 'Server Well-known configuration declares support for the required authorization scopes' + description %( + Server SHALL support, at a minimum, the following requested authorization scopes: + * `openid` + * `fhirUser` + * `launch/patient` + * `patient/ExplanationOfBenefit.read` + * `patient/Coverage.read` + * `patient/Patient.read` + * `patient/Organization.read` + * `patient/Practitioner.read` + * `user/ExplanationOfBenefit.read` + * `user/Coverage.read` + * `user/Patient.read` + * `user/Organization.read` + * `user/Practitioner.read` + ) + + input :well_known_configuration + + run do + skip_if well_known_configuration.blank?, 'No SMART well-known configuration received' + + assert_valid_json(well_known_configuration) + + supported_scopes = JSON.parse(well_known_configuration)['scopes_supported'] + + assert !supported_scopes.nil?, 'Well-known configuration does not include scopes_supported' + assert supported_scopes.is_a?(Array), + "Expected `scopes_supported` field to be an Array, but found #{supported_scopes.class.name}" + + required_scopes = config.options[:required_scopes] || [] + missing_scopes = required_scopes - supported_scopes + + missing_scopes_string = + missing_scopes + .map { |scope| "\n* `#{scope}`" } + .join + + assert missing_scopes.empty?, + "Server did not advertise support for the following required scopes: #{missing_scopes_string}" + end + end + end +end diff --git a/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch_group.rb b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch_group.rb new file mode 100644 index 00000000..31bd89a7 --- /dev/null +++ b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch_group.rb @@ -0,0 +1,152 @@ +# frozen_string_literal: true + +require_relative 'c4bb_smart_launch/well_known_capabilities_test' +require_relative 'c4bb_smart_launch/well_known_supported_scopes_test' +require_relative 'c4bb_smart_launch/smart_scopes_test' + +module CarinForBlueButtonTestKit + module CARIN4BBV200 + class C4BBSMARTLaunchGroup < Inferno::TestGroup + id :c4bb_v200_smart_launch + title 'SMART App Launch' + description %( + # Background + Applications authorize to gain access to a patient record using the + [SMART App Launch + Protocol](http://hl7.org/fhir/smart-app-launch/1.0.0/)'s standalone + launch sequence. + + # Testing Methodology + These tests first access the server's SMART discovery endpoints and + verify that they are available and that the server advertises support + for the required SMART capabilities and required authorization scopes. + They then perform a standalone launch to obtain an access token which + can be used by the remaining tests to access patient data. + ) + input_order :url, + :standalone_client_id, + :standalone_client_secret, + :standalone_requested_scopes, + :use_pkce, + :pkce_code_challenge_method, + :standalone_authorization_method, + :client_auth_type, + :client_auth_encryption_method + + group from: :smart_discovery do + run_as_group + + test from: :c4bb_v200_smart_capabilities do + config( + options: { + required_capabilities: %w[ + launch-standalone + client-public + client-confidential-symmetric + sso-openid-connect + context-standalone-patient + permission-offline + permission-patient + permission-user + ] + } + ) + end + + test from: :c4bb_v200_wellknown_supported_scopes do + config( + options: { + required_scopes: %w[ + openid + fhirUser + launch/patient + patient/ExplanationOfBenefit.read + patient/Coverage.read + patient/Patient.read + patient/Organization.read + patient/Practitioner.read + user/ExplanationOfBenefit.read + user/Coverage.read + user/Patient.read + user/Organization.read + user/Practitioner.read + ] + } + ) + end + end + + group from: :smart_standalone_launch do + run_as_group + description %( + # Background + + The [Standalone + Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) + allows an app, like Inferno, to be launched independent of an + existing EHR session. It is one of the two launch methods described in + the SMART App Launch Framework alongside EHR Launch. The app will + request authorization for the provided scope from the authorization + endpoint, ultimately receiving an authorization token which can be used + to gain access to resources on the FHIR server. + + # Test Methodology + + Inferno will redirect the user to the authorization endpoint so that + they can provide any required credentials and authorize the application. + Upon successful authorization, Inferno will exchange the authorization + code provided for an access token. + + For more information on the #{title}: + + * [Standalone Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) + ) + + config( + inputs: { + requested_scopes: { + default: %( + launch/patient openid fhirUser + patient/ExplanationOfBenefit.read patient/Coverage.read + patient/Patient.read patient/Organization.read + patient/Practitioner.read user/ExplanationOfBenefit.read + user/Coverage.read user/Patient.read + user/Organization.read user/Practitioner.read + ).gsub(/\s{2,}/, ' ').strip + } + }, + outputs: { + patient_id: { name: :patient_ids }, + smart_credentials: { name: :smart_credentials } + } + ) + + test from: :c4bb_v200_smart_scopes do + config( + inputs: { + requested_scopes: { name: :standalone_requested_scopes }, + received_scopes: { name: :standalone_received_scopes } + }, + options: { + required_scopes: %w[ + openid + fhirUser + launch/patient + patient/ExplanationOfBenefit.read + patient/Coverage.read + patient/Patient.read + patient/Organization.read + patient/Practitioner.read + user/ExplanationOfBenefit.read + user/Coverage.read + user/Patient.read + user/Organization.read + user/Practitioner.read + ] + } + ) + end + end + end + end +end From b241c39f2106831da0d3d2f9f901b8f61585d38d Mon Sep 17 00:00:00 2001 From: Vanessa Fotso Date: Fri, 3 Nov 2023 01:51:02 -0400 Subject: [PATCH 5/9] Updated the generator to properly add the smart app group/test to suites Signed-off-by: Vanessa Fotso --- .../generated/v1.1.0/c4bb_test_suite.rb | 38 ++------------- .../c4bb_test_suite.rb | 40 ++-------------- .../generated/v2.0.0/c4bb_test_suite.rb | 38 ++------------- .../generator/suite_generator.rb | 7 +++ .../generator/templates/suite.rb.erb | 46 +++---------------- 5 files changed, 24 insertions(+), 145 deletions(-) diff --git a/lib/carin_for_blue_button_test_kit/generated/v1.1.0/c4bb_test_suite.rb b/lib/carin_for_blue_button_test_kit/generated/v1.1.0/c4bb_test_suite.rb index ffd7cfe2..3f1c8987 100644 --- a/lib/carin_for_blue_button_test_kit/generated/v1.1.0/c4bb_test_suite.rb +++ b/lib/carin_for_blue_button_test_kit/generated/v1.1.0/c4bb_test_suite.rb @@ -3,6 +3,7 @@ require_relative '../../version' require_relative '../../capability_statement/capability_statement_group' +require_relative '../../custom_groups/v1.1.0/c4bb_smart_launch_group' require_relative 'patient_group' require_relative 'coverage_group' @@ -55,46 +56,13 @@ def self.metadata type: :oauth_credentials, optional: true - # SMART Test Suite requirement - group from: :smart_discovery, - run_as_group: true - group from: :smart_standalone_launch, - run_as_group: true, - config: { - outputs: { - patient_id: { name: :patient_ids }, - smart_credentials: { name: :smart_credentials } - } - }, - description: %( - # Background - - The [Standalone - Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) - allows an app, like Inferno, to be launched independent of an - existing EHR session. It is one of the two launch methods described in - the SMART App Launch Framework alongside EHR Launch. The app will - request authorization for the provided scope from the authorization - endpoint, ultimately receiving an authorization token which can be used - to gain access to resources on the FHIR server. - - # Test Methodology - - Inferno will redirect the user to the authorization endpoint so that - they can provide any required credentials and authorize the application. - Upon successful authorization, Inferno will exchange the authorization - code provided for an access token. - - For more information on the #{title}: - - * [Standalone Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) - ) - fhir_client do url :url oauth_credentials :smart_credentials end + group from: :c4bb_v110_smart_launch + group from: :capability_statement_group group from: :c4bb_v110_patient diff --git a/lib/carin_for_blue_button_test_kit/generated/v2.0.0-dev-nonfinancial/c4bb_test_suite.rb b/lib/carin_for_blue_button_test_kit/generated/v2.0.0-dev-nonfinancial/c4bb_test_suite.rb index ca948f01..ab4ddb08 100644 --- a/lib/carin_for_blue_button_test_kit/generated/v2.0.0-dev-nonfinancial/c4bb_test_suite.rb +++ b/lib/carin_for_blue_button_test_kit/generated/v2.0.0-dev-nonfinancial/c4bb_test_suite.rb @@ -3,6 +3,7 @@ require_relative '../../version' require_relative '../../capability_statement/capability_statement_group' +require_relative '../../custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch_group' require_relative 'patient_group' require_relative 'coverage_group' @@ -29,7 +30,7 @@ class C4BBTestKit < Inferno::TestSuite The CARIN for Blue Button test suite validates system conformance to the HL7® FHIR® [CARIN for Blue Button® Implementation Guide](http://hl7.org/fhir/us/carin-bb/history.html). - Development build for the [non-financial branch](https://build.fhir.org/ig/HL7/carin-bb/branches/non-financial/). + Development build for the [non-financial branch](https://build.fhir.org/ig/HL7/carin-bb/branches/non-financial/). ) version VERSION @@ -64,46 +65,13 @@ def self.metadata type: :oauth_credentials, optional: true - # SMART Test Suite requirement - group from: :smart_discovery, - run_as_group: true - group from: :smart_standalone_launch, - run_as_group: true, - config: { - outputs: { - patient_id: { name: :patient_ids }, - smart_credentials: { name: :smart_credentials } - } - }, - description: %( - # Background - - The [Standalone - Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) - allows an app, like Inferno, to be launched independent of an - existing EHR session. It is one of the two launch methods described in - the SMART App Launch Framework alongside EHR Launch. The app will - request authorization for the provided scope from the authorization - endpoint, ultimately receiving an authorization token which can be used - to gain access to resources on the FHIR server. - - # Test Methodology - - Inferno will redirect the user to the authorization endpoint so that - they can provide any required credentials and authorize the application. - Upon successful authorization, Inferno will exchange the authorization - code provided for an access token. - - For more information on the #{title}: - - * [Standalone Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) - ) - fhir_client do url :url oauth_credentials :smart_credentials end + group from: :c4bb_v200devnonfinancial_smart_launch + group from: :capability_statement_group group from: :c4bb_v200devnonfinancial_patient diff --git a/lib/carin_for_blue_button_test_kit/generated/v2.0.0/c4bb_test_suite.rb b/lib/carin_for_blue_button_test_kit/generated/v2.0.0/c4bb_test_suite.rb index d953abeb..4cdc6f5d 100644 --- a/lib/carin_for_blue_button_test_kit/generated/v2.0.0/c4bb_test_suite.rb +++ b/lib/carin_for_blue_button_test_kit/generated/v2.0.0/c4bb_test_suite.rb @@ -3,6 +3,7 @@ require_relative '../../version' require_relative '../../capability_statement/capability_statement_group' +require_relative '../../custom_groups/v2.0.0/c4bb_smart_launch_group' require_relative 'patient_group' require_relative 'coverage_group' @@ -57,46 +58,13 @@ def self.metadata type: :oauth_credentials, optional: true - # SMART Test Suite requirement - group from: :smart_discovery, - run_as_group: true - group from: :smart_standalone_launch, - run_as_group: true, - config: { - outputs: { - patient_id: { name: :patient_ids }, - smart_credentials: { name: :smart_credentials } - } - }, - description: %( - # Background - - The [Standalone - Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) - allows an app, like Inferno, to be launched independent of an - existing EHR session. It is one of the two launch methods described in - the SMART App Launch Framework alongside EHR Launch. The app will - request authorization for the provided scope from the authorization - endpoint, ultimately receiving an authorization token which can be used - to gain access to resources on the FHIR server. - - # Test Methodology - - Inferno will redirect the user to the authorization endpoint so that - they can provide any required credentials and authorize the application. - Upon successful authorization, Inferno will exchange the authorization - code provided for an access token. - - For more information on the #{title}: - - * [Standalone Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) - ) - fhir_client do url :url oauth_credentials :smart_credentials end + group from: :c4bb_v200_smart_launch + group from: :capability_statement_group group from: :c4bb_v200_patient diff --git a/lib/carin_for_blue_button_test_kit/generator/suite_generator.rb b/lib/carin_for_blue_button_test_kit/generator/suite_generator.rb index c277db1e..54502cd8 100644 --- a/lib/carin_for_blue_button_test_kit/generator/suite_generator.rb +++ b/lib/carin_for_blue_button_test_kit/generator/suite_generator.rb @@ -89,6 +89,13 @@ def capability_statement_group_id "c4bb_#{ig_metadata.reformatted_version}_capability_statement" end + def smart_launch_file_name + "../../custom_groups/#{ig_metadata.ig_version}/c4bb_smart_launch_group" + end + + def smart_launch_group_id + "c4bb_#{ig_metadata.reformatted_version}_smart_launch" + end end end end diff --git a/lib/carin_for_blue_button_test_kit/generator/templates/suite.rb.erb b/lib/carin_for_blue_button_test_kit/generator/templates/suite.rb.erb index e14696bc..e0a512ed 100644 --- a/lib/carin_for_blue_button_test_kit/generator/templates/suite.rb.erb +++ b/lib/carin_for_blue_button_test_kit/generator/templates/suite.rb.erb @@ -3,6 +3,7 @@ require 'smart_app_launch_test_kit' require_relative '../../version' require_relative '../../capability_statement/capability_statement_group' +require_relative '<%= smart_launch_file_name %>' <% group_file_list.each do |file_name| %>require_relative '<%= file_name %>' <% end %> @@ -11,11 +12,11 @@ module CarinForBlueButtonTestKit class <%= class_name %> < Inferno::TestSuite title '<%= title %>' description %( - The CARIN for Blue Button test suite validates system conformance to the HL7® FHIR® [CARIN for Blue Button® Implementation Guide](<%=ig_link %>). + The CARIN for Blue Button test suite validates system conformance to the HL7® FHIR® [CARIN for Blue Button® Implementation Guide](<%= ig_link %>). - <% if module_name.include? ('FINANCIAL')%> - Development build for the [non-financial branch](https://build.fhir.org/ig/HL7/carin-bb/branches/non-financial/). - <% end%> + <% if module_name.include? ("FINANCIAL") %> + Development build for the [non-financial branch](https://build.fhir.org/ig/HL7/carin-bb/branches/non-financial/). + <% end %> ) version VERSION links [ @@ -49,46 +50,13 @@ module CarinForBlueButtonTestKit type: :oauth_credentials, optional: true - # SMART Test Suite requirement - group from: :smart_discovery, - run_as_group: true - group from: :smart_standalone_launch, - run_as_group: true, - config: { - outputs: { - patient_id: { name: :patient_ids }, - smart_credentials: { name: :smart_credentials } - } - }, - description: %( - # Background - - The [Standalone - Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) - allows an app, like Inferno, to be launched independent of an - existing EHR session. It is one of the two launch methods described in - the SMART App Launch Framework alongside EHR Launch. The app will - request authorization for the provided scope from the authorization - endpoint, ultimately receiving an authorization token which can be used - to gain access to resources on the FHIR server. - - # Test Methodology - - Inferno will redirect the user to the authorization endpoint so that - they can provide any required credentials and authorize the application. - Upon successful authorization, Inferno will exchange the authorization - code provided for an access token. - - For more information on the #{title}: - - * [Standalone Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) - ) - fhir_client do url :url oauth_credentials :smart_credentials end + group from: :<%= smart_launch_group_id %> + group from: :capability_statement_group <% group_id_list.each do |id| %> group from: :<%= id %><% end %> From 47772f613ddd5f752a325c754a10d272da595c52 Mon Sep 17 00:00:00 2001 From: Vanessa Fotso Date: Fri, 3 Nov 2023 03:03:24 -0400 Subject: [PATCH 6/9] Adding specs for the extended smart capabilities Signed-off-by: Vanessa Fotso --- .../v200_smart_scopes_test_spec.rb | 68 ++++++++++++++++++ .../v200_well_known_capabilities_test_spec.rb | 65 +++++++++++++++++ ...0_well_known_supported_scopes_test_spec.rb | 70 +++++++++++++++++++ 3 files changed, 203 insertions(+) create mode 100644 spec/carin_for_blue_button/v200_smart_scopes_test_spec.rb create mode 100644 spec/carin_for_blue_button/v200_well_known_capabilities_test_spec.rb create mode 100644 spec/carin_for_blue_button/v200_well_known_supported_scopes_test_spec.rb diff --git a/spec/carin_for_blue_button/v200_smart_scopes_test_spec.rb b/spec/carin_for_blue_button/v200_smart_scopes_test_spec.rb new file mode 100644 index 00000000..4c718183 --- /dev/null +++ b/spec/carin_for_blue_button/v200_smart_scopes_test_spec.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +RSpec.describe CarinForBlueButtonTestKit::CARIN4BBV200::SmartScopesTest do + def run(runnable, inputs = {}) + test_run_params = { test_session_id: test_session.id }.merge(runnable.reference_hash) + test_run = Inferno::Repositories::TestRuns.new.create(test_run_params) + inputs.each do |name, value| + session_data_repo.save( + test_session_id: test_session.id, + name:, + value:, + type: runnable.config.input_type(name) + ) + end + Inferno::TestRunner.new(test_session:, test_run:).run(runnable) + end + + let(:test) { described_class } + let(:test_session) { repo_create(:test_session, test_suite_id: 'c4bb_v200') } + let(:session_data_repo) { Inferno::Repositories::SessionData.new } + let(:required_scopes) do + %w[ + openid + fhirUser + launch/patient + patient/ExplanationOfBenefit.read + patient/Coverage.read + patient/Patient.read + patient/Organization.read + patient/Practitioner.read + user/ExplanationOfBenefit.read + user/Coverage.read + user/Patient.read + user/Organization.read + user/Practitioner.read + ] + end + + before do + repo_create(:request, test_session_id: test_session.id, name: :token) + allow_any_instance_of(test).to receive(:required_scopes).and_return(required_scopes) + end + + context 'with received scopes' do + let(:requested_scopes) { required_scopes.join(' ') } + + it 'passes if the received scopes grant access to all required resource types' do + result = run(test, requested_scopes:, received_scopes: required_scopes.join(' ')) + + expect(result.result).to eq('pass') + end + + it 'fails if the received scopes do not grant access to all required resource types' do + result = run(test, requested_scopes:, received_scopes: 'patient/Patient.read') + + expect(result.result).to eq('fail') + end + end + + context 'with requested scopes' do + it 'fails if a required scope was not requested' do + result = run(test, requested_scopes: 'online_access launch') + + expect(result.result).to eq('fail') + expect(result.result_message).to include('Required scopes were not requested: ') + end + end +end diff --git a/spec/carin_for_blue_button/v200_well_known_capabilities_test_spec.rb b/spec/carin_for_blue_button/v200_well_known_capabilities_test_spec.rb new file mode 100644 index 00000000..27d0b166 --- /dev/null +++ b/spec/carin_for_blue_button/v200_well_known_capabilities_test_spec.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +RSpec.describe CarinForBlueButtonTestKit::CARIN4BBV200DEVNONFINANCIAL::WellKnownCapabilitiesTest do + def run(runnable, inputs = {}) + test_run_params = { test_session_id: test_session.id }.merge(runnable.reference_hash) + test_run = Inferno::Repositories::TestRuns.new.create(test_run_params) + inputs.each do |name, value| + session_data_repo.save( + test_session_id: test_session.id, + name:, + value:, + type: runnable.config.input_type(name) + ) + end + Inferno::TestRunner.new(test_session:, test_run:).run(runnable) + end + + let(:test) { described_class } + let(:test_session) { repo_create(:test_session, test_suite_id: 'c4bb_v200') } + let(:session_data_repo) { Inferno::Repositories::SessionData.new } + let(:well_known_config) do + { + capabilities: required_capabilities + } + end + let(:well_known_config_bad) do + { + capabilities: [] + } + end + let(:required_capabilities) do + %w[ + launch-standalone + client-public + client-confidential-symmetric + sso-openid-connect + context-standalone-patient + permission-offline + permission-patient + permission-user + ] + end + let(:test_config) do + Inferno::DSL::Configurable::Configuration.new(options: { required_capabilities: }) + end + before do + allow_any_instance_of(test).to(receive(:config).and_return(test_config)) + end + + it 'passes if all required capabilities are advertised in the Well-known configuration`' do + inputs = { well_known_configuration: JSON.generate(well_known_config) } + + result = run(test, inputs) + + expect(result.result).to eq('pass') + end + + it 'fails if all required capabilities are not advertised in the Well-known configuration`' do + inputs = { well_known_configuration: JSON.generate(well_known_config_bad) } + + result = run(test, inputs) + + expect(result.result).to eq('fail') + end +end diff --git a/spec/carin_for_blue_button/v200_well_known_supported_scopes_test_spec.rb b/spec/carin_for_blue_button/v200_well_known_supported_scopes_test_spec.rb new file mode 100644 index 00000000..f0f2e421 --- /dev/null +++ b/spec/carin_for_blue_button/v200_well_known_supported_scopes_test_spec.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +RSpec.describe CarinForBlueButtonTestKit::CARIN4BBV200::WellKnownSupportedScopesTest do + def run(runnable, inputs = {}) + test_run_params = { test_session_id: test_session.id }.merge(runnable.reference_hash) + test_run = Inferno::Repositories::TestRuns.new.create(test_run_params) + inputs.each do |name, value| + session_data_repo.save( + test_session_id: test_session.id, + name:, + value:, + type: runnable.config.input_type(name) + ) + end + Inferno::TestRunner.new(test_session:, test_run:).run(runnable) + end + + let(:test) { described_class } + let(:test_session) { repo_create(:test_session, test_suite_id: 'c4bb_v200') } + let(:session_data_repo) { Inferno::Repositories::SessionData.new } + let(:well_known_config) do + { + scopes_supported: required_scopes + } + end + let(:well_known_config_bad) do + { + scopes_supported: [] + } + end + let(:required_scopes) do + %w[ + openid + fhirUser + launch/patient + patient/ExplanationOfBenefit.read + patient/Coverage.read + patient/Patient.read + patient/Organization.read + patient/Practitioner.read + user/ExplanationOfBenefit.read + user/Coverage.read + user/Patient.read + user/Organization.read + user/Practitioner.read + ] + end + let(:test_config) do + Inferno::DSL::Configurable::Configuration.new(options: { required_scopes: }) + end + before do + allow_any_instance_of(test).to(receive(:config).and_return(test_config)) + end + + it 'passes if all required scopes are advertised in the Well-known configuration`' do + inputs = { well_known_configuration: JSON.generate(well_known_config) } + + result = run(test, inputs) + + expect(result.result).to eq('pass') + end + + it 'fails if all required scopes are not advertised in the Well-known configuration`' do + inputs = { well_known_configuration: JSON.generate(well_known_config_bad) } + + result = run(test, inputs) + + expect(result.result).to eq('fail') + end +end From 873180e9dbdd795dc9ca00d03ef556ba338798d0 Mon Sep 17 00:00:00 2001 From: Vanessa Fotso Date: Mon, 6 Nov 2023 03:14:35 -0500 Subject: [PATCH 7/9] Removed test for wellknow scope_supported field Signed-off-by: Vanessa Fotso --- .../well_known_supported_scopes_test.rb | 51 -------------- .../well_known_supported_scopes_test.rb | 51 -------------- ...0_well_known_supported_scopes_test_spec.rb | 70 ------------------- 3 files changed, 172 deletions(-) delete mode 100644 lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch/well_known_supported_scopes_test.rb delete mode 100644 lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch/well_known_supported_scopes_test.rb delete mode 100644 spec/carin_for_blue_button/v200_well_known_supported_scopes_test_spec.rb diff --git a/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch/well_known_supported_scopes_test.rb b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch/well_known_supported_scopes_test.rb deleted file mode 100644 index 1442c19b..00000000 --- a/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch/well_known_supported_scopes_test.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true - -module CarinForBlueButtonTestKit - module CARIN4BBV200DEVNONFINANCIAL - class WellKnownSupportedScopesTest < Inferno::Test - id :c4bb_v200devnonfinancial_wellknown_supported_scopes - title 'Server Well-known configuration declares support for the required authorization scopes' - description %( - Server SHALL support, at a minimum, the following requested authorization scopes: - * `openid` - * `fhirUser` - * `launch/patient` - * `patient/ExplanationOfBenefit.read` - * `patient/Coverage.read` - * `patient/Patient.read` - * `patient/Organization.read` - * `patient/Practitioner.read` - * `user/ExplanationOfBenefit.read` - * `user/Coverage.read` - * `user/Patient.read` - * `user/Organization.read` - * `user/Practitioner.read` - ) - - input :well_known_configuration - - run do - skip_if well_known_configuration.blank?, 'No SMART well-known configuration received' - - assert_valid_json(well_known_configuration) - - supported_scopes = JSON.parse(well_known_configuration)['scopes_supported'] - - assert !supported_scopes.nil?, 'Well-known configuration does not include scopes_supported' - assert supported_scopes.is_a?(Array), - "Expected `scopes_supported` field to be an Array, but found #{supported_scopes.class.name}" - - required_scopes = config.options[:required_scopes] || [] - missing_scopes = required_scopes - supported_scopes - - missing_scopes_string = - missing_scopes - .map { |scope| "\n* `#{scope}`" } - .join - - assert missing_scopes.empty?, - "Server did not advertise support for the following required scopes: #{missing_scopes_string}" - end - end - end -end diff --git a/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch/well_known_supported_scopes_test.rb b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch/well_known_supported_scopes_test.rb deleted file mode 100644 index cf26168f..00000000 --- a/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch/well_known_supported_scopes_test.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true - -module CarinForBlueButtonTestKit - module CARIN4BBV200 - class WellKnownSupportedScopesTest < Inferno::Test - id :c4bb_v200_wellknown_supported_scopes - title 'Server Well-known configuration declares support for the required authorization scopes' - description %( - Server SHALL support, at a minimum, the following requested authorization scopes: - * `openid` - * `fhirUser` - * `launch/patient` - * `patient/ExplanationOfBenefit.read` - * `patient/Coverage.read` - * `patient/Patient.read` - * `patient/Organization.read` - * `patient/Practitioner.read` - * `user/ExplanationOfBenefit.read` - * `user/Coverage.read` - * `user/Patient.read` - * `user/Organization.read` - * `user/Practitioner.read` - ) - - input :well_known_configuration - - run do - skip_if well_known_configuration.blank?, 'No SMART well-known configuration received' - - assert_valid_json(well_known_configuration) - - supported_scopes = JSON.parse(well_known_configuration)['scopes_supported'] - - assert !supported_scopes.nil?, 'Well-known configuration does not include scopes_supported' - assert supported_scopes.is_a?(Array), - "Expected `scopes_supported` field to be an Array, but found #{supported_scopes.class.name}" - - required_scopes = config.options[:required_scopes] || [] - missing_scopes = required_scopes - supported_scopes - - missing_scopes_string = - missing_scopes - .map { |scope| "\n* `#{scope}`" } - .join - - assert missing_scopes.empty?, - "Server did not advertise support for the following required scopes: #{missing_scopes_string}" - end - end - end -end diff --git a/spec/carin_for_blue_button/v200_well_known_supported_scopes_test_spec.rb b/spec/carin_for_blue_button/v200_well_known_supported_scopes_test_spec.rb deleted file mode 100644 index f0f2e421..00000000 --- a/spec/carin_for_blue_button/v200_well_known_supported_scopes_test_spec.rb +++ /dev/null @@ -1,70 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe CarinForBlueButtonTestKit::CARIN4BBV200::WellKnownSupportedScopesTest do - def run(runnable, inputs = {}) - test_run_params = { test_session_id: test_session.id }.merge(runnable.reference_hash) - test_run = Inferno::Repositories::TestRuns.new.create(test_run_params) - inputs.each do |name, value| - session_data_repo.save( - test_session_id: test_session.id, - name:, - value:, - type: runnable.config.input_type(name) - ) - end - Inferno::TestRunner.new(test_session:, test_run:).run(runnable) - end - - let(:test) { described_class } - let(:test_session) { repo_create(:test_session, test_suite_id: 'c4bb_v200') } - let(:session_data_repo) { Inferno::Repositories::SessionData.new } - let(:well_known_config) do - { - scopes_supported: required_scopes - } - end - let(:well_known_config_bad) do - { - scopes_supported: [] - } - end - let(:required_scopes) do - %w[ - openid - fhirUser - launch/patient - patient/ExplanationOfBenefit.read - patient/Coverage.read - patient/Patient.read - patient/Organization.read - patient/Practitioner.read - user/ExplanationOfBenefit.read - user/Coverage.read - user/Patient.read - user/Organization.read - user/Practitioner.read - ] - end - let(:test_config) do - Inferno::DSL::Configurable::Configuration.new(options: { required_scopes: }) - end - before do - allow_any_instance_of(test).to(receive(:config).and_return(test_config)) - end - - it 'passes if all required scopes are advertised in the Well-known configuration`' do - inputs = { well_known_configuration: JSON.generate(well_known_config) } - - result = run(test, inputs) - - expect(result.result).to eq('pass') - end - - it 'fails if all required scopes are not advertised in the Well-known configuration`' do - inputs = { well_known_configuration: JSON.generate(well_known_config_bad) } - - result = run(test, inputs) - - expect(result.result).to eq('fail') - end -end From 6d5448a0595f5bd36f1c1f059ddcf7943d55ccc9 Mon Sep 17 00:00:00 2001 From: Vanessa Fotso Date: Mon, 6 Nov 2023 03:18:53 -0500 Subject: [PATCH 8/9] Updated the smart scope test to thoroughly inspect the granted scope and checking if the granted scopes are superset of required scopes Signed-off-by: Vanessa Fotso --- .../c4bb_smart_launch/smart_scopes_test.rb | 70 ++++++++++++++++++- .../c4bb_smart_launch_group.rb | 32 +++------ .../c4bb_smart_launch/smart_scopes_test.rb | 70 ++++++++++++++++++- .../v2.0.0/c4bb_smart_launch_group.rb | 23 ------ 4 files changed, 145 insertions(+), 50 deletions(-) diff --git a/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch/smart_scopes_test.rb b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch/smart_scopes_test.rb index 15f50164..7d25a750 100644 --- a/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch/smart_scopes_test.rb +++ b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch/smart_scopes_test.rb @@ -25,10 +25,71 @@ class SmartScopesTest < Inferno::Test input :requested_scopes, :received_scopes uses_request :token + PATIENT_COMPARTMENT_RESOURCE_TYPES = %w[ + Patient + ExplanationOfBenefit + Coverage + Organization + Practitioner + ].freeze + + def patient_compartment_resource_types + PATIENT_COMPARTMENT_RESOURCE_TYPES + end + def required_scopes config.options[:required_scopes] end + def access_level_regex + /\A(\*|\b(read|c?ru?d?s?)\b)/ + end + + def received_scope_test(scopes) + # check if openid, fhirUser, & launch/patient was granted + scope_subset = scopes - ['openid', 'fhirUser', 'launch/patient'] + assert scope_subset.length == scopes.length - 3, + 'openid, fhirUser, & launch/patient scopes must be supported. Received scopes: ' \ + "#{scope_subset.join(', ')}." + + granted_patient_level_resource_types = [] + granted_user_level_resource_types = [] + + scope_subset.each do |scope| + scope_pieces = scope.split('/') + next unless scope_pieces.length == 2 + + scope_type, resource_scope = scope_pieces + next unless %w[patient user].include?(scope_type) + + resource_scope_parts = resource_scope.split('.') + next unless resource_scope_parts.length == 2 + + resource_type, access_level = resource_scope_parts + next unless access_level =~ access_level_regex + + if scope_type == 'patient' + granted_patient_level_resource_types << resource_type + else + granted_user_level_resource_types << resource_type + end + end + + # Check if the required patient and user level scopes are granted + missing_patient_level_resource_types = patient_compartment_resource_types - granted_patient_level_resource_types + missing_patient_level_resource_types = [] if granted_patient_level_resource_types.include?('*') + + assert missing_patient_level_resource_types.empty?, + "Requested patient-level scopes #{missing_patient_level_resource_types.join(', ')} " \ + 'were not granted by authorization server.' + + missing_user_level_resource_types = patient_compartment_resource_types - granted_user_level_resource_types + missing_user_level_resource_types = [] if granted_user_level_resource_types.include?('*') + assert missing_user_level_resource_types.empty?, + "Requested user-level scopes #{missing_user_level_resource_types.join(', ')} " \ + 'were not granted by authorization server.' + end + run do skip_if request.status != 200, 'Token exchange was unsuccessful' [ @@ -44,8 +105,13 @@ def required_scopes scopes = metadata[:scopes].split received_or_requested = metadata[:received_or_requested] missing_scopes = required_scopes - scopes - assert missing_scopes.empty?, - "Required scopes were not #{received_or_requested}: #{missing_scopes.join(', ')}" + + if received_or_requested == 'requested' + assert missing_scopes.empty?, + "Required scopes were not #{received_or_requested}: #{missing_scopes.join(', ')}" + else + received_scope_test(scopes) + end end end end diff --git a/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch_group.rb b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch_group.rb index e699bfc9..fdecebad 100644 --- a/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch_group.rb +++ b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch_group.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require_relative 'c4bb_smart_launch/well_known_capabilities_test' -require_relative 'c4bb_smart_launch/well_known_supported_scopes_test' require_relative 'c4bb_smart_launch/smart_scopes_test' module CarinForBlueButtonTestKit @@ -23,6 +22,15 @@ class C4BBSMARTLaunchGroup < Inferno::TestGroup They then perform a standalone launch to obtain an access token which can be used by the remaining tests to access patient data. ) + input_order :url, + :standalone_client_id, + :standalone_client_secret, + :standalone_requested_scopes, + :use_pkce, + :pkce_code_challenge_method, + :standalone_authorization_method, + :client_auth_type, + :client_auth_encryption_method group from: :smart_discovery do run_as_group @@ -43,28 +51,6 @@ class C4BBSMARTLaunchGroup < Inferno::TestGroup } ) end - - test from: :c4bb_v200devnonfinancial_wellknown_supported_scopes do - config( - options: { - required_scopes: %w[ - openid - fhirUser - launch/patient - patient/ExplanationOfBenefit.read - patient/Coverage.read - patient/Patient.read - patient/Organization.read - patient/Practitioner.read - user/ExplanationOfBenefit.read - user/Coverage.read - user/Patient.read - user/Organization.read - user/Practitioner.read - ] - } - ) - end end group from: :smart_standalone_launch do diff --git a/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch/smart_scopes_test.rb b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch/smart_scopes_test.rb index b190f5b3..ad6417a0 100644 --- a/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch/smart_scopes_test.rb +++ b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch/smart_scopes_test.rb @@ -25,10 +25,71 @@ class SmartScopesTest < Inferno::Test input :requested_scopes, :received_scopes uses_request :token + PATIENT_COMPARTMENT_RESOURCE_TYPES = %w[ + Patient + ExplanationOfBenefit + Coverage + Organization + Practitioner + ].freeze + + def patient_compartment_resource_types + PATIENT_COMPARTMENT_RESOURCE_TYPES + end + def required_scopes config.options[:required_scopes] end + def access_level_regex + /\A(\*|\b(read|c?ru?d?s?)\b)/ + end + + def received_scope_test(scopes) + # check if openid, fhirUser, & launch/patient was granted + scope_subset = scopes - ['openid', 'fhirUser', 'launch/patient'] + assert scope_subset.length == scopes.length - 3, + 'openid, fhirUser, & launch/patient scopes must be supported. Received scopes: ' \ + "#{scope_subset.join(', ')}." + + granted_patient_level_resource_types = [] + granted_user_level_resource_types = [] + + scope_subset.each do |scope| + scope_pieces = scope.split('/') + next unless scope_pieces.length == 2 + + scope_type, resource_scope = scope_pieces + next unless %w[patient user].include?(scope_type) + + resource_scope_parts = resource_scope.split('.') + next unless resource_scope_parts.length == 2 + + resource_type, access_level = resource_scope_parts + next unless access_level =~ access_level_regex + + if scope_type == 'patient' + granted_patient_level_resource_types << resource_type + else + granted_user_level_resource_types << resource_type + end + end + + # Check if the required patient and user level scopes are granted + missing_patient_level_resource_types = patient_compartment_resource_types - granted_patient_level_resource_types + missing_patient_level_resource_types = [] if granted_patient_level_resource_types.include?('*') + + assert missing_patient_level_resource_types.empty?, + "Requested patient-level scopes #{missing_patient_level_resource_types.join(', ')} " \ + 'were not granted by authorization server.' + + missing_user_level_resource_types = patient_compartment_resource_types - granted_user_level_resource_types + missing_user_level_resource_types = [] if granted_user_level_resource_types.include?('*') + assert missing_user_level_resource_types.empty?, + "Requested user-level scopes #{missing_user_level_resource_types.join(', ')} " \ + 'were not granted by authorization server.' + end + run do skip_if request.status != 200, 'Token exchange was unsuccessful' [ @@ -44,8 +105,13 @@ def required_scopes scopes = metadata[:scopes].split received_or_requested = metadata[:received_or_requested] missing_scopes = required_scopes - scopes - assert missing_scopes.empty?, - "Required scopes were not #{received_or_requested}: #{missing_scopes.join(', ')}" + + if received_or_requested == 'requested' + assert missing_scopes.empty?, + "Required scopes were not #{received_or_requested}: #{missing_scopes.join(', ')}" + else + received_scope_test(scopes) + end end end end diff --git a/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch_group.rb b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch_group.rb index 31bd89a7..1e62b8e3 100644 --- a/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch_group.rb +++ b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch_group.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require_relative 'c4bb_smart_launch/well_known_capabilities_test' -require_relative 'c4bb_smart_launch/well_known_supported_scopes_test' require_relative 'c4bb_smart_launch/smart_scopes_test' module CarinForBlueButtonTestKit @@ -52,28 +51,6 @@ class C4BBSMARTLaunchGroup < Inferno::TestGroup } ) end - - test from: :c4bb_v200_wellknown_supported_scopes do - config( - options: { - required_scopes: %w[ - openid - fhirUser - launch/patient - patient/ExplanationOfBenefit.read - patient/Coverage.read - patient/Patient.read - patient/Organization.read - patient/Practitioner.read - user/ExplanationOfBenefit.read - user/Coverage.read - user/Patient.read - user/Organization.read - user/Practitioner.read - ] - } - ) - end end group from: :smart_standalone_launch do From 6791befaf98ee042c45339457617999e4836966e Mon Sep 17 00:00:00 2001 From: Vanessa Fotso Date: Mon, 6 Nov 2023 03:19:38 -0500 Subject: [PATCH 9/9] updated unit test for smart scope test Signed-off-by: Vanessa Fotso --- .../v200_smart_scopes_test_spec.rb | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/spec/carin_for_blue_button/v200_smart_scopes_test_spec.rb b/spec/carin_for_blue_button/v200_smart_scopes_test_spec.rb index 4c718183..b2895929 100644 --- a/spec/carin_for_blue_button/v200_smart_scopes_test_spec.rb +++ b/spec/carin_for_blue_button/v200_smart_scopes_test_spec.rb @@ -41,28 +41,36 @@ def run(runnable, inputs = {}) allow_any_instance_of(test).to receive(:required_scopes).and_return(required_scopes) end - context 'with received scopes' do - let(:requested_scopes) { required_scopes.join(' ') } - - it 'passes if the received scopes grant access to all required resource types' do - result = run(test, requested_scopes:, received_scopes: required_scopes.join(' ')) + context 'All required scopes are not requested' do + it 'fails if a required scope was not requested' do + result = run(test, requested_scopes: 'online_access launch', + received_scopes: 'launch') - expect(result.result).to eq('pass') + expect(result.result).to eq('fail') + expect(result.result_message).to include('Required scopes were not requested: ') end + end - it 'fails if the received scopes do not grant access to all required resource types' do - result = run(test, requested_scopes:, received_scopes: 'patient/Patient.read') + context 'All required scopes requested' do + it 'fails if all the required scopes were not granted' do + result = run(test, requested_scopes: required_scopes.join(' '), + received_scopes: 'patient/*.*') expect(result.result).to eq('fail') end - end - context 'with requested scopes' do - it 'fails if a required scope was not requested' do - result = run(test, requested_scopes: 'online_access launch') + it 'passes if the granted scopes are the super set of the required requested scopes' do + result = run(test, requested_scopes: required_scopes.join(' '), + received_scopes: 'patient/*.* user/*.* openid fhirUser launch/patient') - expect(result.result).to eq('fail') - expect(result.result_message).to include('Required scopes were not requested: ') + expect(result.result).to eq('pass') + end + + it 'passes if the granted scopes are the exact required requested scopes' do + result = run(test, requested_scopes: required_scopes.join(' '), + received_scopes: required_scopes.join(' ')) + + expect(result.result).to eq('pass') end end end