require_relative 'base_token_refresh_group' require_relative 'patient_context_test' require_relative 'smart_invalid_token_refresh_test' require_relative 'smart_scopes_test' require_relative 'unauthorized_access_test' require_relative 'unrestricted_resource_type_access_group' require_relative 'well_known_capabilities_test' require_relative 'incorrectly_permitted_tls_versions_messages_setup_test' module ONCCertificationG10TestKit class SmartStandalonePatientAppGroup < Inferno::TestGroup title 'Standalone Patient App - Full Access' short_title 'Standalone Patient App' input_instructions %( Register Inferno as a standalone application using the following information: * Redirect URI: `#{SMARTAppLaunch::AppRedirectTest.config.options[:redirect_uri]}` Enter in the appropriate scope to enable patient-level access to all relevant resources. In addition, support for the OpenID Connect (openid fhirUser), refresh tokens (offline_access), and patient context (launch/patient) are required. ) description %( This scenario verifies the ability of a system to perform a single SMART App Launch. Specifically, this scenario performs a Patient Standalone Launch to a SMART on FHIR confidential client with a patient context, refresh token, OpenID Connect (OIDC) identity token, and use the GET HTTP method for code exchange. After launch, a simple Patient resource read is performed on the patient in context. The access token is then refreshed, and the Patient resource is read using the new access token to ensure that the refresh was successful. The authentication information provided by OpenID Connect is decoded and validated, and simple queries are performed to ensure that access is granted to all USCDI data elements. Prior to running the scenario, register Inferno as a confidential client with the following information: * Redirect URI: `#{SMARTAppLaunch::AppRedirectTest.config.options[:redirect_uri]}` The following implementation specifications are relevant to this scenario: * [SMART on FHIR (STU1)](http://www.hl7.org/fhir/smart-app-launch/1.0.0/) * [SMART on FHIR (STU2)](http://hl7.org/fhir/smart-app-launch/STU2) * [OpenID Connect (OIDC)](https://openid.net/specs/openid-connect-core-1_0.html) ) id :g10_smart_standalone_patient_app run_as_group config( inputs: { client_secret: { optional: false, name: :standalone_client_secret } } ) 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 required_suite_options(G10Options::SMART_1_REQUIREMENT) test from: 'g10_smart_well_known_capabilities', config: { options: { required_capabilities: [ 'launch-standalone', 'client-public', 'client-confidential-symmetric', 'sso-openid-connect', 'context-standalone-patient', 'permission-offline', 'permission-patient' ] } } test do required_suite_options(G10Options::US_CORE_7_REQUIREMENT) id :g10_us_core_7_smart_version_check title 'US Core 7 requires SMART App Launch 2.0.0 or above' description %( The [US Core 7 SMART on FHIR Obligations and Capabilities](https://hl7.org/fhir/us/core/STU7/scopes.html) require SMART App Launch 2.0.0 or above, so systems can not certify with US Core 7 and SMART App Launch 1.0.0. The [Test Procedure](https://www.healthit.gov/test-method/standardized-api-patient-and-population-services) also states in **Paragraph (g)(10)(v)(A) – Authentication and authorization for patient and user scopes**: > Note: US Core 7.0.0 must be tested with SMART App Launch 2.0.0 or above. ) run do assert false, 'US Core 7 is not eligible for certification with SMART App Launch 1.0.0. ' \ 'Start a new session with SMART App Launch 2.0.0 or higher.' end end end group from: :smart_discovery_stu2 do required_suite_options(G10Options::SMART_2_REQUIREMENT) test from: 'g10_smart_well_known_capabilities', config: { options: { required_capabilities: [ 'launch-standalone', 'client-public', 'client-confidential-symmetric', 'client-confidential-asymmetric', 'sso-openid-connect', 'context-standalone-patient', 'permission-offline', 'permission-patient', 'authorize-post', 'permission-v2', 'permission-v1' ] } } end group from: :smart_discovery_stu2_2 do # rubocop:disable Naming/VariableNumber required_suite_options(G10Options::SMART_2_2_REQUIREMENT) test from: 'g10_smart_well_known_capabilities', config: { options: { required_capabilities: [ 'launch-standalone', 'client-public', 'client-confidential-symmetric', 'client-confidential-asymmetric', 'sso-openid-connect', 'context-standalone-patient', 'permission-offline', 'permission-patient', 'authorize-post', 'permission-v2', 'permission-v1' ] } } end group from: :smart_standalone_launch do required_suite_options(G10Options::SMART_1_REQUIREMENT) title 'Standalone Launch With Patient Scope' description %( # Background The [Standalone Launch Sequence](http://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 the authorization endpoint so that they may 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](http://hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) ) config( inputs: { requested_scopes: { default: %( launch/patient openid fhirUser offline_access patient/Medication.read patient/AllergyIntolerance.read patient/CarePlan.read patient/CareTeam.read patient/Condition.read patient/Device.read patient/DiagnosticReport.read patient/DocumentReference.read patient/Encounter.read patient/Goal.read patient/Immunization.read patient/Location.read patient/MedicationRequest.read patient/Observation.read patient/Organization.read patient/Patient.read patient/Practitioner.read patient/Procedure.read patient/Provenance.read patient/PractitionerRole.read ).gsub(/\s{2,}/, ' ').strip } } ) test from: :g10_smart_scopes do config( inputs: { requested_scopes: { name: :standalone_requested_scopes }, received_scopes: { name: :standalone_received_scopes } }, options: { scope_version: :v1, required_scope_type: 'patient', required_scopes: ['openid', 'fhirUser', 'launch/patient', 'offline_access'] } ) end test from: :g10_unauthorized_access, config: { inputs: { patient_id: { name: :standalone_patient_id } } } test from: :g10_patient_context, config: { inputs: { patient_id: { name: :standalone_patient_id }, smart_credentials: { name: :standalone_smart_credentials } } } tests[0].config( outputs: { incorrectly_permitted_tls_versions_messages: { name: :auth_incorrectly_permitted_tls_versions_messages } } ) tests[3].config( outputs: { incorrectly_permitted_tls_versions_messages: { name: :token_incorrectly_permitted_tls_versions_messages } } ) end group from: :smart_standalone_launch_stu2, config: { inputs: { use_pkce: { default: 'true', locked: true }, pkce_code_challenge_method: { locked: true }, authorization_method: { name: :standalone_authorization_method, default: 'get', locked: true }, client_auth_type: { locked: true, default: 'confidential_symmetric' } } } do required_suite_options(G10Options::SMART_2_REQUIREMENT) title 'Standalone Launch With Patient Scope' description %( # Background The [Standalone Launch Sequence](http://hl7.org/fhir/smart-app-launch/STU2/app-launch.html#launch-app-standalone-launch) 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 the authorization endpoint so that they may 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](http://hl7.org/fhir/smart-app-launch/STU2/app-launch.html#launch-app-standalone-launch) ) config( inputs: { requested_scopes: { default: %( launch/patient openid fhirUser offline_access patient/Medication.rs patient/AllergyIntolerance.rs patient/CarePlan.rs patient/CareTeam.rs patient/Condition.rs patient/Device.rs patient/DiagnosticReport.rs patient/DocumentReference.rs patient/Encounter.rs patient/Goal.rs patient/Immunization.rs patient/Location.rs patient/MedicationRequest.rs patient/Observation.rs patient/Organization.rs patient/Patient.rs patient/Practitioner.rs patient/Procedure.rs patient/Provenance.rs patient/PractitionerRole.rs ).gsub(/\s{2,}/, ' ').strip } } ) test from: :g10_smart_scopes do config( inputs: { requested_scopes: { name: :standalone_requested_scopes }, received_scopes: { name: :standalone_received_scopes } }, options: { scope_version: :v2, required_scope_type: 'patient', required_scopes: ['openid', 'fhirUser', 'launch/patient', 'offline_access'] } ) end test from: :g10_unauthorized_access, config: { inputs: { patient_id: { name: :standalone_patient_id } } } test from: :g10_patient_context, config: { inputs: { patient_id: { name: :standalone_patient_id }, smart_credentials: { name: :standalone_smart_credentials } } } tests[0].config( outputs: { incorrectly_permitted_tls_versions_messages: { name: :auth_incorrectly_permitted_tls_versions_messages } } ) tests[3].config( outputs: { incorrectly_permitted_tls_versions_messages: { name: :token_incorrectly_permitted_tls_versions_messages } } ) end group from: :smart_standalone_launch_stu2_2, # rubocop:disable Naming/VariableNumber config: { inputs: { use_pkce: { default: 'true', locked: true }, pkce_code_challenge_method: { locked: true }, authorization_method: { name: :standalone_authorization_method, default: 'get', locked: true }, client_auth_type: { locked: true, default: 'confidential_symmetric' } } } do required_suite_options(G10Options::SMART_2_2_REQUIREMENT) title 'Standalone Launch With Patient Scope' description %( # Background The [Standalone Launch Sequence](http://hl7.org/fhir/smart-app-launch/STU2.2/app-launch.html#launch-app-standalone-launch) 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 the authorization endpoint so that they may 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](http://hl7.org/fhir/smart-app-launch/STU2.2/app-launch.html#launch-app-standalone-launch) ) config( inputs: { requested_scopes: { default: %( launch/patient openid fhirUser offline_access patient/Medication.rs patient/AllergyIntolerance.rs patient/CarePlan.rs patient/CareTeam.rs patient/Condition.rs patient/Device.rs patient/DiagnosticReport.rs patient/DocumentReference.rs patient/Encounter.rs patient/Goal.rs patient/Immunization.rs patient/Location.rs patient/MedicationRequest.rs patient/Observation.rs patient/Organization.rs patient/Patient.rs patient/Practitioner.rs patient/Procedure.rs patient/Provenance.rs patient/PractitionerRole.rs ).gsub(/\s{2,}/, ' ').strip } } ) test from: :g10_smart_scopes do config( inputs: { requested_scopes: { name: :standalone_requested_scopes }, received_scopes: { name: :standalone_received_scopes } }, options: { scope_version: :v22, required_scope_type: 'patient', required_scopes: ['openid', 'fhirUser', 'launch/patient', 'offline_access'] } ) end test from: :g10_unauthorized_access, config: { inputs: { patient_id: { name: :standalone_patient_id } } } test from: :g10_patient_context, config: { inputs: { patient_id: { name: :standalone_patient_id }, smart_credentials: { name: :standalone_smart_credentials } } } tests[0].config( outputs: { incorrectly_permitted_tls_versions_messages: { name: :auth_incorrectly_permitted_tls_versions_messages } } ) tests[3].config( outputs: { incorrectly_permitted_tls_versions_messages: { name: :token_incorrectly_permitted_tls_versions_messages } } ) end group from: :smart_openid_connect, required_suite_options: G10Options::SMART_1_REQUIREMENT, config: { inputs: { id_token: { name: :standalone_id_token }, client_id: { name: :standalone_client_id }, requested_scopes: { name: :standalone_requested_scopes }, smart_credentials: { name: :standalone_smart_credentials } } } group from: :smart_openid_connect, required_suite_options: G10Options::SMART_2_REQUIREMENT, id: :smart_openid_connect_stu2, config: { inputs: { id_token: { name: :standalone_id_token }, client_id: { name: :standalone_client_id }, requested_scopes: { name: :standalone_requested_scopes }, smart_credentials: { name: :standalone_smart_credentials } } } group from: :smart_openid_connect_stu2_2, # rubocop:disable Naming/VariableNumber required_suite_options: G10Options::SMART_2_2_REQUIREMENT, config: { inputs: { id_token: { name: :standalone_id_token }, client_id: { name: :standalone_client_id }, requested_scopes: { name: :standalone_requested_scopes }, smart_credentials: { name: :standalone_smart_credentials } } } group from: :g10_token_refresh do id :g10_smart_standalone_token_refresh config( inputs: { refresh_token: { name: :standalone_refresh_token }, client_id: { name: :standalone_client_id }, client_secret: { name: :standalone_client_secret }, received_scopes: { name: :standalone_received_scopes } }, outputs: { refresh_token: { name: :standalone_refresh_token }, received_scopes: { name: :standalone_received_scopes }, access_token: { name: :standalone_access_token }, token_retrieval_time: { name: :standalone_token_retrieval_time }, expires_in: { name: :standalone_expires_in }, smart_credentials: { name: :standalone_smart_credentials } } ) test from: :g10_patient_context do config( inputs: { patient_id: { name: :standalone_patient_id }, smart_credentials: { name: :standalone_smart_credentials } }, options: { refresh_test: true } ) uses_request :token_refresh end test from: :g10_invalid_token_refresh end group from: :g10_unrestricted_resource_type_access, config: { inputs: { received_scopes: { name: :standalone_received_scopes }, patient_id: { name: :standalone_patient_id }, smart_credentials: { name: :standalone_smart_credentials } } } test do id :g10_standalone_credentials_export title 'Set SMART Credentials to Standalone Launch Credentials' input :standalone_smart_credentials, type: :oauth_credentials input :standalone_patient_id output :smart_credentials, :patient_id run do output smart_credentials: standalone_smart_credentials.to_s, patient_id: standalone_patient_id end end test from: :g10_incorrectly_permitted_tls_versions_messages_setup, id: :g10_auth_incorrectly_permitted_tls_versions_messages_setup, config: { inputs: { incorrectly_permitted_tls_versions_messages: { name: :auth_incorrectly_permitted_tls_versions_messages } } } test from: :g10_incorrectly_permitted_tls_versions_messages_setup, id: :g10_token_incorrectly_permitted_tls_versions_messages_setup, config: { inputs: { incorrectly_permitted_tls_versions_messages: { name: :token_incorrectly_permitted_tls_versions_messages } } } end end