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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/config/presets/inferno_reference_server_preset.json +15 -86
  3. data/config/presets/inferno_reference_server_stu2_2_preset.json +20 -69
  4. data/config/presets/inferno_reference_server_stu2_preset.json +20 -69
  5. data/lib/smart_app_launch/app_redirect_test.rb +12 -44
  6. data/lib/smart_app_launch/app_redirect_test_stu2.rb +2 -17
  7. data/lib/smart_app_launch/backend_services_authorization_group.rb +33 -57
  8. data/lib/smart_app_launch/backend_services_authorization_request_builder.rb +22 -9
  9. data/lib/smart_app_launch/backend_services_authorization_request_success_test.rb +26 -21
  10. data/lib/smart_app_launch/backend_services_authorization_response_body_test.rb +19 -5
  11. data/lib/smart_app_launch/backend_services_invalid_client_assertion_test.rb +30 -25
  12. data/lib/smart_app_launch/backend_services_invalid_grant_type_test.rb +30 -24
  13. data/lib/smart_app_launch/backend_services_invalid_jwt_test.rb +31 -26
  14. data/lib/smart_app_launch/client_assertion_builder.rb +27 -12
  15. data/lib/smart_app_launch/cors_openid_fhir_user_claim_test.rb +2 -2
  16. data/lib/smart_app_launch/cors_token_exchange_test.rb +2 -2
  17. data/lib/smart_app_launch/discovery_stu1_group.rb +6 -2
  18. data/lib/smart_app_launch/ehr_launch_group.rb +41 -24
  19. data/lib/smart_app_launch/ehr_launch_group_stu2.rb +26 -10
  20. data/lib/smart_app_launch/ehr_launch_group_stu2_2.rb +0 -16
  21. data/lib/smart_app_launch/openid_fhir_user_claim_test.rb +5 -4
  22. data/lib/smart_app_launch/openid_token_payload_test.rb +6 -8
  23. data/lib/smart_app_launch/smart_stu1_suite.rb +32 -24
  24. data/lib/smart_app_launch/smart_stu2_2_suite.rb +56 -30
  25. data/lib/smart_app_launch/smart_stu2_suite.rb +56 -31
  26. data/lib/smart_app_launch/smart_tls_test.rb +14 -0
  27. data/lib/smart_app_launch/standalone_launch_group.rb +42 -25
  28. data/lib/smart_app_launch/standalone_launch_group_stu2.rb +26 -10
  29. data/lib/smart_app_launch/standalone_launch_group_stu2_2.rb +0 -16
  30. data/lib/smart_app_launch/token_exchange_stu2_2_test.rb +5 -17
  31. data/lib/smart_app_launch/token_exchange_stu2_test.rb +8 -67
  32. data/lib/smart_app_launch/token_exchange_test.rb +18 -38
  33. data/lib/smart_app_launch/token_introspection_access_token_group.rb +12 -4
  34. data/lib/smart_app_launch/token_introspection_access_token_group_stu2_2.rb +9 -1
  35. data/lib/smart_app_launch/token_introspection_group.rb +2 -4
  36. data/lib/smart_app_launch/token_introspection_request_group.rb +2 -4
  37. data/lib/smart_app_launch/token_introspection_response_group.rb +64 -49
  38. data/lib/smart_app_launch/token_refresh_body_test.rb +9 -2
  39. data/lib/smart_app_launch/token_refresh_stu2_test.rb +10 -17
  40. data/lib/smart_app_launch/token_refresh_test.rb +19 -20
  41. data/lib/smart_app_launch/token_response_body_test.rb +14 -4
  42. data/lib/smart_app_launch/token_response_body_test_stu2_2.rb +3 -2
  43. data/lib/smart_app_launch/version.rb +2 -2
  44. data/lib/smart_app_launch/well_known_endpoint_test.rb +11 -1
  45. 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
- client_id: {
40
- name: :standalone_client_id,
41
- title: 'Standalone Client ID',
42
- description: 'Client ID provided during registration of Inferno as a standalone application'
43
- },
44
- client_secret: {
45
- name: :standalone_client_secret,
46
- title: 'Standalone Client Secret',
47
- description: 'Client Secret provided during registration of Inferno as a standalone application. ' \
48
- 'Only for clients using confidential symmetric authentication.'
49
- },
50
- requested_scopes: {
51
- name: :standalone_requested_scopes,
52
- title: 'Standalone Scope',
53
- description: 'OAuth 2.0 scope provided by system to enable all required functionality',
54
- type: 'textarea',
55
- default: 'launch/patient openid fhirUser offline_access patient/*.read'
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: :tls_version_test,
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
- inputs: { url: { name: :smart_authorization_url } },
102
- options: { minimum_allowed_version: OpenSSL::SSL::TLS1_2_VERSION }
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: :tls_version_test,
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
- inputs: { url: { name: :smart_token_url } },
116
- options: { minimum_allowed_version: OpenSSL::SSL::TLS1_2_VERSION }
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
- use_pkce: {
35
- default: 'true',
36
- locked: true
37
- },
38
- pkce_code_challenge_method: {
39
- default: 'S256',
40
- locked: true
41
- },
42
- requested_scopes: {
43
- default: 'launch/patient openid fhirUser offline_access patient/*.rs'
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
- def add_credentials_to_request(oauth2_params, oauth2_headers)
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
- client_credentials = "#{client_id}:#{client_secret}"
13
- oauth2_headers['Authorization'] = "Basic #{Base64.strict_encode64(client_credentials)}"
14
- elsif client_auth_type == 'public'
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
- oauth2_params.merge!(
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 client_auth_type == 'confidential_symmetric'
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: smart_token_url,
86
- client_auth_encryption_method: 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
- output :token_retrieval_time
35
- output :smart_credentials
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 client_secret.present?
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 use_pkce == 'true'
50
+ oauth2_params[:code_verifier] = pkce_code_verifier if smart_auth_info.pkce_enabled?
69
51
 
70
- post(smart_token_url, body: oauth2_params, name: :token, headers: oauth2_headers)
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
- output token_retrieval_time: Time.now.iso8601
57
+ smart_auth_info.issue_time = Time.now
76
58
 
77
59
  token_response_body = JSON.parse(request.response_body)
78
60
 
79
- output smart_credentials: {
80
- refresh_token: token_response_body['refresh_token'],
81
- access_token: token_response_body['access_token'],
82
- expires_in: token_response_body['expires_in'],
83
- client_id:,
84
- client_secret:,
85
- token_retrieval_time:,
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, :standalone_client_id, :standalone_client_secret,
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 :standalone_access_token,
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=#{standalone_access_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")