Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

FI-2909: Add scope selection tests #539

Merged
merged 18 commits into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions config/presets/g10_reference_server_preset.json
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,50 @@
"description": "Client Secret provided during registration of Inferno as an EHR launch application",
"value": "SAMPLE_CONFIDENTIAL_CLIENT_SECRET"
},
{
"name": "granular_scope_selection_client_auth_type",
"value": "confidential_symmetric",
"_title": "Client Authentication Method",
"_type": "radio",
"_options": {
"list_options": [
{
"label": "Public",
"value": "public"
},
{
"label": "Confidential Symmetric",
"value": "confidential_symmetric"
},
{
"label": "Confidential Asymmetric",
"value": "confidential_asymmetric"
}
]
}
},
{
"name": "granular_scope_selection_v2_client_id",
"value": "SAMPLE_CONFIDENTIAL_CLIENT_ID",
"_title": "Granular Scope Selection w/v2 Scopes Client ID",
"_description": "Client ID provided during registration of Inferno as a standalone application",
"_type": "text"
},
{
"name": "granular_scope_selection_v2_requested_scopes",
"value": "launch/patient openid fhirUser offline_access patient/Condition.rs patient/Observation.rs patient/Patient.rs",
"_title": "Granular Scope Selection v2 Scopes",
"_description": "OAuth 2.0 scope provided by system to enable all required functionality",
"_type": "textarea"
},
{
"name": "granular_scope_selection_v2_client_secret",
"value": "SAMPLE_CONFIDENTIAL_CLIENT_SECRET",
"_title": "Granular Scope Selection w/v2 Scopes Client Secret",
"_description": "Client Secret provided during registration of Inferno as a standalone application. Only for clients using confidential symmetric authentication.",
"_type": "text",
"_optional": true
},
{
"name": "token_revocation_attestation",
"type": "radio",
Expand Down
16 changes: 10 additions & 6 deletions lib/onc_certification_g10_test_kit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,18 @@
require_relative 'onc_certification_g10_test_kit/single_patient_us_core_6_api_group'
require_relative 'onc_certification_g10_test_kit/smart_app_launch_invalid_aud_group'
require_relative 'onc_certification_g10_test_kit/smart_asymmetric_launch_group'
require_relative 'onc_certification_g10_test_kit/smart_ehr_patient_launch_group'
require_relative 'onc_certification_g10_test_kit/smart_ehr_patient_launch_group_stu2'
require_relative 'onc_certification_g10_test_kit/smart_ehr_practitioner_app_group'
require_relative 'onc_certification_g10_test_kit/smart_fine_grained_scopes_group'
require_relative 'onc_certification_g10_test_kit/smart_invalid_pkce_group'
require_relative 'onc_certification_g10_test_kit/smart_granular_scope_selection_group'
require_relative 'onc_certification_g10_test_kit/smart_invalid_token_group'
require_relative 'onc_certification_g10_test_kit/smart_invalid_token_group_stu2'
require_relative 'onc_certification_g10_test_kit/smart_invalid_pkce_group'
require_relative 'onc_certification_g10_test_kit/smart_limited_app_group'
require_relative 'onc_certification_g10_test_kit/smart_standalone_patient_app_group'
require_relative 'onc_certification_g10_test_kit/smart_public_standalone_launch_group'
require_relative 'onc_certification_g10_test_kit/smart_public_standalone_launch_group_stu2'
require_relative 'onc_certification_g10_test_kit/smart_standalone_patient_app_group'
require_relative 'onc_certification_g10_test_kit/smart_ehr_patient_launch_group'
require_relative 'onc_certification_g10_test_kit/smart_ehr_patient_launch_group_stu2'
require_relative 'onc_certification_g10_test_kit/smart_ehr_practitioner_app_group'
require_relative 'onc_certification_g10_test_kit/smart_fine_grained_scopes_group'
require_relative 'onc_certification_g10_test_kit/smart_v1_scopes_group'
require_relative 'onc_certification_g10_test_kit/terminology_binding_validator'
require_relative 'onc_certification_g10_test_kit/token_introspection_group'
Expand Down Expand Up @@ -387,6 +388,9 @@ def self.well_known_route_handler
group from: :g10_smart_fine_grained_scopes,
required_suite_options: G10Options::SMART_2_REQUIREMENT.merge(G10Options::US_CORE_6_REQUIREMENT),
exclude_optional: true

group from: :g10_smart_granular_scope_selection,
required_suite_options: G10Options::SMART_2_REQUIREMENT.merge(G10Options::US_CORE_6_REQUIREMENT)
end

group from: :g10_visual_inspection_and_attestations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,7 @@ procedure:
- 9.14.1.1.2.08
- 9.14.2.1.2.08
- 9.10.18
- 9.15.2.05
inferno_notes: |
This step refers to only the receipt of these scopes, which is covered in
Inferno in one step in each the EHR and Standalone launch cases. However,
Expand Down
37 changes: 26 additions & 11 deletions lib/onc_certification_g10_test_kit/short_id_map.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2298,20 +2298,35 @@ g10_certification-Group06-g10_smart_fine_grained_scopes-Group02-us_core_v610_obs
: 9.14.2.3.03
? g10_certification-Group06-g10_smart_fine_grained_scopes-Group02-us_core_v610_observation_granular_scope_2_group-us_core_v610_Observation_granular_scope_read_test
: 9.14.2.3.04
g10_certification-Group06-g10_smart_granular_scope_selection: '9.15'
g10_certification-Group06-g10_smart_granular_scope_selection-smart_discovery_stu2: 9.15.1
g10_certification-Group06-g10_smart_granular_scope_selection-smart_discovery_stu2-well_known_endpoint: 9.15.1.01
g10_certification-Group06-g10_smart_granular_scope_selection-smart_discovery_stu2-well_known_capabilities_stu2: 9.15.1.02
g10_certification-Group06-g10_smart_granular_scope_selection-g10_granular_scope_selection_v2_scopes: 9.15.2
g10_certification-Group06-g10_smart_granular_scope_selection-g10_granular_scope_selection_v2_scopes-standalone_auth_tls: 9.15.2.01
g10_certification-Group06-g10_smart_granular_scope_selection-g10_granular_scope_selection_v2_scopes-smart_app_redirect_stu2: 9.15.2.02
g10_certification-Group06-g10_smart_granular_scope_selection-g10_granular_scope_selection_v2_scopes-smart_code_received: 9.15.2.03
g10_certification-Group06-g10_smart_granular_scope_selection-g10_granular_scope_selection_v2_scopes-standalone_token_tls: 9.15.2.04
g10_certification-Group06-g10_smart_granular_scope_selection-g10_granular_scope_selection_v2_scopes-smart_token_exchange: 9.15.2.05
g10_certification-Group06-g10_smart_granular_scope_selection-g10_granular_scope_selection_v2_scopes-smart_token_response_body: 9.15.2.06
g10_certification-Group06-g10_smart_granular_scope_selection-g10_granular_scope_selection_v2_scopes-smart_token_response_headers: 9.15.2.07
g10_certification-Group06-g10_smart_granular_scope_selection-g10_granular_scope_selection_v2_scopes-g10_smart_scopes: 9.15.2.08
? g10_certification-Group06-g10_smart_granular_scope_selection-g10_granular_scope_selection_v2_scopes-g10_smart_granular_scope_selection
: 9.15.2.09
g10_certification-g10_visual_inspection_and_attestations: '11'
g10_certification-g10_visual_inspection_and_attestations-Test01: '11.01'
g10_certification-g10_visual_inspection_and_attestations-Test02: '11.02'
g10_certification-g10_visual_inspection_and_attestations-Test03: '11.03'
g10_certification-g10_visual_inspection_and_attestations-Test04: '11.04'
g10_certification-g10_visual_inspection_and_attestations-Test05: '11.05'
g10_certification-g10_visual_inspection_and_attestations-Test07: '11.06'
g10_certification-g10_visual_inspection_and_attestations-Test08: '11.07'
g10_certification-g10_visual_inspection_and_attestations-Test09: '11.08'
g10_certification-g10_visual_inspection_and_attestations-Test10: '11.09'
g10_certification-g10_visual_inspection_and_attestations-Test11: '11.10'
g10_certification-g10_visual_inspection_and_attestations-Test13: '11.11'
g10_certification-g10_visual_inspection_and_attestations-g10_public_url_attestation: '11.12'
g10_certification-g10_visual_inspection_and_attestations-g10_tls_version_attestation: '11.13'
g10_certification-g10_visual_inspection_and_attestations-g10_refresh_token_refresh_attestation: '11.14'
g10_certification-g10_visual_inspection_and_attestations-g10_bulk_v2_since_attestation: '11.15'
g10_certification-g10_visual_inspection_and_attestations-g10_clinical_test_scope_attestation: '11.16'
g10_certification-g10_visual_inspection_and_attestations-Test07: '11.07'
g10_certification-g10_visual_inspection_and_attestations-Test08: '11.08'
g10_certification-g10_visual_inspection_and_attestations-Test09: '11.09'
g10_certification-g10_visual_inspection_and_attestations-Test10: '11.10'
g10_certification-g10_visual_inspection_and_attestations-Test11: '11.11'
g10_certification-g10_visual_inspection_and_attestations-Test13: '11.13'
g10_certification-g10_visual_inspection_and_attestations-g10_public_url_attestation: '11.14'
g10_certification-g10_visual_inspection_and_attestations-g10_tls_version_attestation: '11.15'
g10_certification-g10_visual_inspection_and_attestations-g10_refresh_token_refresh_attestation: '11.16'
g10_certification-g10_visual_inspection_and_attestations-g10_bulk_v2_since_attestation: '11.17'
g10_certification-g10_visual_inspection_and_attestations-g10_clinical_test_scope_attestation: '11.18'
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
require_relative 'smart_scopes_test'
require_relative 'smart_granular_scope_selection_test'

module ONCCertificationG10TestKit
class SmartGranularScopeSelectionGroup < Inferno::TestGroup
title 'SMART Granular Scope Selection'
short_title 'SMART Granular Scope Selection'
id :g10_smart_granular_scope_selection

description <<~DESCRIPTION
These tests verify that when resource-level scopes are requested for
Condition and Observation resources, the user is presented with the option
of approving sub-resource scopes rather than the resource-level scope.

The tests request v2 resource-level Condition and Observation scopes. In
each instance, the user must unselect the resource-level scopes and
instead approve sub-resource scopes for Condition and Observation. It is
also required that a resource-level Patient scope be granted.

> As part of supporting the SMART App Launch “permission-v2” capability
for the purposes of certification, if an app requests authorization for
a resource level scope for the “Condition” or “Observation” resources,
then for patient authorization purposes a Health IT Module must support
presentation of the required sub-resource scopes to the patient for
authorization. Specifically, sub-resource scopes must be presented for
patient authorization as follows:

> * “Condition” sub-resource scopes “Encounter Diagnosis”, “Problem List”,
and “Health Concern” if a “Condition” resource level scope is
requested
> * “Observation” sub-resource scopes “Clinical Test”, “Laboratory”,
“Social History”, “SDOH”, “Survey”, and “Vital Signs” if an
“Observation” resource level scope is requested
DESCRIPTION

run_as_group

config(
inputs: {
use_pkce: {
default: 'true',
locked: true
},
pkce_code_challenge_method: {
locked: true
},
granular_scope_selection_authorization_method: {
name: :granular_scope_selection_authorization_method,
default: 'get'
},
client_auth_type: {
name: :granular_scope_selection_client_auth_type,
default: 'confidential_asymmetric'
}
}
)

group from: :smart_discovery_stu2

group from: :smart_standalone_launch_stu2 do
id :g10_granular_scope_selection_v2_scopes
title 'Granular Scope Selection with v2 Scopes'

config(
inputs: {
client_id: {
name: :granular_scope_selection_v2_client_id,
title: 'Granular Scope Selection w/v2 Scopes Client ID'
},
client_secret: {
name: :granular_scope_selection_v2_client_secret,
title: 'Granular Scope Selection w/v2 Scopes Client Secret',
default: nil,
optional: true
},
requested_scopes: {
name: :granular_scope_selection_v2_requested_scopes,
title: 'Granular Scope Selection v2 Scopes',
default: %(
launch/patient openid fhirUser offline_access patient/Condition.rs
patient/Observation.rs patient/Patient.rs
).gsub(/\s{2,}/, ' ').strip
},
received_scopes: { name: :granular_scope_selection_v2_received_scopes }
},
outputs: {
requested_scopes: { name: :granular_scope_selection_v2_requested_scopes },
received_scopes: { name: :granular_scope_selection_v2_received_scopes }
},
options: {
redirect_message_proc: proc do |auth_url|
%(
### #{self.class.parent&.parent&.title}

[Follow this link to authorize with the SMART server](#{auth_url}).

Tests will resume once Inferno receives a request at
`#{config.options[:redirect_uri]}` with a state of `#{state}`.
)
end
}
)

test from: :g10_smart_scopes do
config(
options: {
scope_version: :v2,
required_scope_type: 'patient',
required_scopes: ['openid', 'fhirUser', 'launch/patient', 'offline_access']
}
)

def patient_compartment_resource_types
['Patient', 'Condition', 'Observation']
end
end

test from: :g10_smart_granular_scope_selection
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
module ONCCertificationG10TestKit
class SMARTGranularScopeSelectionTest < Inferno::Test
title 'Granular Scope Selection'
description %(
This test verifies that granular scopes have been issued for Condition and
Observation resources, and that a v2 read scope has been issued for the
Patient resource.
)
id :g10_smart_granular_scope_selection
input :requested_scopes, :received_scopes

def resources_with_granular_scopes
['Condition', 'Observation']
end

def resource_level_scope_regex(resource_type)
/#{resource_type}\.(\*|read|c?ru?d?s?)\z/
end

def v2_resource_level_scope_regex(resource_type)
/#{resource_type}\.(\*|c?ru?d?s?)\z/
end

def granular_scope_regex(resource_type)
/#{resource_type}\.(\*|c?ru?d?s?)\?.+=.+/
end

run do
assert requested_scopes.present?
requested_scopes = self.requested_scopes.split
(resources_with_granular_scopes + ['Patient']).each do |resource_type|
assert requested_scopes.any? { |scope| scope.match(resource_level_scope_regex(resource_type)) },
"No resource-level scope was requested for #{resource_type}"

granular_scope = requested_scopes.find { |scope| scope.match(granular_scope_regex(resource_type)) }
assert granular_scope.nil?, "Granular scope was requested: #{granular_scope}"
end

assert received_scopes.present?
received_scopes = self.received_scopes.split

resources_with_granular_scopes.each do |resource_type|
resource_level_scope = received_scopes.find { |scope| scope.match?(resource_level_scope_regex(resource_type)) }
assert resource_level_scope.nil?, "Resource-level scope was granted: #{resource_level_scope}"
assert received_scopes.any? { |scope| scope.match?(granular_scope_regex(resource_type)) },
"No granular scopes were granted for #{resource_type}"
end

assert received_scopes.any? { |scope| scope.match?(v2_resource_level_scope_regex('Patient')) },
'No v2 resource-level scope was granted for Patient'
end
end
end
Binary file modified onc_certification_g10_matrix.xlsx
Binary file not shown.
Loading
Loading