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
@@ -5,9 +5,9 @@ module SMARTAppLaunch
|
|
5
5
|
id :smart_app_launch_test_kit
|
6
6
|
title 'SMART App Launch Test Kit'
|
7
7
|
description <<~DESCRIPTION
|
8
|
-
The SMART App Launch Test Kit
|
9
|
-
|
10
|
-
Application Launch Framework Implementation
|
8
|
+
The SMART App Launch Test Kit validates the conformance of authorization server
|
9
|
+
implementations and clients that interact with them to a specified version of the
|
10
|
+
[SMART Application Launch Framework Implementation
|
11
11
|
Guide](http://hl7.org/fhir/smart-app-launch/index.html). This Test Kit also
|
12
12
|
provides Brand Bundle Publisher testing for the User-access Brands and Endpoints
|
13
13
|
specification. This Test Kit supports following versions of the SMART App
|
@@ -25,21 +25,26 @@ module SMARTAppLaunch
|
|
25
25
|
kits for any FHIR-based data exchange.
|
26
26
|
|
27
27
|
To run tests for a SMART App Launch authorization server, select one of the
|
28
|
-
"SMART App Launch" suites. To run tests for a
|
28
|
+
"SMART App Launch" suites. To run tests for a SMART App Launch or backend
|
29
|
+
services client, select the "SMART App Launch STU2.2 Client" suite and choose
|
30
|
+
the type of client. To run tests for a Brand Bundle Publisher, select
|
29
31
|
the "SMART User-access Brands and Endpoints" suite.
|
30
32
|
|
31
33
|
## Status
|
32
34
|
|
33
|
-
The SMART App Launch Test Kit
|
34
|
-
implement the SMART App Launch IG
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
35
|
+
The SMART App Launch Test Kit provides five suites that verify that systems
|
36
|
+
correctly implement different aspects and versions of the SMART App Launch IG.
|
37
|
+
- Three server suites (SMART App Launch STU1, STU2, and STU2.2) verifying that SMART server implementations
|
38
|
+
can provide authorization and/or authentication services to
|
39
|
+
client applications accessing HL7 FHIR APIs. Thes
|
40
|
+
- Standalone Launch
|
41
|
+
- EHR Launch
|
42
|
+
- Backend Services (STU2 and STU2.2 only)
|
43
|
+
- Token Introspection (STU2 and STU2.2 only)
|
44
|
+
- A client suite (SMART App Launch STU2.2 Client) verifying that a SMART STU2.2
|
45
|
+
client can obtain and use an access token using the SMART flows and authentication options.
|
46
|
+
- A suite verifying a server's compliance with the User-access Brands and Endpoints
|
47
|
+
specification.
|
43
48
|
|
44
49
|
See the test descriptions within the test kit for detail on the specific
|
45
50
|
validations performed as part of testing these requirements.
|
@@ -2,6 +2,14 @@
|
|
2
2
|
|
3
3
|
module SMARTAppLaunch
|
4
4
|
TOKEN_TAG = 'token'
|
5
|
-
|
5
|
+
AUTHORIZATION_TAG = 'authorization'
|
6
|
+
INTROSPECTION_TAG = 'introspection'
|
7
|
+
SMART_TAG = 'SMART'
|
6
8
|
ACCESS_TAG = 'access'
|
9
|
+
CLIENT_CREDENTIALS_TAG = 'client_credentials'
|
10
|
+
AUTHORIZATION_CODE_TAG = 'authorization_code'
|
11
|
+
REFRESH_TOKEN_TAG = 'refresh_token'
|
12
|
+
PUBLIC_TAG = 'public'
|
13
|
+
CONFIDENTIAL_SYMMETRIC_TAG = 'confidential_symmetric'
|
14
|
+
CONFIDENTIAL_ASYMMETRIC_TAG = 'confidential_asymmetric'
|
7
15
|
end
|
@@ -78,8 +78,8 @@ module SMARTAppLaunch
|
|
78
78
|
end
|
79
79
|
|
80
80
|
def check_for_missing_scopes(requested_scopes, body)
|
81
|
-
expected_scopes = requested_scopes
|
82
|
-
new_scopes = body['scope']
|
81
|
+
expected_scopes = requested_scopes&.split || []
|
82
|
+
new_scopes = body['scope']&.split || []
|
83
83
|
missing_scopes = expected_scopes - new_scopes
|
84
84
|
|
85
85
|
warning do
|
@@ -6,7 +6,11 @@ module SMARTAppLaunch
|
|
6
6
|
RESUME_FAIL_PATH = '/resume_fail'
|
7
7
|
AUTH_SERVER_PATH = '/auth'
|
8
8
|
SMART_DISCOVERY_PATH = "#{FHIR_PATH}/.well-known/smart-configuration".freeze
|
9
|
+
OIDC_DISCOVERY_PATH = "#{FHIR_PATH}/.well-known/openid-configuration".freeze
|
10
|
+
OIDC_JWKS_PATH = "#{FHIR_PATH}/.well-known/jwks.json".freeze
|
9
11
|
TOKEN_PATH = "#{AUTH_SERVER_PATH}/token".freeze
|
12
|
+
AUTHORIZATION_PATH = "#{AUTH_SERVER_PATH}/authorization".freeze
|
13
|
+
INTROSPECTION_PATH = "#{AUTH_SERVER_PATH}/introspect".freeze
|
10
14
|
|
11
15
|
module URLs
|
12
16
|
def client_base_url
|
@@ -33,6 +37,14 @@ module SMARTAppLaunch
|
|
33
37
|
@client_token_url ||= client_base_url + TOKEN_PATH
|
34
38
|
end
|
35
39
|
|
40
|
+
def client_authorization_url
|
41
|
+
@client_authorization_url ||= client_base_url + AUTHORIZATION_PATH
|
42
|
+
end
|
43
|
+
|
44
|
+
def client_introspection_url
|
45
|
+
@client_introspection_url ||= client_base_url + INTROSPECTION_PATH
|
46
|
+
end
|
47
|
+
|
36
48
|
def client_suite_id
|
37
49
|
SMARTAppLaunch::SMARTClientSTU22Suite.id
|
38
50
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: smart_app_launch_test_kit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stephen MacVicar
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-05-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: inferno_core
|
@@ -146,7 +146,9 @@ extra_rdoc_files: []
|
|
146
146
|
files:
|
147
147
|
- LICENSE
|
148
148
|
- config/presets/SMART_RunClientAgainstServer.json.erb
|
149
|
-
- config/presets/
|
149
|
+
- config/presets/SMART_RunServerAgainstClient_ConfidentialAsymmetric.json.erb
|
150
|
+
- config/presets/SMART_RunServerAgainstClient_ConfidentialSymmetric.json.erb
|
151
|
+
- config/presets/SMART_RunServerAgainstClient_Public.json.erb
|
150
152
|
- config/presets/inferno_reference_server_preset.json
|
151
153
|
- config/presets/inferno_reference_server_stu2_2_preset.json
|
152
154
|
- config/presets/inferno_reference_server_stu2_preset.json
|
@@ -167,12 +169,32 @@ files:
|
|
167
169
|
- lib/smart_app_launch/backend_services_invalid_jwt_test.rb
|
168
170
|
- lib/smart_app_launch/client_assertion_builder.rb
|
169
171
|
- lib/smart_app_launch/client_stu2_2_suite.rb
|
170
|
-
- lib/smart_app_launch/client_suite/
|
171
|
-
- lib/smart_app_launch/client_suite/
|
172
|
-
- lib/smart_app_launch/client_suite/
|
173
|
-
- lib/smart_app_launch/client_suite/
|
174
|
-
- lib/smart_app_launch/client_suite/
|
175
|
-
- lib/smart_app_launch/client_suite/
|
172
|
+
- lib/smart_app_launch/client_suite/access_alca_interaction_test.rb
|
173
|
+
- lib/smart_app_launch/client_suite/access_alcs_interaction_test.rb
|
174
|
+
- lib/smart_app_launch/client_suite/access_alp_interaction_test.rb
|
175
|
+
- lib/smart_app_launch/client_suite/access_bsca_interaction_test.rb
|
176
|
+
- lib/smart_app_launch/client_suite/access_group.rb
|
177
|
+
- lib/smart_app_launch/client_suite/authentication_verification.rb
|
178
|
+
- lib/smart_app_launch/client_suite/authorization_request_verification_test.rb
|
179
|
+
- lib/smart_app_launch/client_suite/client_descriptions.rb
|
180
|
+
- lib/smart_app_launch/client_suite/client_options.rb
|
181
|
+
- lib/smart_app_launch/client_suite/oidc_jwks.json
|
182
|
+
- lib/smart_app_launch/client_suite/oidc_jwks.rb
|
183
|
+
- lib/smart_app_launch/client_suite/registration_alca_group.rb
|
184
|
+
- lib/smart_app_launch/client_suite/registration_alca_verification_test.rb
|
185
|
+
- lib/smart_app_launch/client_suite/registration_alcs_group.rb
|
186
|
+
- lib/smart_app_launch/client_suite/registration_alcs_verification_test.rb
|
187
|
+
- lib/smart_app_launch/client_suite/registration_alp_group.rb
|
188
|
+
- lib/smart_app_launch/client_suite/registration_alp_verification_test.rb
|
189
|
+
- lib/smart_app_launch/client_suite/registration_bsca_group.rb
|
190
|
+
- lib/smart_app_launch/client_suite/registration_bsca_verification_test.rb
|
191
|
+
- lib/smart_app_launch/client_suite/registration_verification.rb
|
192
|
+
- lib/smart_app_launch/client_suite/token_request_alca_verification_test.rb
|
193
|
+
- lib/smart_app_launch/client_suite/token_request_alcs_verification_test.rb
|
194
|
+
- lib/smart_app_launch/client_suite/token_request_alp_verification_test.rb
|
195
|
+
- lib/smart_app_launch/client_suite/token_request_bsca_verification_test.rb
|
196
|
+
- lib/smart_app_launch/client_suite/token_request_verification.rb
|
197
|
+
- lib/smart_app_launch/client_suite/token_use_verification_test.rb
|
176
198
|
- lib/smart_app_launch/code_received_test.rb
|
177
199
|
- lib/smart_app_launch/cors_metadata_request_test.rb
|
178
200
|
- lib/smart_app_launch/cors_openid_fhir_user_claim_test.rb
|
@@ -186,9 +208,14 @@ files:
|
|
186
208
|
- lib/smart_app_launch/ehr_launch_group.rb
|
187
209
|
- lib/smart_app_launch/ehr_launch_group_stu2.rb
|
188
210
|
- lib/smart_app_launch/ehr_launch_group_stu2_2.rb
|
189
|
-
- lib/smart_app_launch/endpoints/
|
211
|
+
- lib/smart_app_launch/endpoints/echoing_fhir_responder_endpoint.rb
|
190
212
|
- lib/smart_app_launch/endpoints/mock_smart_server.rb
|
191
|
-
- lib/smart_app_launch/endpoints/mock_smart_server/
|
213
|
+
- lib/smart_app_launch/endpoints/mock_smart_server/authorization_endpoint.rb
|
214
|
+
- lib/smart_app_launch/endpoints/mock_smart_server/introspection_endpoint.rb
|
215
|
+
- lib/smart_app_launch/endpoints/mock_smart_server/smart_authorization_response_creation.rb
|
216
|
+
- lib/smart_app_launch/endpoints/mock_smart_server/smart_introspection_response_creation.rb
|
217
|
+
- lib/smart_app_launch/endpoints/mock_smart_server/smart_token_response_creation.rb
|
218
|
+
- lib/smart_app_launch/endpoints/mock_smart_server/token_endpoint.rb
|
192
219
|
- lib/smart_app_launch/jwks.rb
|
193
220
|
- lib/smart_app_launch/launch_received_test.rb
|
194
221
|
- lib/smart_app_launch/metadata.rb
|
@@ -1,42 +0,0 @@
|
|
1
|
-
{
|
2
|
-
"title": "Demo: Run Against the SMART Client Suite",
|
3
|
-
"id": "smart_run_server_against_client_v2_2",
|
4
|
-
"test_suite_id": "smart_stu2_2",
|
5
|
-
"inputs": [
|
6
|
-
{
|
7
|
-
"name": "url",
|
8
|
-
"description": "URL of the FHIR endpoint used by SMART applications",
|
9
|
-
"title": "FHIR Endpoint",
|
10
|
-
"type": "text",
|
11
|
-
"value": "<%= Inferno::Application['base_url'] %>/custom/smart_client_stu2_2/fhir"
|
12
|
-
},
|
13
|
-
{
|
14
|
-
"name": "backend_services_smart_auth_info",
|
15
|
-
"options": {
|
16
|
-
"mode": "auth",
|
17
|
-
"components": [
|
18
|
-
{
|
19
|
-
"name": "auth_type",
|
20
|
-
"default": "backend_services",
|
21
|
-
"locked": "true"
|
22
|
-
},
|
23
|
-
{
|
24
|
-
"name": "use_discovery",
|
25
|
-
"locked": true
|
26
|
-
}
|
27
|
-
]
|
28
|
-
},
|
29
|
-
"title": "Backend Services Credentials",
|
30
|
-
"type": "auth_info",
|
31
|
-
"value": {
|
32
|
-
"encryption_algorithm": "ES384",
|
33
|
-
"auth_type": "backend_services",
|
34
|
-
"use_discovery": "true",
|
35
|
-
"token_url": "<%= Inferno::Application['base_url'] %>/custom/smart_client_stu2_2/auth/token",
|
36
|
-
"requested_scopes": "system/*.rs",
|
37
|
-
"client_id": "smart_client_test_demo"
|
38
|
-
},
|
39
|
-
"default": {}
|
40
|
-
}
|
41
|
-
]
|
42
|
-
}
|
@@ -1,26 +0,0 @@
|
|
1
|
-
require_relative 'client_access_interaction_test'
|
2
|
-
require_relative 'client_token_request_verification_test'
|
3
|
-
require_relative 'client_token_use_verification_test'
|
4
|
-
|
5
|
-
module SMARTAppLaunch
|
6
|
-
class SMARTClientAccess < Inferno::TestGroup
|
7
|
-
id :smart_client_access
|
8
|
-
title 'Client Access'
|
9
|
-
description %(
|
10
|
-
During these tests, the client system will access Inferno's simulated
|
11
|
-
FHIR server by requesting an access token and making a FHIR request.
|
12
|
-
Inferno will then verify that any token requests made were conformant
|
13
|
-
and that a token returned from a token request was used on an access request.
|
14
|
-
)
|
15
|
-
|
16
|
-
run_as_group
|
17
|
-
|
18
|
-
input :smart_jwk_set,
|
19
|
-
optional: true,
|
20
|
-
locked: true
|
21
|
-
|
22
|
-
test from: :smart_client_access_interaction
|
23
|
-
test from: :smart_client_token_request_verification
|
24
|
-
test from: :smart_client_token_use_verification
|
25
|
-
end
|
26
|
-
end
|
@@ -1,64 +0,0 @@
|
|
1
|
-
require_relative '../urls'
|
2
|
-
require_relative '../endpoints/mock_smart_server'
|
3
|
-
|
4
|
-
module SMARTAppLaunch
|
5
|
-
class SMARTClientAccessInteraction < Inferno::Test
|
6
|
-
include URLs
|
7
|
-
|
8
|
-
id :smart_client_access_interaction
|
9
|
-
title 'Perform SMART-secured Access'
|
10
|
-
description %(
|
11
|
-
During this test, Inferno will wait for the client to access data
|
12
|
-
using a SMART token obtained during earlier tests.
|
13
|
-
)
|
14
|
-
input :client_id,
|
15
|
-
title: 'Client Id',
|
16
|
-
type: 'text',
|
17
|
-
locked: true,
|
18
|
-
description: %(
|
19
|
-
The registered Client Id for use in obtaining access tokens.
|
20
|
-
Create a new session if you need to change this value.
|
21
|
-
)
|
22
|
-
input :smart_jwk_set,
|
23
|
-
title: 'JSON Web Key Set (JWKS)',
|
24
|
-
type: 'textarea',
|
25
|
-
optional: true,
|
26
|
-
locked: true,
|
27
|
-
description: %(
|
28
|
-
The SMART client's JSON Web Key Set in the form of either a publicly accessible url
|
29
|
-
containing the JWKS, or the raw JWKS JSON. Must include the key(s) Inferno will need to
|
30
|
-
verify signatures on token requests made by the client.
|
31
|
-
Create a new session if you need to change this value.
|
32
|
-
)
|
33
|
-
input :echoed_fhir_response,
|
34
|
-
title: 'FHIR Response to Echo',
|
35
|
-
type: 'textarea',
|
36
|
-
description: %(
|
37
|
-
JSON representation of a FHIR resource for Inferno to echo when a request
|
38
|
-
is made to the simulated FHIR server. The provided content will be echoed
|
39
|
-
back exactly and no check will be made that it is appropriate for the request
|
40
|
-
made. If nothing is provided, an OperationOutcome will be returned.
|
41
|
-
),
|
42
|
-
optional: true
|
43
|
-
|
44
|
-
run do
|
45
|
-
wait(
|
46
|
-
identifier: client_id,
|
47
|
-
message: %(
|
48
|
-
**Access**
|
49
|
-
|
50
|
-
Use the registered client id (#{client_id}) to obtain an access
|
51
|
-
token using SMART Backend Services
|
52
|
-
and use that token to access a FHIR endpoint under the simulated server's base URL
|
53
|
-
|
54
|
-
`#{client_fhir_base_url}`
|
55
|
-
|
56
|
-
Inferno will echo the response provided in the **FHIR Response to Echo** input.
|
57
|
-
|
58
|
-
[Click here](#{client_resume_pass_url}?token=#{client_id}) once you performed
|
59
|
-
the access.
|
60
|
-
)
|
61
|
-
)
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
@@ -1,15 +0,0 @@
|
|
1
|
-
require_relative 'client_registration_verification_test'
|
2
|
-
|
3
|
-
module SMARTAppLaunch
|
4
|
-
class SMARTClientRegistration < Inferno::TestGroup
|
5
|
-
id :smart_client_registration
|
6
|
-
title 'Client Registration'
|
7
|
-
description %(
|
8
|
-
During these tests, Inferno will verify the registration details provided as inputs,
|
9
|
-
including the client's JSON Web Key Set.
|
10
|
-
)
|
11
|
-
run_as_group
|
12
|
-
|
13
|
-
test from: :smart_client_registration_verification
|
14
|
-
end
|
15
|
-
end
|
@@ -1,52 +0,0 @@
|
|
1
|
-
require_relative '../tags'
|
2
|
-
require_relative '../endpoints/mock_smart_server'
|
3
|
-
|
4
|
-
module SMARTAppLaunch
|
5
|
-
class SMARTClientRegistrationVerification < Inferno::Test
|
6
|
-
|
7
|
-
id :smart_client_registration_verification
|
8
|
-
title 'Verify SMART Registration'
|
9
|
-
description %(
|
10
|
-
During this test, Inferno will verify that the SMART registration details
|
11
|
-
provided are conformant.
|
12
|
-
)
|
13
|
-
input :smart_jwk_set,
|
14
|
-
title: 'SMART JSON Web Key Set (JWKS)',
|
15
|
-
type: 'textarea',
|
16
|
-
description: %(
|
17
|
-
The SMART client's JSON Web Key Set including the key(s) Inferno will need to
|
18
|
-
verify signatures on token requests made by the client. May be provided as either
|
19
|
-
a publicly accessible url containing the JWKS, or the raw JWKS JSON.
|
20
|
-
)
|
21
|
-
input :client_id,
|
22
|
-
title: 'Client Id',
|
23
|
-
type: 'text',
|
24
|
-
optional: true,
|
25
|
-
description: %(
|
26
|
-
If a particular client id is desired, put it here. Otherwise a
|
27
|
-
default of the Inferno session id will be used.
|
28
|
-
)
|
29
|
-
|
30
|
-
output :client_id
|
31
|
-
|
32
|
-
run do
|
33
|
-
omit_if smart_jwk_set.blank?, # for re-use: mark the smart_jwk_set input as optional when importing to enable
|
34
|
-
'Not configured for SMART authentication.'
|
35
|
-
|
36
|
-
if client_id.blank?
|
37
|
-
client_id = test_session_id
|
38
|
-
output(client_id:)
|
39
|
-
end
|
40
|
-
|
41
|
-
jwks_warnings = []
|
42
|
-
parsed_smart_jwk_set = MockSMARTServer.jwk_set(smart_jwk_set, jwks_warnings)
|
43
|
-
jwks_warnings.each { |warning| add_message('warning', warning) }
|
44
|
-
|
45
|
-
assert parsed_smart_jwk_set.length.positive?, 'JWKS content does not include any valid keys.'
|
46
|
-
|
47
|
-
# 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
|
48
|
-
|
49
|
-
assert messages.none? { |msg| msg[:type] == 'error' }, 'Invalid key set provided. See messages for details'
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
@@ -1,146 +0,0 @@
|
|
1
|
-
require_relative '../tags'
|
2
|
-
require_relative '../urls'
|
3
|
-
require_relative '../endpoints/mock_smart_server'
|
4
|
-
|
5
|
-
module SMARTAppLaunch
|
6
|
-
class SMARTClientTokenRequestVerification < Inferno::Test
|
7
|
-
include URLs
|
8
|
-
|
9
|
-
id :smart_client_token_request_verification
|
10
|
-
title 'Verify SMART Token Requests'
|
11
|
-
description %(
|
12
|
-
Check that SMART token requests are conformant.
|
13
|
-
)
|
14
|
-
|
15
|
-
input :client_id,
|
16
|
-
title: 'Client Id',
|
17
|
-
type: 'text',
|
18
|
-
optional: false,
|
19
|
-
locked: true,
|
20
|
-
description: %(
|
21
|
-
The registered Client Id for use in obtaining access tokens.
|
22
|
-
Create a new session if you need to change this value.
|
23
|
-
)
|
24
|
-
input :smart_jwk_set,
|
25
|
-
title: 'JSON Web Key Set (JWKS)',
|
26
|
-
type: 'textarea',
|
27
|
-
optional: false,
|
28
|
-
locked: true,
|
29
|
-
description: %(
|
30
|
-
The SMART client's JSON Web Key Set in the form of either a publicly accessible url
|
31
|
-
containing the JWKS, or the raw JWKS JSON. Must include the key(s) Inferno will need to
|
32
|
-
verify signatures on token requests made by the client.
|
33
|
-
Create a new session if you need to change this value.
|
34
|
-
)
|
35
|
-
output :smart_tokens
|
36
|
-
|
37
|
-
run do
|
38
|
-
omit_if smart_jwk_set.blank?, # for re-use: mark the smart_jwk_set input as optional when importing to enable
|
39
|
-
'SMART Backend Services authentication not demonstrated as a part of this test session.'
|
40
|
-
|
41
|
-
load_tagged_requests(TOKEN_TAG, SMART_TAG)
|
42
|
-
skip_if requests.blank?, 'No SMART token requests made.'
|
43
|
-
|
44
|
-
jti_list = []
|
45
|
-
token_list = []
|
46
|
-
requests.each_with_index do |token_request, index|
|
47
|
-
request_params = URI.decode_www_form(token_request.request_body).to_h
|
48
|
-
check_request_params(request_params, index + 1)
|
49
|
-
check_client_assertion(request_params['client_assertion'], index + 1, jti_list)
|
50
|
-
token_list << extract_token_from_response(token_request)
|
51
|
-
end
|
52
|
-
|
53
|
-
output smart_tokens: token_list.compact.join("\n")
|
54
|
-
|
55
|
-
assert messages.none? { |msg|
|
56
|
-
msg[:type] == 'error'
|
57
|
-
}, 'Invalid token requests detected. See messages for details.'
|
58
|
-
end
|
59
|
-
|
60
|
-
def check_request_params(params, request_num)
|
61
|
-
if params['grant_type'] != 'client_credentials'
|
62
|
-
add_message('error',
|
63
|
-
"Token request #{request_num} had an incorrect `grant_type`: expected 'client_credentials', " \
|
64
|
-
"but got '#{params['grant_type']}'")
|
65
|
-
end
|
66
|
-
if params['client_assertion_type'] != 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
|
67
|
-
add_message('error',
|
68
|
-
"Token request #{request_num} had an incorrect `client_assertion_type`: " \
|
69
|
-
"expected 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', " \
|
70
|
-
"but got '#{params['client_assertion_type']}'")
|
71
|
-
end
|
72
|
-
return unless params['scope'].blank?
|
73
|
-
|
74
|
-
add_message('error', "Token request #{request_num} did not include the requested `scope`")
|
75
|
-
end
|
76
|
-
|
77
|
-
def check_client_assertion(assertion, request_num, jti_list)
|
78
|
-
decoded_token =
|
79
|
-
begin
|
80
|
-
JWT::EncodedToken.new(assertion)
|
81
|
-
rescue StandardError => e
|
82
|
-
add_message('error', "Token request #{request_num} contained an invalid client assertion jwt: #{e}")
|
83
|
-
nil
|
84
|
-
end
|
85
|
-
|
86
|
-
return unless decoded_token.present?
|
87
|
-
|
88
|
-
check_jwt_header(decoded_token.header, request_num)
|
89
|
-
check_jwt_payload(decoded_token.payload, request_num, jti_list)
|
90
|
-
check_jwt_signature(decoded_token, request_num)
|
91
|
-
end
|
92
|
-
|
93
|
-
def check_jwt_header(header, request_num)
|
94
|
-
return unless header['typ'] != 'JWT'
|
95
|
-
|
96
|
-
add_message('error', "client assertion jwt on token request #{request_num} has an incorrect `typ` header: " \
|
97
|
-
"expected 'JWT', got '#{header['typ']}'")
|
98
|
-
end
|
99
|
-
|
100
|
-
def check_jwt_payload(claims, request_num, jti_list)
|
101
|
-
if claims['iss'] != client_id
|
102
|
-
add_message('error', "client assertion jwt on token request #{request_num} has an incorrect `iss` claim: " \
|
103
|
-
"expected '#{client_id}', got '#{claims['iss']}'")
|
104
|
-
end
|
105
|
-
|
106
|
-
if claims['sub'] != client_id
|
107
|
-
add_message('error', "client assertion jwt on token request #{request_num} has an incorrect `sub` claim: " \
|
108
|
-
"expected '#{client_id}', got '#{claims['sub']}'")
|
109
|
-
end
|
110
|
-
|
111
|
-
if claims['aud'] != client_token_url
|
112
|
-
add_message('error', "client assertion jwt on token request #{request_num} has an incorrect `aud` claim: " \
|
113
|
-
"expected '#{client_token_url}', got '#{claims['aud']}'")
|
114
|
-
end
|
115
|
-
|
116
|
-
if claims['exp'].blank?
|
117
|
-
add_message('error', "client assertion jwt on token request #{request_num} is missing the `exp` claim.")
|
118
|
-
end
|
119
|
-
|
120
|
-
if claims['jti'].blank?
|
121
|
-
add_message('error', "client assertion jwt on token request #{request_num} is missing the `jti` claim.")
|
122
|
-
elsif jti_list.include?(claims['jti'])
|
123
|
-
add_message('error', "client assertion jwt on token request #{request_num} has a `jti` claim that was " \
|
124
|
-
"previouly used: '#{claims['jti']}'.")
|
125
|
-
else
|
126
|
-
jti_list << claims['jti']
|
127
|
-
end
|
128
|
-
end
|
129
|
-
|
130
|
-
def check_jwt_signature(encoded_token, request_num)
|
131
|
-
error = MockSMARTServer.smart_assertion_signature_verification(encoded_token, smart_jwk_set)
|
132
|
-
|
133
|
-
return unless error.present?
|
134
|
-
|
135
|
-
add_message('error', "Signature validation failed on token request #{request_num}: #{error}")
|
136
|
-
end
|
137
|
-
|
138
|
-
def extract_token_from_response(request)
|
139
|
-
return unless request.status == 200
|
140
|
-
|
141
|
-
JSON.parse(request.response_body)&.dig('access_token')
|
142
|
-
rescue
|
143
|
-
nil
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
@@ -1,52 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative '../urls'
|
4
|
-
require_relative '../tags'
|
5
|
-
require_relative 'mock_smart_server'
|
6
|
-
|
7
|
-
module SMARTAppLaunch
|
8
|
-
class EchoingFHIRResponderEndpoint < Inferno::DSL::SuiteEndpoint
|
9
|
-
def test_run_identifier
|
10
|
-
MockSMARTServer.token_to_client_id(request.headers['authorization']&.delete_prefix('Bearer '))
|
11
|
-
end
|
12
|
-
|
13
|
-
def make_response
|
14
|
-
return if response.status == 401 # set in update_result (expired token handling there)
|
15
|
-
|
16
|
-
response.content_type = 'application/fhir+json'
|
17
|
-
|
18
|
-
# If the tester provided a response, echo it
|
19
|
-
# otherwise, operation outcome
|
20
|
-
echo_response = JSON.parse(result.input_json)
|
21
|
-
.find { |input| input['name'].include?('echoed_fhir_response') }
|
22
|
-
&.dig('value')
|
23
|
-
|
24
|
-
unless echo_response.present?
|
25
|
-
response.status = 400
|
26
|
-
response.body = FHIR::OperationOutcome.new(
|
27
|
-
issue: FHIR::OperationOutcome::Issue.new(
|
28
|
-
severity: 'fatal', code: 'required',
|
29
|
-
details: FHIR::CodeableConcept.new(text: 'No response provided to echo.')
|
30
|
-
)
|
31
|
-
).to_json
|
32
|
-
return
|
33
|
-
end
|
34
|
-
|
35
|
-
response.status = 200
|
36
|
-
response.body = echo_response
|
37
|
-
end
|
38
|
-
|
39
|
-
def update_result
|
40
|
-
if MockSMARTServer.request_has_expired_token?(request)
|
41
|
-
MockSMARTServer.update_response_for_expired_token(response)
|
42
|
-
return
|
43
|
-
end
|
44
|
-
|
45
|
-
nil # never update for now
|
46
|
-
end
|
47
|
-
|
48
|
-
def tags
|
49
|
-
[ACCESS_TAG]
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
@@ -1,27 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative '../../urls'
|
4
|
-
require_relative '../../tags'
|
5
|
-
require_relative '../mock_smart_server'
|
6
|
-
|
7
|
-
module SMARTAppLaunch
|
8
|
-
module MockSMARTServer
|
9
|
-
class TokenEndpoint < Inferno::DSL::SuiteEndpoint
|
10
|
-
def test_run_identifier
|
11
|
-
MockSMARTServer.client_id_from_client_assertion(request.params[:client_assertion])
|
12
|
-
end
|
13
|
-
|
14
|
-
def make_response
|
15
|
-
MockSMARTServer.make_smart_token_response(request, response, result)
|
16
|
-
end
|
17
|
-
|
18
|
-
def update_result
|
19
|
-
nil # never update for now
|
20
|
-
end
|
21
|
-
|
22
|
-
def tags
|
23
|
-
[TOKEN_TAG, SMART_TAG]
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|