smart_app_launch_test_kit 0.5.1 → 0.6.0
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/inferno_reference_server_preset.json +15 -86
- data/config/presets/inferno_reference_server_stu2_2_preset.json +20 -69
- data/config/presets/inferno_reference_server_stu2_preset.json +20 -69
- data/lib/smart_app_launch/app_redirect_test.rb +12 -44
- data/lib/smart_app_launch/app_redirect_test_stu2.rb +2 -17
- data/lib/smart_app_launch/backend_services_authorization_group.rb +33 -57
- data/lib/smart_app_launch/backend_services_authorization_request_builder.rb +22 -9
- data/lib/smart_app_launch/backend_services_authorization_request_success_test.rb +26 -21
- data/lib/smart_app_launch/backend_services_authorization_response_body_test.rb +19 -5
- data/lib/smart_app_launch/backend_services_invalid_client_assertion_test.rb +30 -25
- data/lib/smart_app_launch/backend_services_invalid_grant_type_test.rb +30 -24
- data/lib/smart_app_launch/backend_services_invalid_jwt_test.rb +31 -26
- data/lib/smart_app_launch/client_assertion_builder.rb +27 -12
- data/lib/smart_app_launch/cors_openid_fhir_user_claim_test.rb +2 -2
- data/lib/smart_app_launch/cors_token_exchange_test.rb +2 -2
- data/lib/smart_app_launch/discovery_stu1_group.rb +6 -2
- data/lib/smart_app_launch/ehr_launch_group.rb +41 -24
- data/lib/smart_app_launch/ehr_launch_group_stu2.rb +26 -10
- data/lib/smart_app_launch/ehr_launch_group_stu2_2.rb +0 -16
- data/lib/smart_app_launch/openid_fhir_user_claim_test.rb +5 -4
- data/lib/smart_app_launch/openid_token_payload_test.rb +6 -8
- data/lib/smart_app_launch/smart_stu1_suite.rb +32 -24
- data/lib/smart_app_launch/smart_stu2_2_suite.rb +56 -30
- data/lib/smart_app_launch/smart_stu2_suite.rb +56 -31
- data/lib/smart_app_launch/smart_tls_test.rb +14 -0
- data/lib/smart_app_launch/standalone_launch_group.rb +42 -25
- data/lib/smart_app_launch/standalone_launch_group_stu2.rb +26 -10
- data/lib/smart_app_launch/standalone_launch_group_stu2_2.rb +0 -16
- data/lib/smart_app_launch/token_exchange_stu2_2_test.rb +5 -17
- data/lib/smart_app_launch/token_exchange_stu2_test.rb +8 -67
- data/lib/smart_app_launch/token_exchange_test.rb +18 -38
- data/lib/smart_app_launch/token_introspection_access_token_group.rb +12 -4
- data/lib/smart_app_launch/token_introspection_access_token_group_stu2_2.rb +9 -1
- data/lib/smart_app_launch/token_introspection_group.rb +2 -4
- data/lib/smart_app_launch/token_introspection_request_group.rb +2 -4
- data/lib/smart_app_launch/token_introspection_response_group.rb +64 -49
- data/lib/smart_app_launch/token_refresh_body_test.rb +9 -2
- data/lib/smart_app_launch/token_refresh_stu2_test.rb +10 -17
- data/lib/smart_app_launch/token_refresh_test.rb +19 -20
- data/lib/smart_app_launch/token_response_body_test.rb +14 -4
- data/lib/smart_app_launch/token_response_body_test_stu2_2.rb +3 -2
- data/lib/smart_app_launch/version.rb +2 -2
- data/lib/smart_app_launch/well_known_endpoint_test.rb +11 -1
- metadata +5 -4
@@ -1,5 +1,6 @@
|
|
1
1
|
require_relative 'app_redirect_test'
|
2
2
|
require_relative 'code_received_test'
|
3
|
+
require_relative 'smart_tls_test'
|
3
4
|
require_relative 'token_exchange_test'
|
4
5
|
require_relative 'token_response_body_test'
|
5
6
|
require_relative 'token_response_headers_test'
|
@@ -36,23 +37,35 @@ module SMARTAppLaunch
|
|
36
37
|
|
37
38
|
config(
|
38
39
|
inputs: {
|
39
|
-
|
40
|
-
name: :
|
41
|
-
title: 'Standalone
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
40
|
+
smart_auth_info: {
|
41
|
+
name: :standalone_smart_auth_info,
|
42
|
+
title: 'Standalone Launch Credentials',
|
43
|
+
options: {
|
44
|
+
components: [
|
45
|
+
{
|
46
|
+
name: :auth_type,
|
47
|
+
options: {
|
48
|
+
list_options: [
|
49
|
+
{ label: 'Public', value: 'public' },
|
50
|
+
{ label: 'Confidential Symmetric', value: 'symmetric' }
|
51
|
+
]
|
52
|
+
}
|
53
|
+
},
|
54
|
+
{
|
55
|
+
name: :requested_scopes,
|
56
|
+
default: 'launch/patient openid fhirUser offline_access patient/*.read'
|
57
|
+
},
|
58
|
+
{
|
59
|
+
name: :use_discovery,
|
60
|
+
locked: true
|
61
|
+
},
|
62
|
+
{
|
63
|
+
name: :auth_request_method,
|
64
|
+
default: 'GET',
|
65
|
+
locked: true
|
66
|
+
}
|
67
|
+
]
|
68
|
+
}
|
56
69
|
},
|
57
70
|
url: {
|
58
71
|
title: 'Standalone FHIR Endpoint',
|
@@ -67,7 +80,6 @@ module SMARTAppLaunch
|
|
67
80
|
smart_credentials: {
|
68
81
|
name: :standalone_smart_credentials
|
69
82
|
}
|
70
|
-
|
71
83
|
},
|
72
84
|
outputs: {
|
73
85
|
code: { name: :standalone_code },
|
@@ -81,7 +93,8 @@ module SMARTAppLaunch
|
|
81
93
|
encounter_id: { name: :standalone_encounter_id },
|
82
94
|
received_scopes: { name: :standalone_received_scopes },
|
83
95
|
intent: { name: :standalone_intent },
|
84
|
-
smart_credentials: { name: :standalone_smart_credentials }
|
96
|
+
smart_credentials: { name: :standalone_smart_credentials },
|
97
|
+
smart_auth_info: { name: :standalone_smart_auth_info }
|
85
98
|
},
|
86
99
|
requests: {
|
87
100
|
redirect: { name: :standalone_redirect },
|
@@ -89,7 +102,7 @@ module SMARTAppLaunch
|
|
89
102
|
}
|
90
103
|
)
|
91
104
|
|
92
|
-
test from: :
|
105
|
+
test from: :smart_tls,
|
93
106
|
id: :standalone_auth_tls,
|
94
107
|
title: 'OAuth 2.0 authorize endpoint secured by transport layer security',
|
95
108
|
description: %(
|
@@ -98,12 +111,14 @@ module SMARTAppLaunch
|
|
98
111
|
servers, over TLS-secured channels.
|
99
112
|
),
|
100
113
|
config: {
|
101
|
-
|
102
|
-
|
114
|
+
options: {
|
115
|
+
minimum_allowed_version: OpenSSL::SSL::TLS1_2_VERSION,
|
116
|
+
smart_endpoint_key: :auth_url
|
117
|
+
}
|
103
118
|
}
|
104
119
|
test from: :smart_app_redirect
|
105
120
|
test from: :smart_code_received
|
106
|
-
test from: :
|
121
|
+
test from: :smart_tls,
|
107
122
|
id: :standalone_token_tls,
|
108
123
|
title: 'OAuth 2.0 token endpoint secured by transport layer security',
|
109
124
|
description: %(
|
@@ -112,8 +127,10 @@ module SMARTAppLaunch
|
|
112
127
|
servers, over TLS-secured channels.
|
113
128
|
),
|
114
129
|
config: {
|
115
|
-
|
116
|
-
|
130
|
+
options: {
|
131
|
+
minimum_allowed_version: OpenSSL::SSL::TLS1_2_VERSION,
|
132
|
+
smart_endpoint_key: :token_url
|
133
|
+
}
|
117
134
|
}
|
118
135
|
test from: :smart_token_exchange
|
119
136
|
test from: :smart_token_response_body
|
@@ -31,16 +31,32 @@ module SMARTAppLaunch
|
|
31
31
|
|
32
32
|
config(
|
33
33
|
inputs: {
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
34
|
+
smart_auth_info: {
|
35
|
+
name: :standalone_smart_auth_info,
|
36
|
+
title: 'Standalone Launch Credentials',
|
37
|
+
options: {
|
38
|
+
components: [
|
39
|
+
{
|
40
|
+
name: :requested_scopes,
|
41
|
+
default: 'launch/patient openid fhirUser offline_access patient/*.rs'
|
42
|
+
},
|
43
|
+
{
|
44
|
+
name: :pkce_support,
|
45
|
+
default: 'enabled',
|
46
|
+
locked: true
|
47
|
+
},
|
48
|
+
{
|
49
|
+
name: :pkce_code_challenge_method,
|
50
|
+
default: 'S256',
|
51
|
+
locked: true
|
52
|
+
},
|
53
|
+
Inferno::DSL::AuthInfo.default_auth_type_component_without_backend_services,
|
54
|
+
{
|
55
|
+
name: :use_discovery,
|
56
|
+
locked: true
|
57
|
+
}
|
58
|
+
]
|
59
|
+
}
|
44
60
|
}
|
45
61
|
}
|
46
62
|
)
|
@@ -30,22 +30,6 @@ module SMARTAppLaunch
|
|
30
30
|
* [Standalone Launch Sequence](http://hl7.org/fhir/smart-app-launch/STU2.2/app-launch.html#launch-app-standalone-launch)
|
31
31
|
)
|
32
32
|
|
33
|
-
config(
|
34
|
-
inputs: {
|
35
|
-
use_pkce: {
|
36
|
-
default: 'true',
|
37
|
-
locked: true
|
38
|
-
},
|
39
|
-
pkce_code_challenge_method: {
|
40
|
-
default: 'S256',
|
41
|
-
locked: true
|
42
|
-
},
|
43
|
-
requested_scopes: {
|
44
|
-
default: 'launch/patient openid fhirUser offline_access patient/*.rs'
|
45
|
-
}
|
46
|
-
}
|
47
|
-
)
|
48
|
-
|
49
33
|
test from: :smart_token_exchange_stu2_2
|
50
34
|
|
51
35
|
token_exchange_index = children.find_index { |child| child.id.to_s.end_with? 'token_exchange' }
|
@@ -4,26 +4,14 @@ module SMARTAppLaunch
|
|
4
4
|
class TokenExchangeSTU22Test < TokenExchangeSTU2Test
|
5
5
|
id :smart_token_exchange_stu2_2
|
6
6
|
|
7
|
-
|
8
|
-
if client_auth_type == 'confidential_symmetric'
|
9
|
-
assert client_secret.present?,
|
10
|
-
'A client secret must be provided when using confidential symmetric client authentication.'
|
7
|
+
input :smart_auth_info, type: :auth_info, options: { mode: 'auth' }
|
11
8
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
oauth2_params[:client_id] = client_id
|
9
|
+
def add_credentials_to_request(oauth2_params, oauth2_headers)
|
10
|
+
if smart_auth_info.public_auth?
|
11
|
+
oauth2_params[:client_id] = smart_auth_info.client_id
|
16
12
|
oauth2_headers['Origin'] = Inferno::Application['inferno_host']
|
17
13
|
else
|
18
|
-
|
19
|
-
client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
|
20
|
-
client_assertion: ClientAssertionBuilder.build(
|
21
|
-
iss: client_id,
|
22
|
-
sub: client_id,
|
23
|
-
aud: smart_token_url,
|
24
|
-
client_auth_encryption_method:
|
25
|
-
)
|
26
|
-
)
|
14
|
+
super
|
27
15
|
end
|
28
16
|
end
|
29
17
|
end
|
@@ -13,79 +13,20 @@ module SMARTAppLaunch
|
|
13
13
|
)
|
14
14
|
id :smart_token_exchange_stu2
|
15
15
|
|
16
|
-
input :client_auth_encryption_method,
|
17
|
-
title: 'Encryption Method (Confidential Asymmetric Client Auth Only)',
|
18
|
-
type: 'radio',
|
19
|
-
default: 'ES384',
|
20
|
-
options: {
|
21
|
-
list_options: [
|
22
|
-
{
|
23
|
-
label: 'ES384',
|
24
|
-
value: 'ES384'
|
25
|
-
},
|
26
|
-
{
|
27
|
-
label: 'RS384',
|
28
|
-
value: 'RS384'
|
29
|
-
}
|
30
|
-
]
|
31
|
-
}
|
32
|
-
|
33
|
-
input :client_auth_type,
|
34
|
-
title: 'Client Authentication Method',
|
35
|
-
type: 'radio',
|
36
|
-
default: 'public',
|
37
|
-
options: {
|
38
|
-
list_options: [
|
39
|
-
{
|
40
|
-
label: 'Public',
|
41
|
-
value: 'public'
|
42
|
-
},
|
43
|
-
{
|
44
|
-
label: 'Confidential Symmetric',
|
45
|
-
value: 'confidential_symmetric'
|
46
|
-
},
|
47
|
-
{
|
48
|
-
label: 'Confidential Asymmetric',
|
49
|
-
value: 'confidential_asymmetric'
|
50
|
-
}
|
51
|
-
]
|
52
|
-
}
|
53
|
-
|
54
|
-
config(
|
55
|
-
inputs: {
|
56
|
-
use_pkce: {
|
57
|
-
default: 'true',
|
58
|
-
options: {
|
59
|
-
list_options: [
|
60
|
-
{
|
61
|
-
label: 'Enabled',
|
62
|
-
value: 'true'
|
63
|
-
}
|
64
|
-
]
|
65
|
-
}
|
66
|
-
}
|
67
|
-
}
|
68
|
-
)
|
69
|
-
|
70
16
|
def add_credentials_to_request(oauth2_params, oauth2_headers)
|
71
|
-
if
|
72
|
-
assert client_secret.present?,
|
73
|
-
"A client secret must be provided when using confidential symmetric client authentication."
|
74
|
-
|
75
|
-
client_credentials = "#{client_id}:#{client_secret}"
|
76
|
-
oauth2_headers['Authorization'] = "Basic #{Base64.strict_encode64(client_credentials)}"
|
77
|
-
elsif client_auth_type == 'public'
|
78
|
-
oauth2_params[:client_id] = client_id
|
79
|
-
else
|
17
|
+
if smart_auth_info.asymmetric_auth?
|
80
18
|
oauth2_params.merge!(
|
81
19
|
client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
|
82
20
|
client_assertion: ClientAssertionBuilder.build(
|
83
|
-
iss: client_id,
|
84
|
-
sub: client_id,
|
85
|
-
aud:
|
86
|
-
client_auth_encryption_method:
|
21
|
+
iss: smart_auth_info.client_id,
|
22
|
+
sub: smart_auth_info.client_id,
|
23
|
+
aud: smart_auth_info.token_url,
|
24
|
+
client_auth_encryption_method: smart_auth_info.encryption_algorithm,
|
25
|
+
custom_jwks: smart_auth_info.jwks
|
87
26
|
)
|
88
27
|
)
|
28
|
+
else
|
29
|
+
super
|
89
30
|
end
|
90
31
|
end
|
91
32
|
end
|
@@ -9,30 +9,12 @@ module SMARTAppLaunch
|
|
9
9
|
RFC6749](https://tools.ietf.org/html/rfc6749#section-4.1.3).
|
10
10
|
)
|
11
11
|
id :smart_token_exchange
|
12
|
-
|
13
|
-
input :code,
|
14
|
-
:smart_token_url,
|
15
|
-
:client_id
|
16
|
-
input :client_secret, optional: true
|
17
|
-
input :use_pkce,
|
18
|
-
title: 'Proof Key for Code Exchange (PKCE)',
|
19
|
-
type: 'radio',
|
20
|
-
default: 'false',
|
21
|
-
options: {
|
22
|
-
list_options: [
|
23
|
-
{
|
24
|
-
label: 'Enabled',
|
25
|
-
value: 'true'
|
26
|
-
},
|
27
|
-
{
|
28
|
-
label: 'Disabled',
|
29
|
-
value: 'false'
|
30
|
-
}
|
31
|
-
]
|
32
|
-
}
|
12
|
+
input :code
|
33
13
|
input :pkce_code_verifier, optional: true
|
34
|
-
|
35
|
-
|
14
|
+
input :smart_auth_info, type: :auth_info, options: { mode: 'auth' }
|
15
|
+
|
16
|
+
output :token_retrieval_time, :smart_credentials, :smart_auth_info
|
17
|
+
|
36
18
|
uses_request :redirect
|
37
19
|
makes_request :token
|
38
20
|
|
@@ -45,11 +27,11 @@ module SMARTAppLaunch
|
|
45
27
|
end
|
46
28
|
|
47
29
|
def add_credentials_to_request(oauth2_params, oauth2_headers)
|
48
|
-
if
|
49
|
-
client_credentials = "#{client_id}:#{client_secret}"
|
30
|
+
if smart_auth_info.symmetric_auth?
|
31
|
+
client_credentials = "#{smart_auth_info.client_id}:#{smart_auth_info.client_secret}"
|
50
32
|
oauth2_headers['Authorization'] = "Basic #{Base64.strict_encode64(client_credentials)}"
|
51
33
|
else
|
52
|
-
oauth2_params[:client_id] = client_id
|
34
|
+
oauth2_params[:client_id] = smart_auth_info.client_id
|
53
35
|
end
|
54
36
|
end
|
55
37
|
|
@@ -65,26 +47,24 @@ module SMARTAppLaunch
|
|
65
47
|
|
66
48
|
add_credentials_to_request(oauth2_params, oauth2_headers)
|
67
49
|
|
68
|
-
oauth2_params[:code_verifier] = pkce_code_verifier if
|
50
|
+
oauth2_params[:code_verifier] = pkce_code_verifier if smart_auth_info.pkce_enabled?
|
69
51
|
|
70
|
-
post(
|
52
|
+
post(smart_auth_info.token_url, body: oauth2_params, name: :token, headers: oauth2_headers)
|
71
53
|
|
72
54
|
assert_response_status(200)
|
73
55
|
assert_valid_json(request.response_body)
|
74
56
|
|
75
|
-
|
57
|
+
smart_auth_info.issue_time = Time.now
|
76
58
|
|
77
59
|
token_response_body = JSON.parse(request.response_body)
|
78
60
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
token_url: smart_token_url
|
87
|
-
}.to_json
|
61
|
+
smart_auth_info.refresh_token = token_response_body['refresh_token']
|
62
|
+
smart_auth_info.access_token = token_response_body['access_token']
|
63
|
+
smart_auth_info.expires_in = token_response_body['expires_in']
|
64
|
+
|
65
|
+
output smart_credentials: smart_auth_info,
|
66
|
+
token_retrieval_time: smart_auth_info.issue_time.iso8601,
|
67
|
+
smart_auth_info: smart_auth_info
|
88
68
|
end
|
89
69
|
end
|
90
70
|
end
|
@@ -15,12 +15,20 @@ module SMARTAppLaunch
|
|
15
15
|
These tests are currently designed such that the token introspection URL must be present in the SMART well-known endpoint.
|
16
16
|
|
17
17
|
)
|
18
|
-
|
18
|
+
|
19
19
|
input_instructions %(
|
20
20
|
Register Inferno as a Standalone SMART App and provide the registration details below.
|
21
21
|
)
|
22
|
-
|
23
|
-
group from: :smart_discovery_stu2
|
22
|
+
|
23
|
+
group from: :smart_discovery_stu2,
|
24
|
+
config: {
|
25
|
+
inputs: {
|
26
|
+
smart_auth_info: { name: :standalone_smart_auth_info }
|
27
|
+
},
|
28
|
+
outputs: {
|
29
|
+
smart_auth_info: { name: :standalone_smart_auth_info }
|
30
|
+
}
|
31
|
+
}
|
24
32
|
group from: :smart_standalone_launch_stu2
|
25
33
|
end
|
26
|
-
end
|
34
|
+
end
|
@@ -20,7 +20,15 @@ module SMARTAppLaunch
|
|
20
20
|
Register Inferno as a Standalone SMART App and provide the registration details below.
|
21
21
|
)
|
22
22
|
|
23
|
-
group from: :smart_discovery_stu2_2
|
23
|
+
group from: :smart_discovery_stu2_2,
|
24
|
+
config: {
|
25
|
+
inputs: {
|
26
|
+
smart_auth_info: { name: :standalone_smart_auth_info }
|
27
|
+
},
|
28
|
+
outputs: {
|
29
|
+
smart_auth_info: { name: :standalone_smart_auth_info }
|
30
|
+
}
|
31
|
+
}
|
24
32
|
group from: :smart_standalone_launch_stu2_2
|
25
33
|
end
|
26
34
|
end
|
@@ -44,11 +44,9 @@ module SMARTAppLaunch
|
|
44
44
|
group from: :smart_token_introspection_request_group
|
45
45
|
group from: :smart_token_introspection_response_group
|
46
46
|
|
47
|
-
input_order :url, :
|
48
|
-
:authorization_method, :use_pkce, :pkce_code_challenge_method,
|
49
|
-
:standalone_requested_scopes, :client_auth_encryption_method,
|
50
|
-
:client_auth_type, :custom_authorization_header,
|
47
|
+
input_order :url, :standalone_smart_auth_info, :custom_authorization_header,
|
51
48
|
:optional_introspection_request_params
|
49
|
+
|
52
50
|
input_instructions %(
|
53
51
|
Executing tests at this level will run all three Token Introspection groups back-to-back. If test groups need
|
54
52
|
to be run independently, exit this window and select a specific test group instead.
|
@@ -66,9 +66,7 @@ module SMARTAppLaunch
|
|
66
66
|
body are returned in the response.
|
67
67
|
)
|
68
68
|
|
69
|
-
input :
|
70
|
-
title: 'Access Token',
|
71
|
-
description: 'The access token to be introspected. MUST be active.'
|
69
|
+
input :standalone_smart_auth_info, type: :auth_info, options: { mode: 'access' }
|
72
70
|
|
73
71
|
output :active_token_introspection_response_body
|
74
72
|
|
@@ -77,7 +75,7 @@ module SMARTAppLaunch
|
|
77
75
|
skip_if well_known_introspection_url.nil?, 'No introspection URL present in SMART well-known endpoint.'
|
78
76
|
|
79
77
|
headers = { 'Accept' => 'application/json', 'Content-Type' => 'application/x-www-form-urlencoded' }
|
80
|
-
body = "token=#{
|
78
|
+
body = "token=#{standalone_smart_auth_info.access_token}"
|
81
79
|
|
82
80
|
if custom_authorization_header.present?
|
83
81
|
custom_headers = custom_authorization_header.split("\n")
|