smart_app_launch_test_kit 0.6.1 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/config/presets/SMART_RunClientAgainstServer.json.erb +58 -10
- data/config/presets/SMART_RunServerAgainstClient_ConfidentialAsymmetric.json.erb +183 -0
- data/config/presets/SMART_RunServerAgainstClient_ConfidentialSymmetric.json.erb +157 -0
- data/config/presets/SMART_RunServerAgainstClient_Public.json.erb +155 -0
- data/lib/smart_app_launch/backend_services_invalid_client_assertion_test.rb +1 -1
- data/lib/smart_app_launch/backend_services_invalid_jwt_test.rb +1 -1
- data/lib/smart_app_launch/client_stu2_2_suite.rb +60 -19
- data/lib/smart_app_launch/client_suite/access_alca_interaction_test.rb +75 -0
- data/lib/smart_app_launch/client_suite/access_alcs_interaction_test.rb +75 -0
- data/lib/smart_app_launch/client_suite/access_alp_interaction_test.rb +75 -0
- data/lib/smart_app_launch/client_suite/access_bsca_interaction_test.rb +46 -0
- data/lib/smart_app_launch/client_suite/access_group.rb +85 -0
- data/lib/smart_app_launch/client_suite/authentication_verification.rb +86 -0
- data/lib/smart_app_launch/client_suite/authorization_request_verification_test.rb +108 -0
- data/lib/smart_app_launch/client_suite/client_descriptions.rb +114 -0
- data/lib/smart_app_launch/client_suite/client_options.rb +35 -0
- data/lib/smart_app_launch/client_suite/oidc_jwks.json +32 -0
- data/lib/smart_app_launch/client_suite/oidc_jwks.rb +27 -0
- data/lib/smart_app_launch/client_suite/registration_alca_group.rb +15 -0
- data/lib/smart_app_launch/client_suite/registration_alca_verification_test.rb +57 -0
- data/lib/smart_app_launch/client_suite/registration_alcs_group.rb +15 -0
- data/lib/smart_app_launch/client_suite/registration_alcs_verification_test.rb +56 -0
- data/lib/smart_app_launch/client_suite/registration_alp_group.rb +16 -0
- data/lib/smart_app_launch/client_suite/registration_alp_verification_test.rb +50 -0
- data/lib/smart_app_launch/client_suite/registration_bsca_group.rb +15 -0
- data/lib/smart_app_launch/client_suite/registration_bsca_verification_test.rb +40 -0
- data/lib/smart_app_launch/client_suite/registration_verification.rb +58 -0
- data/lib/smart_app_launch/client_suite/token_request_alca_verification_test.rb +53 -0
- data/lib/smart_app_launch/client_suite/token_request_alcs_verification_test.rb +53 -0
- data/lib/smart_app_launch/client_suite/token_request_alp_verification_test.rb +48 -0
- data/lib/smart_app_launch/client_suite/token_request_bsca_verification_test.rb +53 -0
- data/lib/smart_app_launch/client_suite/token_request_verification.rb +116 -0
- data/lib/smart_app_launch/client_suite/{client_token_use_verification_test.rb → token_use_verification_test.rb} +1 -8
- data/lib/smart_app_launch/docs/smart_stu2_2_client_suite_description.md +128 -41
- data/lib/smart_app_launch/endpoints/echoing_fhir_responder_endpoint.rb +96 -0
- data/lib/smart_app_launch/endpoints/mock_smart_server/authorization_endpoint.rb +27 -0
- data/lib/smart_app_launch/endpoints/mock_smart_server/introspection_endpoint.rb +33 -0
- data/lib/smart_app_launch/endpoints/mock_smart_server/smart_authorization_response_creation.rb +30 -0
- data/lib/smart_app_launch/endpoints/mock_smart_server/smart_introspection_response_creation.rb +46 -0
- data/lib/smart_app_launch/endpoints/mock_smart_server/smart_token_response_creation.rb +250 -0
- data/lib/smart_app_launch/endpoints/mock_smart_server/token_endpoint.rb +58 -0
- data/lib/smart_app_launch/endpoints/mock_smart_server.rb +128 -67
- data/lib/smart_app_launch/metadata.rb +19 -14
- data/lib/smart_app_launch/tags.rb +9 -1
- data/lib/smart_app_launch/token_payload_validation.rb +2 -2
- data/lib/smart_app_launch/urls.rb +12 -0
- data/lib/smart_app_launch/version.rb +2 -2
- metadata +38 -11
- data/config/presets/SMART_RunServerAgainstClient.json.erb +0 -42
- data/lib/smart_app_launch/client_suite/client_access_group.rb +0 -26
- data/lib/smart_app_launch/client_suite/client_access_interaction_test.rb +0 -64
- data/lib/smart_app_launch/client_suite/client_registration_group.rb +0 -15
- data/lib/smart_app_launch/client_suite/client_registration_verification_test.rb +0 -52
- data/lib/smart_app_launch/client_suite/client_token_request_verification_test.rb +0 -146
- data/lib/smart_app_launch/endpoints/echoing_fhir_responder.rb +0 -52
- data/lib/smart_app_launch/endpoints/mock_smart_server/token.rb +0 -27
@@ -0,0 +1,114 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rack/utils'
|
4
|
+
|
5
|
+
module SMARTAppLaunch
|
6
|
+
RE_RUN_REGISTRATION_SUFFIX =
|
7
|
+
'Create a new session and re-run the Client Registration group if you need to change this value.'
|
8
|
+
INPUT_CLIENT_ID_DESCRIPTION =
|
9
|
+
'Testers may provide a specific value for Inferno to assign as the client id. If no value is provided, ' \
|
10
|
+
'the Inferno session id will be used.'
|
11
|
+
INPUT_CLIENT_ID_DESCRIPTION_LOCKED =
|
12
|
+
"The registered Client Id for use in obtaining access tokens. #{RE_RUN_REGISTRATION_SUFFIX}".freeze
|
13
|
+
INPUT_SMART_LAUNCH_URLS_DESCRIPTION =
|
14
|
+
'If the client app supports EHR launch, a comma-delimited list of one or more URLs that Inferno can ' \
|
15
|
+
'use to launch the app.'
|
16
|
+
INPUT_SMART_LAUNCH_URLS_DESCRIPTION_LOCKED =
|
17
|
+
'Registered Launch URLs in the form of a comma-separated list of zero or more URLs. If present, Inferno ' \
|
18
|
+
"will provide an option to use each to launch the app. #{RE_RUN_REGISTRATION_SUFFIX}".freeze
|
19
|
+
INPUT_SMART_REDIRECT_URIS_DESCRIPTION =
|
20
|
+
'A comma-separated list of one or more URIs that the app will sepcify as the target of the redirect for ' \
|
21
|
+
'Inferno to use when providing the authorization code.'
|
22
|
+
INPUT_SMART_REDIRECT_URIS_DESCRIPTION_LOCKED =
|
23
|
+
'Registered Redirect URIs in the form of a comma-separated list of one or more URIs. Redirect URIs ' \
|
24
|
+
"specified in authorization requests must come from this list. #{RE_RUN_REGISTRATION_SUFFIX}".freeze
|
25
|
+
INPUT_CLIENT_SECRET_DESCRIPTION =
|
26
|
+
'Provide the client secret that the confidential symmetric client will send with token requests ' \
|
27
|
+
'to authenticate the client to Inferno.'
|
28
|
+
INPUT_CLIENT_SECRET_DESCRIPTION_LOCKED =
|
29
|
+
'The registered client secret that will be provided during token requests to authenticate the client ' \
|
30
|
+
"to Inferno. #{RE_RUN_REGISTRATION_SUFFIX}".freeze
|
31
|
+
INPUT_CLIENT_JWKS_DESCRIPTION =
|
32
|
+
'The SMART client\'s JSON Web Key Set including the key(s) Inferno will need to verify signatures ' \
|
33
|
+
'on token requests made by the client. May be provided as either a publicly accessible url containing ' \
|
34
|
+
'the JWKS, or the raw JWKS JSON.'
|
35
|
+
INPUT_CLIENT_JWKS_DESCRIPTION_LOCKED =
|
36
|
+
'The SMART client\'s JSON Web Key Set in the form of either a publicly accessible url containing the ' \
|
37
|
+
'JWKS, or the raw JWKS JSON. Must include the key(s) Inferno will need to verify signatures on token ' \
|
38
|
+
"requests made by the client. #{RE_RUN_REGISTRATION_SUFFIX}".freeze
|
39
|
+
|
40
|
+
INPUT_LAUNCH_CONTEXT_DESCRIPTION =
|
41
|
+
'Launch context details to be included in access token responses, specified as a JSON array. If provided, ' \
|
42
|
+
'the contents will be merged into Inferno\'s token responses.'
|
43
|
+
INPUT_FHIR_USER_RELATIVE_REFERENCE =
|
44
|
+
'A FHIR relative reference (<resource type>/<id>) for the FHIR user record to return when the openid ' \
|
45
|
+
'and fhirUser scopes are requested. Include this resource in the **Available Resources** input so ' \
|
46
|
+
'that it can be accessed via FHIR read.'
|
47
|
+
INPUT_FHIR_READ_RESOURCES_BUNDLE_DESCRIPTION =
|
48
|
+
'Resources to make available in Inferno\'s simulated FHIR server provided as a FHIR bundle. Each entry ' \
|
49
|
+
'must contain a resource with the id element populated. Each instance present will be available for ' \
|
50
|
+
'retrieval from Inferno at the endpoint: <fhir-base>/<resource type>/<instance id>. These will only ' \
|
51
|
+
'be available through the read interaction.'
|
52
|
+
INPUT_ECHOED_FHIR_RESPONSE_DESCRIPTION =
|
53
|
+
'JSON representation of a default FHIR resource for Inferno to echo when a request is made to the ' \
|
54
|
+
'simulated FHIR server. Reads targetting resources in the **Available Resources** input will return ' \
|
55
|
+
'that resource instead of this. Otherwise, the content here will be echoed back exactly and no check ' \
|
56
|
+
'will be made that it is appropriate for the request made. If nothing is provided, an OperationOutcome ' \
|
57
|
+
'indicating nothing to echo will be returned.'
|
58
|
+
|
59
|
+
module ClientWaitDialogDescriptions
|
60
|
+
def access_wait_dialog_backend_services_access_prefix(client_id, fhir_base_url)
|
61
|
+
<<~PREFIX
|
62
|
+
**Access**
|
63
|
+
|
64
|
+
Use the registered client id (#{client_id}) to obtain an access
|
65
|
+
token using SMART Backend Services
|
66
|
+
and use that token to access a FHIR endpoint under the simulated server's base URL:
|
67
|
+
|
68
|
+
`#{fhir_base_url}`
|
69
|
+
|
70
|
+
PREFIX
|
71
|
+
end
|
72
|
+
|
73
|
+
def access_wait_dialog_app_launch_access_prefix(client_id, authentication_approach, fhir_base_url)
|
74
|
+
<<~PREFIX
|
75
|
+
**Launch and Access**
|
76
|
+
|
77
|
+
The app has been registered with Inferno's simulated SMART server as a
|
78
|
+
#{authentication_approach} client with client id `#{client_id}`.
|
79
|
+
|
80
|
+
Perform a standalone launch to connect to Inferno's simulated FHIR server at:
|
81
|
+
|
82
|
+
`#{fhir_base_url}`
|
83
|
+
|
84
|
+
PREFIX
|
85
|
+
end
|
86
|
+
|
87
|
+
def access_wait_dialog_ehr_launch_instructions(smart_launch_urls, fhir_base_url)
|
88
|
+
if smart_launch_urls.present?
|
89
|
+
launch_key = SecureRandom.hex(32)
|
90
|
+
output(launch_key:)
|
91
|
+
|
92
|
+
launch_query_string = Rack::Utils.build_query({ iss: fhir_base_url, launch: launch_key })
|
93
|
+
ehr_launch_locations = smart_launch_urls.split(',').map { |launch_url| "#{launch_url}?#{launch_query_string}" }
|
94
|
+
ehr_launch_links_string = ehr_launch_locations.map { |url| "- [launch](#{url})" }.join("\n")
|
95
|
+
|
96
|
+
"\n\nOr open one of the following links in a new tab to perform an EHR launch:\n#{ehr_launch_links_string}\n\n"
|
97
|
+
else
|
98
|
+
''
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def access_wait_dialog_access_response_and_continue_suffix(client_id, resume_pass_url)
|
103
|
+
<<~SUFFIX
|
104
|
+
Inferno will respond to requests with either:
|
105
|
+
- A resource from the Bundle in the **Available Resources** input if the request is a read matching
|
106
|
+
a resource type and id found in the Bundle.
|
107
|
+
- Otherwise, the contents of the **Default FHIR Response** if provided.
|
108
|
+
- Otherwise, an OperationOutcome indicating nothing to echo.
|
109
|
+
|
110
|
+
[Click here](#{resume_pass_url}?token=#{client_id}) once the client has made a data access request.
|
111
|
+
SUFFIX
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../tags'
|
4
|
+
|
5
|
+
module SMARTAppLaunch
|
6
|
+
module SMARTClientOptions
|
7
|
+
module_function
|
8
|
+
|
9
|
+
SMART_APP_LAUNCH_PUBLIC = "#{SMART_TAG},#{AUTHORIZATION_CODE_TAG},#{PUBLIC_TAG}".freeze
|
10
|
+
SMART_APP_LAUNCH_CONFIDENTIAL_SYMMETRIC =
|
11
|
+
"#{SMART_TAG},#{AUTHORIZATION_CODE_TAG},#{CONFIDENTIAL_SYMMETRIC_TAG}".freeze
|
12
|
+
SMART_APP_LAUNCH_CONFIDENTIAL_ASYMMETRIC =
|
13
|
+
"#{SMART_TAG},#{AUTHORIZATION_CODE_TAG},#{CONFIDENTIAL_ASYMMETRIC_TAG}".freeze
|
14
|
+
SMART_BACKEND_SERVICES_CONFIDENTIAL_ASYMMETRIC =
|
15
|
+
"#{SMART_TAG},#{CLIENT_CREDENTIALS_TAG},#{CONFIDENTIAL_ASYMMETRIC_TAG}".freeze
|
16
|
+
|
17
|
+
def oauth_flow(suite_options)
|
18
|
+
if suite_options[:client_type].include?(AUTHORIZATION_CODE_TAG)
|
19
|
+
AUTHORIZATION_CODE_TAG
|
20
|
+
elsif suite_options[:client_type].include?(CLIENT_CREDENTIALS_TAG)
|
21
|
+
CLIENT_CREDENTIALS_TAG
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def smart_authentication_approach(suite_options)
|
26
|
+
if suite_options[:client_type].include?(PUBLIC_TAG)
|
27
|
+
PUBLIC_TAG
|
28
|
+
elsif suite_options[:client_type].include?(CONFIDENTIAL_SYMMETRIC_TAG)
|
29
|
+
CONFIDENTIAL_SYMMETRIC_TAG
|
30
|
+
elsif suite_options[:client_type].include?(CONFIDENTIAL_ASYMMETRIC_TAG)
|
31
|
+
CONFIDENTIAL_ASYMMETRIC_TAG
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
{
|
2
|
+
"keys": [
|
3
|
+
{
|
4
|
+
"kty": "RSA",
|
5
|
+
"use": "sig",
|
6
|
+
"key_ops": [
|
7
|
+
"verify"
|
8
|
+
],
|
9
|
+
"alg": "RS256",
|
10
|
+
"kid": "618e76fc7eb7c3401c7fd47b9af5e94d",
|
11
|
+
"n": "uoYzdYZQDp7yHTfY4XL1PFCFPfijG1rP2dNgMmsqKbm9qpMjiUuXCpdoOXWE838pcu3Ahi6L3dGgGS3Aeys3YrnF6h9uRTD1DHf-UWlOq4iPCzk26a-bBuyVZDVqS7Y5HsyF7i_UvYV1W0EaHSpAF911Vo_W_FVyiSkAZP1yN0ECbbjnSqxhaa44SGw90oh6XnyNBLu7a11aLCURkaRJOKXHj_vf8UyaD-IA9YkvM80St7QF6u6LRVYmCShbpfWRmejh_Swf4zdqcPl-duUsZE-OBysoTq92FaXr605igM30Rdkx8hs-HTwZtjn5hB3XNTiG2Wka1cUXKNyH59u8Yw",
|
12
|
+
"e": "AQAB"
|
13
|
+
},
|
14
|
+
{
|
15
|
+
"kty": "RSA",
|
16
|
+
"use": "sig",
|
17
|
+
"key_ops": [
|
18
|
+
"sign"
|
19
|
+
],
|
20
|
+
"alg": "RS256",
|
21
|
+
"kid": "618e76fc7eb7c3401c7fd47b9af5e94d",
|
22
|
+
"d": "k2SxDVHRuXwIvuX-0EjTWZIXeF0eJuOgE_VgsvbUHpzUMBKNplTBSnFSvvUK1o_J5TPTSzVE-UhJRxxMWghQgAdlShkEPlDtk6jOou6gaBRFVQ0lQ4ys6M_TTZiYIrQgdyIPQ6Uwa4MmtbHAPQPCGhm6O2j27fdnxtNLqIJO2zF9OzF4VP5_v63bbMfwbKECvB_bbcGLmJrq2ClI1iTOw-GawQ3I8sfwf52D3mb-qDJyQwtomNxf7EhvmGBC9u_8JVY48qsCCYWr2KAauID02WtCVepxM4ltKOLJb6BL5QpKmJ8svYLWjSZvNJSdk-EoB9grqechyDOIa2DuU9g-QQ",
|
23
|
+
"n": "uoYzdYZQDp7yHTfY4XL1PFCFPfijG1rP2dNgMmsqKbm9qpMjiUuXCpdoOXWE838pcu3Ahi6L3dGgGS3Aeys3YrnF6h9uRTD1DHf-UWlOq4iPCzk26a-bBuyVZDVqS7Y5HsyF7i_UvYV1W0EaHSpAF911Vo_W_FVyiSkAZP1yN0ECbbjnSqxhaa44SGw90oh6XnyNBLu7a11aLCURkaRJOKXHj_vf8UyaD-IA9YkvM80St7QF6u6LRVYmCShbpfWRmejh_Swf4zdqcPl-duUsZE-OBysoTq92FaXr605igM30Rdkx8hs-HTwZtjn5hB3XNTiG2Wka1cUXKNyH59u8Yw",
|
24
|
+
"e": "AQAB",
|
25
|
+
"p": "x3syoDXXDJne_-aSeHMm3muQ1KfIGewMhklvGNZ6LVTIJWUE5UYiGshABmgn86GfS-uZc_oUo3WcXiCLUlZSBqZFTIyXgy9y979VWA0whPCYcVSLzSGOiUtv3Ys4VGYCmGSuJnyLDp0CNN63Gf_iGpjCRbaPCPdRnyBgfSEvETs",
|
26
|
+
"q": "718z8hC_AQXrhQxvTsCEv7FXSX0Ev10Tjdq94DcsD91g34KTbrF9K9wtPfBUQaUx5Z2rMOTdLxbtHKvP-YQ4YMoQioA9qsclcnwdvKoRcRWim9oBnLa3Iuqttdwc9U2FWEQ4wqv16rsQw7URU4qlYkiVjrHvRJb8Nx_AJsA8Tvk",
|
27
|
+
"dp": "waDGDVj1exfIq-ClgCFWM0N5-9E4nGDR729MVXGqemH3PMUHsX0YEaMa8p0bWpMhStJPy5GNgvTgaUVxtuRvDmFKlvlJAF-IWw7vyl5TIFdhwW_tm5nc_0uoNAW1EcdK8Z2YpWbym6avw54DYUtNr79jo8OGp49ZPPpybkNNqo0",
|
28
|
+
"dq": "ge3mL02Br9d7yKNAQ7niFH75Ry1yB0FJXOVPzUWFSDM84vVoe1wh-k2vzQAHa_50ABO-GXMQz_-cwsRLxj9LrtXfdp43Wtxv6h2OspqJjx1UP05tM5hF_dDua1lH6qqiZ4_YU2qtuDTD28cL2ZHXRWrqqyLQIiXmTzGPxjjwQ1k",
|
29
|
+
"qi": "n3fMznRlCCwuoCq0kv4tL0cravVhcg33rA-CQXlIxgqj3h1yiBJ4p76pHceV6tvYsPyX3YsUKNOBP9HcxuHK3I3kFqMN5Y4C4r8dTE4fbJK9QT_guo_k_GKijNp0NDNCCeKxU78SRYvA527VY-3Z6TUC_K6O-5Pd6aPJN1QDtoA"
|
30
|
+
}
|
31
|
+
]
|
32
|
+
}
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'jwt'
|
2
|
+
|
3
|
+
module SMARTAppLaunch
|
4
|
+
class OIDCJWKS
|
5
|
+
class << self
|
6
|
+
def jwks_json
|
7
|
+
@jwks_json ||=
|
8
|
+
JSON.pretty_generate(
|
9
|
+
{ keys: jwks.export[:keys].select { |key| key[:key_ops]&.include?('verify') } }
|
10
|
+
)
|
11
|
+
end
|
12
|
+
|
13
|
+
def default_jwks_path
|
14
|
+
@default_jwks_path ||= File.join(__dir__, 'oidc_jwks.json')
|
15
|
+
end
|
16
|
+
|
17
|
+
def jwks_path
|
18
|
+
@jwks_path ||=
|
19
|
+
ENV.fetch('SIMULATED_OIDC_JWKS_PATH', default_jwks_path)
|
20
|
+
end
|
21
|
+
|
22
|
+
def jwks
|
23
|
+
@jwks ||= JWT::JWK::Set.new(JSON.parse(File.read(jwks_path)))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require_relative 'registration_alca_verification_test'
|
2
|
+
|
3
|
+
module SMARTAppLaunch
|
4
|
+
class SMARTClientRegistrationAppLaunchConfidentialAsymmetric < Inferno::TestGroup
|
5
|
+
id :smart_client_registration_alca
|
6
|
+
title 'SMART App Launch Confidential Symmetric Client Registration'
|
7
|
+
description %(
|
8
|
+
During these tests, Inferno will verify the provided registration details for the
|
9
|
+
SMART App Launch client using Confidential Asymmetric authentication.
|
10
|
+
)
|
11
|
+
run_as_group
|
12
|
+
|
13
|
+
test from: :smart_client_registration_alca_verification
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require_relative '../tags'
|
2
|
+
require_relative '../endpoints/mock_smart_server'
|
3
|
+
require_relative 'client_options'
|
4
|
+
require_relative 'registration_verification'
|
5
|
+
require_relative 'client_descriptions'
|
6
|
+
|
7
|
+
module SMARTAppLaunch
|
8
|
+
class SMARTClientRegistrationAppLaunchConfidentialAsymmetricVerification < Inferno::Test
|
9
|
+
include RegistrationVerification
|
10
|
+
|
11
|
+
id :smart_client_registration_alca_verification
|
12
|
+
title 'Verify SMART App Launch Confidential Asymmetric Client Registration'
|
13
|
+
description %(
|
14
|
+
During this test, Inferno will verify that the registration details
|
15
|
+
provided for a [SMART App Launch](https://hl7.org/fhir/smart-app-launch/STU2.2/app-launch.html)
|
16
|
+
confidential client using [asymmetric authentication](https://hl7.org/fhir/smart-app-launch/STU2.2/client-confidential-asymmetric.html)
|
17
|
+
are conformant.
|
18
|
+
)
|
19
|
+
|
20
|
+
input :client_id,
|
21
|
+
title: 'Client Id',
|
22
|
+
type: 'text',
|
23
|
+
optional: true,
|
24
|
+
description: INPUT_CLIENT_ID_DESCRIPTION
|
25
|
+
input :smart_launch_urls,
|
26
|
+
title: 'SMART App Launch URL(s)',
|
27
|
+
type: 'textarea',
|
28
|
+
optional: true,
|
29
|
+
description: INPUT_SMART_LAUNCH_URLS_DESCRIPTION
|
30
|
+
input :smart_redirect_uris,
|
31
|
+
title: 'SMART App Launch Redirect URI(s)',
|
32
|
+
type: 'textarea',
|
33
|
+
description: INPUT_SMART_REDIRECT_URIS_DESCRIPTION
|
34
|
+
input :smart_jwk_set,
|
35
|
+
title: 'SMART Confidential Asymmetric JSON Web Key Set (JWKS)',
|
36
|
+
type: 'textarea',
|
37
|
+
description: INPUT_CLIENT_JWKS_DESCRIPTION
|
38
|
+
|
39
|
+
output :client_id
|
40
|
+
output :smart_launch_urls # normalized
|
41
|
+
output :smart_redirect_uris # normalized
|
42
|
+
|
43
|
+
run do
|
44
|
+
if client_id.blank?
|
45
|
+
client_id = test_session_id
|
46
|
+
output(client_id:)
|
47
|
+
end
|
48
|
+
|
49
|
+
verify_registered_launch_urls(smart_launch_urls)
|
50
|
+
verify_registered_redirect_uris(smart_redirect_uris)
|
51
|
+
verify_registered_jwks(smart_jwk_set)
|
52
|
+
|
53
|
+
assert messages.none? { |msg| msg[:type] == 'error' },
|
54
|
+
'Invalid registration details provided. See messages for details'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require_relative 'registration_alcs_verification_test'
|
2
|
+
|
3
|
+
module SMARTAppLaunch
|
4
|
+
class SMARTClientRegistrationAppLaunchConfidentialSymmetric < Inferno::TestGroup
|
5
|
+
id :smart_client_registration_alcs
|
6
|
+
title 'SMART App Launch Confidential Symmetric Client Registration'
|
7
|
+
description %(
|
8
|
+
During these tests, Inferno will verify the provided registration details for the
|
9
|
+
SMART App Launch client using Confidential Symmetric authentication.
|
10
|
+
)
|
11
|
+
run_as_group
|
12
|
+
|
13
|
+
test from: :smart_client_registration_alcs_verification
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require_relative '../tags'
|
2
|
+
require_relative '../endpoints/mock_smart_server'
|
3
|
+
require_relative 'client_options'
|
4
|
+
require_relative 'registration_verification'
|
5
|
+
require_relative 'client_descriptions'
|
6
|
+
|
7
|
+
module SMARTAppLaunch
|
8
|
+
class SMARTClientRegistrationAppLaunchConfidentialSymmetricVerification < Inferno::Test
|
9
|
+
include RegistrationVerification
|
10
|
+
|
11
|
+
id :smart_client_registration_alcs_verification
|
12
|
+
title 'Verify SMART App Launch Confidential Symmetric Client Registration'
|
13
|
+
description %(
|
14
|
+
During this test, Inferno will verify that the registration details
|
15
|
+
provided for a [SMART App Launch](https://hl7.org/fhir/smart-app-launch/STU2.2/app-launch.html)
|
16
|
+
confidential client using [symmetric authentication](https://hl7.org/fhir/smart-app-launch/STU2.2/client-confidential-symmetric.html)
|
17
|
+
are conformant.
|
18
|
+
)
|
19
|
+
|
20
|
+
input :client_id,
|
21
|
+
title: 'Client Id',
|
22
|
+
type: 'text',
|
23
|
+
optional: true,
|
24
|
+
description: INPUT_CLIENT_ID_DESCRIPTION
|
25
|
+
input :smart_launch_urls,
|
26
|
+
title: 'SMART App Launch URL(s)',
|
27
|
+
type: 'textarea',
|
28
|
+
optional: true,
|
29
|
+
description: INPUT_SMART_LAUNCH_URLS_DESCRIPTION
|
30
|
+
input :smart_redirect_uris,
|
31
|
+
title: 'SMART App Launch Redirect URI(s)',
|
32
|
+
type: 'textarea',
|
33
|
+
description: INPUT_SMART_REDIRECT_URIS_DESCRIPTION
|
34
|
+
input :smart_client_secret,
|
35
|
+
title: 'SMART Confidential Symmetric Client Secret',
|
36
|
+
type: 'text',
|
37
|
+
description: INPUT_CLIENT_SECRET_DESCRIPTION
|
38
|
+
|
39
|
+
output :client_id
|
40
|
+
output :smart_launch_urls # normalized
|
41
|
+
output :smart_redirect_uris # normalized
|
42
|
+
|
43
|
+
run do
|
44
|
+
if client_id.blank?
|
45
|
+
client_id = test_session_id
|
46
|
+
output(client_id:)
|
47
|
+
end
|
48
|
+
|
49
|
+
verify_registered_launch_urls(smart_launch_urls)
|
50
|
+
verify_registered_redirect_uris(smart_redirect_uris)
|
51
|
+
|
52
|
+
assert messages.none? { |msg| msg[:type] == 'error' },
|
53
|
+
'Invalid registration details provided. See messages for details'
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
|
2
|
+
require_relative 'registration_alp_verification_test'
|
3
|
+
|
4
|
+
module SMARTAppLaunch
|
5
|
+
class SMARTClientRegistrationAppLaunchPublic < Inferno::TestGroup
|
6
|
+
id :smart_client_registration_alp
|
7
|
+
title 'SMART App Launch Public Client Registration'
|
8
|
+
description %(
|
9
|
+
During these tests, Inferno will verify the provided registration details for the
|
10
|
+
SMART App Launch client using Confidential Symmetric authentication.
|
11
|
+
)
|
12
|
+
run_as_group
|
13
|
+
|
14
|
+
test from: :smart_client_registration_alp_verification
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require_relative '../tags'
|
2
|
+
require_relative '../endpoints/mock_smart_server'
|
3
|
+
require_relative 'client_options'
|
4
|
+
require_relative 'registration_verification'
|
5
|
+
|
6
|
+
module SMARTAppLaunch
|
7
|
+
class SMARTClientRegistrationAppLaunchPublicVerification < Inferno::Test
|
8
|
+
include RegistrationVerification
|
9
|
+
|
10
|
+
id :smart_client_registration_alp_verification
|
11
|
+
title 'Verify SMART App Launch Public Client Registration'
|
12
|
+
description %(
|
13
|
+
During this test, Inferno will verify that the registration details
|
14
|
+
provided for a [SMART App Launch](https://hl7.org/fhir/smart-app-launch/STU2.2/app-launch.html)
|
15
|
+
public client using are conformant.
|
16
|
+
)
|
17
|
+
|
18
|
+
input :client_id,
|
19
|
+
title: 'Client Id',
|
20
|
+
type: 'text',
|
21
|
+
optional: true,
|
22
|
+
description: INPUT_CLIENT_ID_DESCRIPTION
|
23
|
+
input :smart_launch_urls,
|
24
|
+
title: 'SMART App Launch URL(s)',
|
25
|
+
type: 'textarea',
|
26
|
+
optional: true,
|
27
|
+
description: INPUT_SMART_LAUNCH_URLS_DESCRIPTION
|
28
|
+
input :smart_redirect_uris,
|
29
|
+
title: 'SMART App Launch Redirect URI(s)',
|
30
|
+
type: 'textarea',
|
31
|
+
description: INPUT_SMART_REDIRECT_URIS_DESCRIPTION
|
32
|
+
|
33
|
+
output :client_id
|
34
|
+
output :smart_launch_urls # normalized
|
35
|
+
output :smart_redirect_uris # normalized
|
36
|
+
|
37
|
+
run do
|
38
|
+
if client_id.blank?
|
39
|
+
client_id = test_session_id
|
40
|
+
output(client_id:)
|
41
|
+
end
|
42
|
+
|
43
|
+
verify_registered_launch_urls(smart_launch_urls)
|
44
|
+
verify_registered_redirect_uris(smart_redirect_uris)
|
45
|
+
|
46
|
+
assert messages.none? { |msg| msg[:type] == 'error' },
|
47
|
+
'Invalid registration details provided. See messages for details'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require_relative 'registration_bsca_verification_test'
|
2
|
+
|
3
|
+
module SMARTAppLaunch
|
4
|
+
class SMARTClientRegistrationBackendServicesConfidentialAsymmetric < Inferno::TestGroup
|
5
|
+
id :smart_client_registration_bsca
|
6
|
+
title 'Backend Services Confidential Asymmetric Client Registration'
|
7
|
+
description %(
|
8
|
+
During these tests, Inferno will verify the provided registration details for the
|
9
|
+
SMART Backend Services client using Confidential Asymmetric authentication.
|
10
|
+
)
|
11
|
+
run_as_group
|
12
|
+
|
13
|
+
test from: :smart_client_registration_bsca_verification
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require_relative '../tags'
|
2
|
+
require_relative '../endpoints/mock_smart_server'
|
3
|
+
require_relative 'registration_verification'
|
4
|
+
|
5
|
+
module SMARTAppLaunch
|
6
|
+
class SMARTClientBackendServicesRegistrationVerification < Inferno::Test
|
7
|
+
include RegistrationVerification
|
8
|
+
|
9
|
+
id :smart_client_registration_bsca_verification
|
10
|
+
title 'Verify SMART Backend Services Confidential Asymmetric Client Registration'
|
11
|
+
description %(
|
12
|
+
During this test, Inferno will verify that the registration details
|
13
|
+
provided for a [SMART Backend Services](https://hl7.org/fhir/smart-app-launch/STU2.2/backend-services.html)
|
14
|
+
client using [asymmetric authentication](https://hl7.org/fhir/smart-app-launch/STU2.2/client-confidential-asymmetric.html)
|
15
|
+
are valid.
|
16
|
+
)
|
17
|
+
input :client_id,
|
18
|
+
title: 'Client Id',
|
19
|
+
type: 'text',
|
20
|
+
optional: true,
|
21
|
+
description: INPUT_CLIENT_ID_DESCRIPTION
|
22
|
+
input :smart_jwk_set,
|
23
|
+
title: 'SMART Confidential Asymmetric JSON Web Key Set (JWKS)',
|
24
|
+
type: 'textarea',
|
25
|
+
description: INPUT_CLIENT_JWKS_DESCRIPTION
|
26
|
+
|
27
|
+
output :client_id
|
28
|
+
|
29
|
+
run do
|
30
|
+
if client_id.blank?
|
31
|
+
client_id = test_session_id
|
32
|
+
output(client_id:)
|
33
|
+
end
|
34
|
+
|
35
|
+
verify_registered_jwks(smart_jwk_set)
|
36
|
+
|
37
|
+
assert messages.none? { |msg| msg[:type] == 'error' }, 'Invalid key set provided. See messages for details'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module SMARTAppLaunch
|
2
|
+
module RegistrationVerification
|
3
|
+
def verify_registered_jwks(jwks_input)
|
4
|
+
|
5
|
+
jwks_warnings = []
|
6
|
+
parsed_smart_jwk_set = MockSMARTServer.jwk_set(smart_jwk_set, jwks_warnings)
|
7
|
+
jwks_warnings.each { |warning| add_message('warning', warning) }
|
8
|
+
|
9
|
+
# TODO: add key-specific verification per end of https://build.fhir.org/ig/HL7/smart-app-launch/client-confidential-asymmetric.html#registering-a-client-communicating-public-keys
|
10
|
+
|
11
|
+
unless parsed_smart_jwk_set.length.positive?
|
12
|
+
add_message(
|
13
|
+
'error',
|
14
|
+
'JWKS content for Confidential Asymmetric authentication does not include any valid keys.'
|
15
|
+
)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def verify_registered_launch_urls(launch_urls)
|
20
|
+
return unless launch_urls.present?
|
21
|
+
|
22
|
+
normalized_launch_urls = normalize_urls(launch_urls, 'launch URL')
|
23
|
+
output smart_launch_urls: normalized_launch_urls.join(',').strip
|
24
|
+
end
|
25
|
+
|
26
|
+
def verify_registered_redirect_uris(redirect_uris)
|
27
|
+
return unless redirect_uris.present?
|
28
|
+
|
29
|
+
normalized_redirect_uris = normalize_urls(redirect_uris, 'redirect URI')
|
30
|
+
|
31
|
+
output smart_redirect_uris: normalized_redirect_uris.join(',').strip
|
32
|
+
end
|
33
|
+
|
34
|
+
def normalize_urls(url_list, type_for_error)
|
35
|
+
url_list.split(',').map(&:strip).each_with_object([]) do |url, normalized_urls|
|
36
|
+
next if url.blank?
|
37
|
+
|
38
|
+
parsed_uri =
|
39
|
+
begin
|
40
|
+
URI.parse(url)
|
41
|
+
rescue URI::InvalidURIError
|
42
|
+
add_message('error', "Registered #{type_for_error} '#{url}' is not a valid URI.")
|
43
|
+
nil
|
44
|
+
end
|
45
|
+
next unless parsed_uri.present?
|
46
|
+
unless parsed_uri.scheme == 'https' || parsed_uri.scheme == 'http'
|
47
|
+
add_message('error', "Registered #{type_for_error} '#{url}' is not a valid http address.")
|
48
|
+
next
|
49
|
+
end
|
50
|
+
|
51
|
+
normalized_urls << url
|
52
|
+
unless parsed_uri.scheme == 'https'
|
53
|
+
add_message('error', "Registered #{type_for_error} '#{url}' is not a valid https URI.")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require_relative '../tags'
|
2
|
+
require_relative '../urls'
|
3
|
+
require_relative '../endpoints/mock_smart_server'
|
4
|
+
require_relative 'authentication_verification'
|
5
|
+
require_relative 'client_descriptions'
|
6
|
+
require_relative 'client_options'
|
7
|
+
require_relative 'token_request_verification'
|
8
|
+
|
9
|
+
module SMARTAppLaunch
|
10
|
+
class SMARTClientTokenRequestAppLaunchConfidentialAsymmetricVerification < Inferno::Test
|
11
|
+
include URLs
|
12
|
+
include AuthenticationVerification
|
13
|
+
include TokenRequestVerification
|
14
|
+
|
15
|
+
id :smart_client_token_request_alca_verification
|
16
|
+
title 'Verify SMART Token Requests'
|
17
|
+
description %(
|
18
|
+
Check that SMART token requests are conformant.
|
19
|
+
)
|
20
|
+
|
21
|
+
input :client_id,
|
22
|
+
title: 'Client Id',
|
23
|
+
type: 'text',
|
24
|
+
optional: false,
|
25
|
+
locked: true,
|
26
|
+
description: INPUT_CLIENT_ID_DESCRIPTION_LOCKED
|
27
|
+
input :smart_jwk_set,
|
28
|
+
title: 'JSON Web Key Set (JWKS)',
|
29
|
+
type: 'textarea',
|
30
|
+
locked: true,
|
31
|
+
description: INPUT_CLIENT_JWKS_DESCRIPTION_LOCKED
|
32
|
+
|
33
|
+
output :smart_tokens
|
34
|
+
|
35
|
+
def client_suite_id
|
36
|
+
return config.options[:endpoint_suite_id] if config.options[:endpoint_suite_id].present?
|
37
|
+
|
38
|
+
SMARTAppLaunch::SMARTClientSTU22Suite.id
|
39
|
+
end
|
40
|
+
|
41
|
+
run do
|
42
|
+
load_tagged_requests(TOKEN_TAG, SMART_TAG, AUTHORIZATION_CODE_TAG)
|
43
|
+
skip_if requests.blank?, 'No SMART authorization code token requests made.'
|
44
|
+
load_tagged_requests(TOKEN_TAG, SMART_TAG, REFRESH_TOKEN_TAG) # verify refresh_requests as well
|
45
|
+
|
46
|
+
verify_token_requests(AUTHORIZATION_CODE_TAG, CONFIDENTIAL_ASYMMETRIC_TAG)
|
47
|
+
|
48
|
+
assert messages.none? { |msg|
|
49
|
+
msg[:type] == 'error'
|
50
|
+
}, 'Invalid token requests received. See messages for details.'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require_relative '../tags'
|
2
|
+
require_relative '../urls'
|
3
|
+
require_relative '../endpoints/mock_smart_server'
|
4
|
+
require_relative 'authentication_verification'
|
5
|
+
require_relative 'client_descriptions'
|
6
|
+
require_relative 'client_options'
|
7
|
+
require_relative 'token_request_verification'
|
8
|
+
|
9
|
+
module SMARTAppLaunch
|
10
|
+
class SMARTClientTokenRequestAppLaunchConfidentialSymmetricVerification < Inferno::Test
|
11
|
+
include URLs
|
12
|
+
include AuthenticationVerification
|
13
|
+
include TokenRequestVerification
|
14
|
+
|
15
|
+
id :smart_client_token_request_alcs_verification
|
16
|
+
title 'Verify SMART Token Requests'
|
17
|
+
description %(
|
18
|
+
Check that SMART token requests are conformant.
|
19
|
+
)
|
20
|
+
|
21
|
+
input :client_id,
|
22
|
+
title: 'Client Id',
|
23
|
+
type: 'text',
|
24
|
+
optional: false,
|
25
|
+
locked: true,
|
26
|
+
description: INPUT_CLIENT_ID_DESCRIPTION_LOCKED
|
27
|
+
input :smart_client_secret,
|
28
|
+
title: 'SMART Confidential Symmetric Client Secret',
|
29
|
+
type: 'text',
|
30
|
+
locked: true,
|
31
|
+
description: INPUT_CLIENT_SECRET_DESCRIPTION_LOCKED
|
32
|
+
|
33
|
+
output :smart_tokens
|
34
|
+
|
35
|
+
def client_suite_id
|
36
|
+
return config.options[:endpoint_suite_id] if config.options[:endpoint_suite_id].present?
|
37
|
+
|
38
|
+
SMARTAppLaunch::SMARTClientSTU22Suite.id
|
39
|
+
end
|
40
|
+
|
41
|
+
run do
|
42
|
+
load_tagged_requests(TOKEN_TAG, SMART_TAG, AUTHORIZATION_CODE_TAG)
|
43
|
+
skip_if requests.blank?, 'No SMART authorization code token requests made.'
|
44
|
+
load_tagged_requests(TOKEN_TAG, SMART_TAG, REFRESH_TOKEN_TAG) # verify refresh_requests as well
|
45
|
+
|
46
|
+
verify_token_requests(AUTHORIZATION_CODE_TAG, CONFIDENTIAL_SYMMETRIC_TAG)
|
47
|
+
|
48
|
+
assert messages.none? { |msg|
|
49
|
+
msg[:type] == 'error'
|
50
|
+
}, 'Invalid token requests received. See messages for details.'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|