smart_app_launch_test_kit 0.1.3 → 0.1.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 758773637de38a995aa7943da1291685f655e97f829c2e11d7adfc54d129ad42
4
- data.tar.gz: b0c2adfcf695418617999e414a0d85766e440e943787d811ffb54218810fee2c
3
+ metadata.gz: fa36db12fcfc122798d91b90db284ba19912e36f0f9086a8fe193ddcead7417d
4
+ data.tar.gz: a78b8efce897f363156cfcad7bdcf49279b95da08dd26c4c4b2cc15f6ff424dc
5
5
  SHA512:
6
- metadata.gz: 9b9aabc2d05bf3b3a3fe49f35fe0be2e634f7c774d86d3d6a0129aa1cd424f1c5cebd03c84c24855445a0baf95ac23c730d614e9f628a946b5ac8d5ca0b6f8fa
7
- data.tar.gz: b287eaf62940d60d904af996c0cbbd90d59ba68ce79cdbe6400a20515b9eccb6aa0ff6c094bd07cfd86b928a11c3b6e7e740c896d125c62f5da65a857450ed38
6
+ metadata.gz: 590164ee28fbcd4d482656268af102328c534315cf55aa193ab55c8b4d4c806bef8735f5abd5ade375f89554849bcf3c6f60fa9206957ee64d6a6d5a491143c0
7
+ data.tar.gz: 0a779fd7a525a96edb4cc1f0dee37bfe5bc4ca1df772dd07fa92ca95c4f9722c35d81dd63ea4df1d2c038f0b344267be64f5700bf9e80a5f2aedc3514a9c33e9
@@ -12,13 +12,23 @@ module SMARTAppLaunch
12
12
 
13
13
  config options: { launch_uri: "#{Inferno::Application['base_url']}/custom/smart/launch" }
14
14
 
15
+ def wait_message
16
+ return instance_exec(&config.options[:launch_message_proc]) if config.options[:launch_message_proc].present?
17
+
18
+ %(
19
+ ### #{self.class.parent&.parent&.title}
20
+
21
+ Waiting for Inferno to be launched from the EHR.
22
+
23
+ Tests will resume once Inferno receives a launch request at
24
+ `#{config.options[:launch_uri]}` with an `iss` of `#{url}`.
25
+ )
26
+ end
27
+
15
28
  run do
16
29
  wait(
17
30
  identifier: url,
18
- message: %(
19
- Waiting to receive a request at
20
- `#{config.options[:launch_uri]}` with an `iss` of `#{url}`.
21
- )
31
+ message: wait_message
22
32
  )
23
33
  end
24
34
  end
@@ -58,10 +58,17 @@ module SMARTAppLaunch
58
58
  end
59
59
 
60
60
  def wait_message(auth_url)
61
+ if config.options[:redirect_message_proc].present?
62
+ return instance_exec(auth_url, &config.options[:redirect_message_proc])
63
+ end
64
+
61
65
  %(
66
+ ### #{self.class.parent&.parent&.title}
67
+
62
68
  [Follow this link to authorize with the SMART server](#{auth_url}).
63
- Waiting to receive a request at `#{config.options[:redirect_uri]}` with
64
- a state of `#{state}`.
69
+
70
+ Tests will resume once Inferno receives a request at
71
+ `#{config.options[:redirect_uri]}` with a state of `#{state}`.
65
72
  )
66
73
  end
67
74
 
@@ -1,5 +1,8 @@
1
+ require_relative 'well_known_capabilities_stu1_test'
2
+ require_relative 'well_known_endpoint_test'
3
+
1
4
  module SMARTAppLaunch
2
- class DiscoveryGroup < Inferno::TestGroup
5
+ class DiscoverySTU1Group < Inferno::TestGroup
3
6
  id :smart_discovery
4
7
  title 'SMART on FHIR Discovery'
5
8
  short_description 'Retrieve server\'s SMART on FHIR configuration.'
@@ -36,83 +39,10 @@ module SMARTAppLaunch
36
39
  * [OpenID Connect Core](https://openid.net/specs/openid-connect-core-1_0.html)
37
40
  )
38
41
 
39
- test do
40
- title 'FHIR server makes SMART configuration available from well-known endpoint'
41
- description %(
42
- The authorization endpoints accepted by a FHIR resource server can
43
- be exposed as a Well-Known Uniform Resource Identifier
44
- )
45
- input :url,
46
- title: 'FHIR Endpoint',
47
- description: 'URL of the FHIR endpoint used by SMART applications'
48
- output :well_known_configuration,
49
- :well_known_authorization_url,
50
- :well_known_introspection_url,
51
- :well_known_management_url,
52
- :well_known_registration_url,
53
- :well_known_revocation_url,
54
- :well_known_token_url
55
- makes_request :smart_well_known_configuration
56
-
57
- run do
58
- well_known_configuration_url = "#{url.chomp('/')}/.well-known/smart-configuration"
59
- get(well_known_configuration_url, name: :smart_well_known_configuration)
60
-
61
- assert_response_status(200)
62
-
63
- assert_valid_json(request.response_body)
64
-
65
- config = JSON.parse(request.response_body)
66
- output well_known_configuration: request.response_body,
67
- well_known_authorization_url: config['authorization_endpoint'],
68
- well_known_introspection_url: config['introspection_endpoint'],
69
- well_known_management_url: config['management_endpoint'],
70
- well_known_registration_url: config['registration_endpoint'],
71
- well_known_revocation_url: config['revocation_endpoint'],
72
- well_known_token_url: config['token_endpoint']
73
-
74
- content_type = request.response_header('Content-Type')&.value
75
-
76
- assert content_type.present?, 'No `Content-Type` header received.'
77
- assert content_type.start_with?('application/json'),
78
- "`Content-Type` must be `application/json`, but received: `#{content_type}`"
79
- end
80
- end
81
-
82
- test do
83
- title 'Well-known configuration contains required fields'
84
- description %(
85
- The JSON from .well-known/smart-configuration contains the following
86
- required fields: `authorization_endpoint`, `token_endpoint`,
87
- `capabilities`
88
- )
89
- input :well_known_configuration
90
-
91
- run do
92
- skip_if well_known_configuration.blank?, 'No well-known configuration found'
93
- config = JSON.parse(well_known_configuration)
94
-
95
- ['authorization_endpoint', 'token_endpoint', 'capabilities'].each do |key|
96
- assert config.key?(key), "Well-known configuration does not include `#{key}`"
97
- assert config[key].present?, "Well-known configuration field `#{key}` is blank"
98
- end
99
-
100
- assert config['authorization_endpoint'].is_a?(String),
101
- 'Well-known `authorization_endpoint` field must be a string'
102
- assert config['token_endpoint'].is_a?(String),
103
- 'Well-known `token_endpoint` field must be a string'
104
- assert config['capabilities'].is_a?(Array),
105
- 'Well-known `capabilities` field must be an array'
106
-
107
- non_string_capabilities = config['capabilities'].reject { |capability| capability.is_a? String }
108
-
109
- assert non_string_capabilities.blank?, %(
110
- Well-known `capabilities` field must be an array of strings, but found
111
- non-string values:
112
- #{non_string_capabilities.map { |value| "`#{value.nil? ? 'nil' : value}`" }.join(', ')}
113
- )
114
- end
115
- end
42
+ test from: :well_known_endpoint,
43
+ id: 'Test01'
44
+ test from: :well_known_capabilities_stu1,
45
+ id: 'Test02'
116
46
 
117
47
  test do
118
48
  title 'Conformance/CapabilityStatement provides OAuth 2.0 endpoints'
@@ -0,0 +1,50 @@
1
+ require_relative 'well_known_capabilities_stu2_test'
2
+ require_relative 'well_known_endpoint_test'
3
+
4
+ module SMARTAppLaunch
5
+ class DiscoverySTU2Group < Inferno::TestGroup
6
+ id :smart_discovery_stu2
7
+ title 'SMART on FHIR Discovery'
8
+ short_description 'Retrieve server\'s SMART on FHIR configuration.'
9
+ description %(
10
+ # Background
11
+
12
+ The #{title} Sequence test looks for authorization endpoints and SMART
13
+ capabilities as described by the [SMART App Launch
14
+ Framework](http://hl7.org/fhir/smart-app-launch/STU2/).
15
+ The SMART launch framework uses OAuth 2.0 to *authorize* apps, like
16
+ Inferno, to access certain information on a FHIR server. The
17
+ authorization service accessed at the endpoint allows users to give
18
+ these apps permission without sharing their credentials with the
19
+ application itself. Instead, the application receives an access token
20
+ which allows it to access resources on the server. The access token
21
+ itself has a limited lifetime and permission scopes associated with it.
22
+ A refresh token may also be provided to the application in order to
23
+ obtain another access token. Unlike access tokens, a refresh token is
24
+ not shared with the resource server. If OpenID Connect is used, an id
25
+ token may be provided as well. The id token can be used to
26
+ *authenticate* the user. The id token is digitally signed and allows the
27
+ identity of the user to be verified.
28
+
29
+ # Test Methodology
30
+
31
+ This test suite will examine the SMART on FHIR configuration contained
32
+ in the `/.well-known/smart-configuration` endpoint.
33
+
34
+ For more information see:
35
+
36
+ * [SMART App Launch Framework](http://hl7.org/fhir/smart-app-launch/STU2/)
37
+ * [The OAuth 2.0 Authorization Framework](https://tools.ietf.org/html/rfc6749)
38
+ * [OpenID Connect Core](https://openid.net/specs/openid-connect-core-1_0.html)
39
+ )
40
+
41
+ test from: :well_known_endpoint,
42
+ config: {
43
+ outputs: {
44
+ well_known_authorization_url: { name: :smart_authorization_url },
45
+ well_known_token_url: { name: :smart_token_url }
46
+ }
47
+ }
48
+ test from: :well_known_capabilities_stu2
49
+ end
50
+ end
@@ -0,0 +1,45 @@
1
+ require_relative 'ehr_launch_group'
2
+
3
+ module SMARTAppLaunch
4
+ class EHRLaunchGroupSTU2 < EHRLaunchGroup
5
+ id :smart_ehr_launch_stu2
6
+ description %(
7
+ # Background
8
+
9
+ The [EHR
10
+ Launch](http://hl7.org/fhir/smart-app-launch/STU2/app-launch.html#launch-app-ehr-launch)
11
+ is one of two ways in which an app can be launched, the other being
12
+ Standalone launch. In an EHR launch, the app is launched from an
13
+ existing EHR session or portal by a redirect to the registered launch
14
+ URL. The EHR provides the app two parameters:
15
+
16
+ * `iss` - Which contains the FHIR server url
17
+ * `launch` - An identifier needed for authorization
18
+
19
+ # Test Methodology
20
+
21
+ Inferno will wait for the EHR server redirect upon execution. When the
22
+ redirect is received Inferno will check for the presence of the `iss`
23
+ and `launch` parameters. The security of the authorization endpoint is
24
+ then checked and authorization is attempted using the provided `launch`
25
+ identifier.
26
+
27
+ For more information on the #{title} see:
28
+
29
+ * [SMART EHR Launch Sequence](http://hl7.org/fhir/smart-app-launch/STU2/app-launch.html#launch-app-ehr-launch)
30
+ )
31
+
32
+ config(
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
+ }
43
+ )
44
+ end
45
+ end
@@ -0,0 +1,154 @@
1
+ require 'tls_test_kit'
2
+
3
+ require_relative 'version'
4
+ require_relative 'discovery_stu1_group'
5
+ require_relative 'standalone_launch_group'
6
+ require_relative 'ehr_launch_group'
7
+ require_relative 'openid_connect_group'
8
+ require_relative 'token_refresh_group'
9
+
10
+ module SMARTAppLaunch
11
+ class SMARTSTU1Suite < Inferno::TestSuite
12
+ id 'smart'
13
+ title 'SMART App Launch STU1'
14
+ version VERSION
15
+
16
+ resume_test_route :get, '/launch' do
17
+ request.query_parameters['iss']
18
+ end
19
+
20
+ resume_test_route :get, '/redirect' do
21
+ request.query_parameters['state']
22
+ end
23
+
24
+ config options: {
25
+ redirect_uri: "#{Inferno::Application['base_url']}/custom/smart/redirect",
26
+ launch_uri: "#{Inferno::Application['base_url']}/custom/smart/launch"
27
+ }
28
+
29
+ group do
30
+ title 'Standalone Launch'
31
+ id :smart_full_standalone_launch
32
+
33
+ run_as_group
34
+
35
+ group from: :smart_discovery
36
+ group from: :smart_standalone_launch
37
+
38
+ group from: :smart_openid_connect,
39
+ config: {
40
+ inputs: {
41
+ id_token: { name: :standalone_id_token },
42
+ client_id: { name: :standalone_client_id },
43
+ requested_scopes: { name: :standalone_requested_scopes },
44
+ access_token: { name: :standalone_access_token },
45
+ smart_credentials: { name: :standalone_smart_credentials }
46
+ }
47
+ }
48
+
49
+ group from: :smart_token_refresh,
50
+ id: :smart_standalone_refresh_without_scopes,
51
+ title: 'SMART Token Refresh Without Scopes',
52
+ config: {
53
+ inputs: {
54
+ refresh_token: { name: :standalone_refresh_token },
55
+ client_id: { name: :standalone_client_id },
56
+ client_secret: { name: :standalone_client_secret },
57
+ received_scopes: { name: :standalone_received_scopes }
58
+ },
59
+ outputs: {
60
+ refresh_token: { name: :standalone_refresh_token },
61
+ received_scopes: { name: :standalone_received_scopes },
62
+ access_token: { name: :standalone_access_token },
63
+ token_retrieval_time: { name: :standalone_token_retrieval_time },
64
+ expires_in: { name: :standalone_expires_in },
65
+ smart_credentials: { name: :standalone_smart_credentials }
66
+ }
67
+ }
68
+
69
+ group from: :smart_token_refresh,
70
+ id: :smart_standalone_refresh_with_scopes,
71
+ title: 'SMART Token Refresh With Scopes',
72
+ config: {
73
+ options: { include_scopes: true },
74
+ inputs: {
75
+ refresh_token: { name: :standalone_refresh_token },
76
+ client_id: { name: :standalone_client_id },
77
+ client_secret: { name: :standalone_client_secret },
78
+ received_scopes: { name: :standalone_received_scopes }
79
+ },
80
+ outputs: {
81
+ refresh_token: { name: :standalone_refresh_token },
82
+ received_scopes: { name: :standalone_received_scopes },
83
+ access_token: { name: :standalone_access_token },
84
+ token_retrieval_time: { name: :standalone_token_retrieval_time },
85
+ expires_in: { name: :standalone_expires_in },
86
+ smart_credentials: { name: :standalone_smart_credentials }
87
+ }
88
+ }
89
+ end
90
+
91
+ group do
92
+ title 'EHR Launch'
93
+ id :smart_full_ehr_launch
94
+
95
+ run_as_group
96
+
97
+ group from: :smart_discovery
98
+
99
+ group from: :smart_ehr_launch
100
+
101
+ group from: :smart_openid_connect,
102
+ config: {
103
+ inputs: {
104
+ id_token: { name: :ehr_id_token },
105
+ client_id: { name: :ehr_client_id },
106
+ requested_scopes: { name: :ehr_requested_scopes },
107
+ access_token: { name: :ehr_access_token },
108
+ smart_credentials: { name: :ehr_smart_credentials }
109
+ }
110
+ }
111
+
112
+ group from: :smart_token_refresh,
113
+ id: :smart_ehr_refresh_without_scopes,
114
+ title: 'SMART Token Refresh Without Scopes',
115
+ config: {
116
+ inputs: {
117
+ refresh_token: { name: :ehr_refresh_token },
118
+ client_id: { name: :ehr_client_id },
119
+ client_secret: { name: :ehr_client_secret },
120
+ received_scopes: { name: :ehr_received_scopes }
121
+ },
122
+ outputs: {
123
+ refresh_token: { name: :ehr_refresh_token },
124
+ received_scopes: { name: :ehr_received_scopes },
125
+ access_token: { name: :ehr_access_token },
126
+ token_retrieval_time: { name: :ehr_token_retrieval_time },
127
+ expires_in: { name: :ehr_expires_in },
128
+ smart_credentials: { name: :ehr_smart_credentials }
129
+ }
130
+ }
131
+
132
+ group from: :smart_token_refresh,
133
+ id: :smart_ehr_refresh_with_scopes,
134
+ title: 'SMART Token Refresh With Scopes',
135
+ config: {
136
+ options: { include_scopes: true },
137
+ inputs: {
138
+ refresh_token: { name: :ehr_refresh_token },
139
+ client_id: { name: :ehr_client_id },
140
+ client_secret: { name: :ehr_client_secret },
141
+ received_scopes: { name: :ehr_received_scopes }
142
+ },
143
+ outputs: {
144
+ refresh_token: { name: :ehr_refresh_token },
145
+ received_scopes: { name: :ehr_received_scopes },
146
+ access_token: { name: :ehr_access_token },
147
+ token_retrieval_time: { name: :ehr_token_retrieval_time },
148
+ expires_in: { name: :ehr_expires_in },
149
+ smart_credentials: { name: :ehr_smart_credentials }
150
+ }
151
+ }
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,154 @@
1
+ require 'tls_test_kit'
2
+
3
+ require_relative 'version'
4
+ require_relative 'discovery_stu2_group'
5
+ require_relative 'standalone_launch_group_stu2'
6
+ require_relative 'ehr_launch_group_stu2'
7
+ require_relative 'openid_connect_group'
8
+ require_relative 'token_refresh_group'
9
+
10
+ module SMARTAppLaunch
11
+ class SMARTSTU2Suite < Inferno::TestSuite
12
+ id 'smart_stu2'
13
+ title 'SMART App Launch STU2 (Work in Progress)'
14
+ version VERSION
15
+
16
+ resume_test_route :get, '/launch' do
17
+ request.query_parameters['iss']
18
+ end
19
+
20
+ resume_test_route :get, '/redirect' do
21
+ request.query_parameters['state']
22
+ end
23
+
24
+ config options: {
25
+ redirect_uri: "#{Inferno::Application['base_url']}/custom/smart/redirect",
26
+ launch_uri: "#{Inferno::Application['base_url']}/custom/smart/launch"
27
+ }
28
+
29
+ group do
30
+ title 'Standalone Launch'
31
+ id :smart_full_standalone_launch
32
+
33
+ run_as_group
34
+
35
+ group from: :smart_discovery_stu2
36
+ group from: :smart_standalone_launch_stu2
37
+
38
+ group from: :smart_openid_connect,
39
+ config: {
40
+ inputs: {
41
+ id_token: { name: :standalone_id_token },
42
+ client_id: { name: :standalone_client_id },
43
+ requested_scopes: { name: :standalone_requested_scopes },
44
+ access_token: { name: :standalone_access_token },
45
+ smart_credentials: { name: :standalone_smart_credentials }
46
+ }
47
+ }
48
+
49
+ group from: :smart_token_refresh,
50
+ id: :smart_standalone_refresh_without_scopes,
51
+ title: 'SMART Token Refresh Without Scopes',
52
+ config: {
53
+ inputs: {
54
+ refresh_token: { name: :standalone_refresh_token },
55
+ client_id: { name: :standalone_client_id },
56
+ client_secret: { name: :standalone_client_secret },
57
+ received_scopes: { name: :standalone_received_scopes }
58
+ },
59
+ outputs: {
60
+ refresh_token: { name: :standalone_refresh_token },
61
+ received_scopes: { name: :standalone_received_scopes },
62
+ access_token: { name: :standalone_access_token },
63
+ token_retrieval_time: { name: :standalone_token_retrieval_time },
64
+ expires_in: { name: :standalone_expires_in },
65
+ smart_credentials: { name: :standalone_smart_credentials }
66
+ }
67
+ }
68
+
69
+ group from: :smart_token_refresh,
70
+ id: :smart_standalone_refresh_with_scopes,
71
+ title: 'SMART Token Refresh With Scopes',
72
+ config: {
73
+ options: { include_scopes: true },
74
+ inputs: {
75
+ refresh_token: { name: :standalone_refresh_token },
76
+ client_id: { name: :standalone_client_id },
77
+ client_secret: { name: :standalone_client_secret },
78
+ received_scopes: { name: :standalone_received_scopes }
79
+ },
80
+ outputs: {
81
+ refresh_token: { name: :standalone_refresh_token },
82
+ received_scopes: { name: :standalone_received_scopes },
83
+ access_token: { name: :standalone_access_token },
84
+ token_retrieval_time: { name: :standalone_token_retrieval_time },
85
+ expires_in: { name: :standalone_expires_in },
86
+ smart_credentials: { name: :standalone_smart_credentials }
87
+ }
88
+ }
89
+ end
90
+
91
+ group do
92
+ title 'EHR Launch'
93
+ id :smart_full_ehr_launch
94
+
95
+ run_as_group
96
+
97
+ group from: :smart_discovery_stu2
98
+
99
+ group from: :smart_ehr_launch_stu2
100
+
101
+ group from: :smart_openid_connect,
102
+ config: {
103
+ inputs: {
104
+ id_token: { name: :ehr_id_token },
105
+ client_id: { name: :ehr_client_id },
106
+ requested_scopes: { name: :ehr_requested_scopes },
107
+ access_token: { name: :ehr_access_token },
108
+ smart_credentials: { name: :ehr_smart_credentials }
109
+ }
110
+ }
111
+
112
+ group from: :smart_token_refresh,
113
+ id: :smart_ehr_refresh_without_scopes,
114
+ title: 'SMART Token Refresh Without Scopes',
115
+ config: {
116
+ inputs: {
117
+ refresh_token: { name: :ehr_refresh_token },
118
+ client_id: { name: :ehr_client_id },
119
+ client_secret: { name: :ehr_client_secret },
120
+ received_scopes: { name: :ehr_received_scopes }
121
+ },
122
+ outputs: {
123
+ refresh_token: { name: :ehr_refresh_token },
124
+ received_scopes: { name: :ehr_received_scopes },
125
+ access_token: { name: :ehr_access_token },
126
+ token_retrieval_time: { name: :ehr_token_retrieval_time },
127
+ expires_in: { name: :ehr_expires_in },
128
+ smart_credentials: { name: :ehr_smart_credentials }
129
+ }
130
+ }
131
+
132
+ group from: :smart_token_refresh,
133
+ id: :smart_ehr_refresh_with_scopes,
134
+ title: 'SMART Token Refresh With Scopes',
135
+ config: {
136
+ options: { include_scopes: true },
137
+ inputs: {
138
+ refresh_token: { name: :ehr_refresh_token },
139
+ client_id: { name: :ehr_client_id },
140
+ client_secret: { name: :ehr_client_secret },
141
+ received_scopes: { name: :ehr_received_scopes }
142
+ },
143
+ outputs: {
144
+ refresh_token: { name: :ehr_refresh_token },
145
+ received_scopes: { name: :ehr_received_scopes },
146
+ access_token: { name: :ehr_access_token },
147
+ token_retrieval_time: { name: :ehr_token_retrieval_time },
148
+ expires_in: { name: :ehr_expires_in },
149
+ smart_credentials: { name: :ehr_smart_credentials }
150
+ }
151
+ }
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,43 @@
1
+ require_relative 'standalone_launch_group'
2
+
3
+ module SMARTAppLaunch
4
+ class StandaloneLaunchGroupSTU2 < StandaloneLaunchGroup
5
+ id :smart_standalone_launch_stu2
6
+ description %(
7
+ # Background
8
+
9
+ The [Standalone
10
+ Launch Sequence](http://hl7.org/fhir/smart-app-launch/STU2/app-launch.html#launch-app-standalone-launch)
11
+ allows an app, like Inferno, to be launched independent of an
12
+ existing EHR session. It is one of the two launch methods described in
13
+ the SMART App Launch Framework alongside EHR Launch. The app will
14
+ request authorization for the provided scope from the authorization
15
+ endpoint, ultimately receiving an authorization token which can be used
16
+ to gain access to resources on the FHIR server.
17
+
18
+ # Test Methodology
19
+
20
+ Inferno will redirect the user to the the authorization endpoint so that
21
+ they may provide any required credentials and authorize the application.
22
+ Upon successful authorization, Inferno will exchange the authorization
23
+ code provided for an access token.
24
+
25
+ For more information on the #{title}:
26
+
27
+ * [Standalone Launch Sequence](http://hl7.org/fhir/smart-app-launch/STU2/app-launch.html#launch-app-standalone-launch)
28
+ )
29
+
30
+ config(
31
+ inputs: {
32
+ use_pkce: {
33
+ default: 'true',
34
+ locked: true
35
+ },
36
+ pkce_code_challenge_method: {
37
+ default: 'S256',
38
+ locked: true
39
+ }
40
+ }
41
+ )
42
+ end
43
+ end
@@ -16,7 +16,7 @@ module SMARTAppLaunch
16
16
  the Pragma response header field with a value of no-cache to be
17
17
  consistent with the requirements of the inital access token exchange.
18
18
  )
19
- input :well_known_token_url, :refresh_token, :client_id, :received_scopes
19
+ input :smart_token_url, :refresh_token, :client_id, :received_scopes
20
20
  input :client_secret, optional: true
21
21
  output :smart_credentials, :token_retrieval_time
22
22
  makes_request :token_refresh
@@ -39,7 +39,7 @@ module SMARTAppLaunch
39
39
  oauth2_params['client_id'] = client_id
40
40
  end
41
41
 
42
- post(well_known_token_url, body: oauth2_params, name: :token_refresh, headers: oauth2_headers)
42
+ post(smart_token_url, body: oauth2_params, name: :token_refresh, headers: oauth2_headers)
43
43
 
44
44
  assert_response_status(200)
45
45
  assert_valid_json(request.response_body)
@@ -54,7 +54,7 @@ module SMARTAppLaunch
54
54
  client_id: client_id,
55
55
  client_secret: client_secret,
56
56
  token_retrieval_time: token_retrieval_time,
57
- token_url: well_known_token_url
57
+ token_url: smart_token_url
58
58
  }.to_json
59
59
  end
60
60
  end
@@ -1,3 +1,3 @@
1
1
  module SMARTAppLaunch
2
- VERSION = '0.1.3'
2
+ VERSION = '0.1.4'
3
3
  end
@@ -0,0 +1,39 @@
1
+ module SMARTAppLaunch
2
+ class WellKnownCapabilitiesSTU1Test < Inferno::Test
3
+ title 'Well-known configuration contains required fields'
4
+ id :well_known_capabilities_stu1
5
+ input :well_known_configuration
6
+ description %(
7
+ The JSON from .well-known/smart-configuration contains the following
8
+ required fields: `authorization_endpoint`, `token_endpoint`,
9
+ `capabilities`
10
+ )
11
+
12
+ def required_capabilities
13
+ {
14
+ 'authorization_endpoint' => String,
15
+ 'token_endpoint' => String,
16
+ 'capabilities' => Array
17
+ }
18
+ end
19
+
20
+ run do
21
+ skip_if well_known_configuration.blank?, 'No well-known configuration found'
22
+ config = JSON.parse(well_known_configuration)
23
+
24
+ required_capabilities.each do |key, type|
25
+ assert config.key?(key), "Well-known configuration does not include `#{key}`"
26
+ assert config[key].present?, "Well-known configuration field `#{key}` is blank"
27
+ assert config[key].is_a?(type), "Well-known `#{key}` must be type: #{type.to_s.downcase}"
28
+ end
29
+
30
+ non_string_capabilities = config['capabilities'].reject { |capability| capability.is_a? String }
31
+
32
+ assert non_string_capabilities.blank?, %(
33
+ Well-known `capabilities` field must be an array of strings, but found
34
+ non-string values:
35
+ #{non_string_capabilities.map { |value| "`#{value.nil? ? 'nil' : value}`" }.join(', ')}
36
+ )
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,61 @@
1
+ module SMARTAppLaunch
2
+ class WellKnownCapabilitiesSTU2Test < Inferno::Test
3
+ title 'Well-known configuration contains required fields'
4
+ id :well_known_capabilities_stu2
5
+ input :well_known_configuration
6
+ description %(
7
+ The JSON from .well-known/smart-configuration contains the following
8
+ required fields: `authorization_endpoint`, `token_endpoint`,
9
+ `capabilities`, `grant_types_supported`, `code_challenge_methods_supported`.
10
+ If the `sso-openid-connect` capability is supported, then `issuer` and `jwks_uri` must be
11
+ present. If `sso-openid-connect` capability is not supported, then `issuer` must be omitted.
12
+ )
13
+
14
+ def required_capabilities
15
+ {
16
+ 'authorization_endpoint' => String,
17
+ 'token_endpoint' => String,
18
+ 'capabilities' => Array,
19
+ 'grant_types_supported' => Array,
20
+ 'code_challenge_methods_supported' => Array
21
+ }
22
+ end
23
+
24
+ run do
25
+ skip_if well_known_configuration.blank?, 'No well-known configuration found'
26
+ config = JSON.parse(well_known_configuration)
27
+
28
+ required_capabilities.each do |key, type|
29
+ assert config.key?(key), "Well-known configuration does not include `#{key}`"
30
+ assert config[key].present?, "Well-known configuration field `#{key}` is blank"
31
+ assert config[key].is_a?(type), "Well-known `#{key}` must be type: #{type.to_s.downcase}"
32
+ end
33
+
34
+ assert config['grant_types_supported'].include?('authorization_code'),
35
+ 'Well-known `grant_types_supported` must include `authorization_code` grant type to indicate SMART App Launch Support'
36
+ assert config['code_challenge_methods_supported'].include?('S256'),
37
+ 'Well-known `code_challenge_methods_supported` must include `S256`'
38
+ assert config['code_challenge_methods_supported'].exclude?('plain'),
39
+ 'Well-known `code_challenge_methods_support` must not include `plain`'
40
+
41
+ if config['capabilities'].include?('sso-openid-connect')
42
+ assert config['issuer'].is_a?(String),
43
+ 'Well-known `issuer` field must be a string and present when server capabilities includes `sso-openid-connect`'
44
+ assert config['jwks_uri'].is_a?(String),
45
+ 'Well-known `jwks_uri` field must be a string and present when server capabilites includes `sso-openid-coneect`'
46
+ else
47
+ warning do
48
+ assert config['issuer'].nil?, 'Well-known `issuer` is omitted when server capabilites does not include `sso-openid-connect`'
49
+ end
50
+ end
51
+
52
+ non_string_capabilities = config['capabilities'].reject { |capability| capability.is_a? String }
53
+
54
+ assert non_string_capabilities.blank?, %(
55
+ Well-known `capabilities` field must be an array of strings, but found
56
+ non-string values:
57
+ #{non_string_capabilities.map { |value| "`#{value.nil? ? 'nil' : value}`" }.join(', ')}
58
+ )
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,46 @@
1
+ module SMARTAppLaunch
2
+ class WellKnownEndpointTest < Inferno::Test
3
+ title 'FHIR server makes SMART configuration available from well-known endpoint'
4
+ id :well_known_endpoint
5
+ description %(
6
+ The authorization endpoints accepted by a FHIR resource server can
7
+ be exposed as a Well-Known Uniform Resource Identifier
8
+ )
9
+ input :url,
10
+ title: 'FHIR Endpoint',
11
+ description: 'URL of the FHIR endpoint used by SMART applications'
12
+ output :well_known_configuration,
13
+ :well_known_authorization_url,
14
+ :well_known_introspection_url,
15
+ :well_known_management_url,
16
+ :well_known_registration_url,
17
+ :well_known_revocation_url,
18
+ :well_known_token_url
19
+ makes_request :smart_well_known_configuration
20
+
21
+ run do
22
+ well_known_configuration_url = "#{url.chomp('/')}/.well-known/smart-configuration"
23
+ get(well_known_configuration_url,
24
+ name: :smart_well_known_configuration,
25
+ headers: { 'Accept' => 'application/json' })
26
+ assert_response_status(200)
27
+
28
+ assert_valid_json(request.response_body)
29
+
30
+ config = JSON.parse(request.response_body)
31
+ output well_known_configuration: request.response_body,
32
+ well_known_authorization_url: config['authorization_endpoint'],
33
+ well_known_introspection_url: config['introspection_endpoint'],
34
+ well_known_management_url: config['management_endpoint'],
35
+ well_known_registration_url: config['registration_endpoint'],
36
+ well_known_revocation_url: config['revocation_endpoint'],
37
+ well_known_token_url: config['token_endpoint']
38
+
39
+ content_type = request.response_header('Content-Type')&.value
40
+
41
+ assert content_type.present?, 'No `Content-Type` header received.'
42
+ assert content_type.start_with?('application/json'),
43
+ "`Content-Type` must be `application/json`, but received: `#{content_type}`"
44
+ end
45
+ end
46
+ end
@@ -1,174 +1,4 @@
1
1
  require 'tls_test_kit'
2
2
 
3
- require_relative 'smart_app_launch/version'
4
- require_relative 'smart_app_launch/discovery_group'
5
- require_relative 'smart_app_launch/standalone_launch_group'
6
- require_relative 'smart_app_launch/ehr_launch_group'
7
- require_relative 'smart_app_launch/openid_connect_group'
8
- require_relative 'smart_app_launch/token_refresh_group'
9
-
10
- # TODO: Remove once this functionality is released in core:
11
- # https://github.com/inferno-framework/inferno-core/pull/86
12
- module Inferno
13
- module DSL
14
- module Runnable
15
- def required_inputs(prior_outputs = [])
16
- required_inputs =
17
- inputs
18
- .reject { |input| input_definitions[input][:optional] }
19
- .map { |input| config.input_name(input) }
20
- .reject { |input| prior_outputs.include?(input) }
21
- children_required_inputs = children.flat_map { |child| child.required_inputs(prior_outputs) }
22
- prior_outputs.concat(outputs.map { |output| config.output_name(output) })
23
- (required_inputs + children_required_inputs).flatten.uniq
24
- end
25
- end
26
- end
27
- end
28
-
29
- module SMARTAppLaunch
30
- class SMARTSuite < Inferno::TestSuite
31
- id 'smart'
32
- title 'SMART App Launch STU1'
33
- version VERSION
34
-
35
- resume_test_route :get, '/launch' do
36
- request.query_parameters['iss']
37
- end
38
-
39
- resume_test_route :get, '/redirect' do
40
- request.query_parameters['state']
41
- end
42
-
43
- config options: {
44
- redirect_uri: "#{Inferno::Application['base_url']}/custom/smart/redirect",
45
- launch_uri: "#{Inferno::Application['base_url']}/custom/smart/launch"
46
- }
47
-
48
- group do
49
- title 'Standalone Launch'
50
- id :smart_full_standalone_launch
51
-
52
- run_as_group
53
-
54
- group from: :smart_discovery
55
-
56
- group from: :smart_standalone_launch
57
-
58
- group from: :smart_openid_connect,
59
- config: {
60
- inputs: {
61
- id_token: { name: :standalone_id_token },
62
- client_id: { name: :standalone_client_id },
63
- requested_scopes: { name: :standalone_requested_scopes },
64
- access_token: { name: :standalone_access_token },
65
- smart_credentials: { name: :standalone_smart_credentials }
66
- }
67
- }
68
-
69
- group from: :smart_token_refresh,
70
- id: :smart_standalone_refresh_without_scopes,
71
- title: 'SMART Token Refresh Without Scopes',
72
- config: {
73
- inputs: {
74
- refresh_token: { name: :standalone_refresh_token },
75
- client_id: { name: :standalone_client_id },
76
- client_secret: { name: :standalone_client_secret },
77
- received_scopes: { name: :standalone_received_scopes }
78
- },
79
- outputs: {
80
- refresh_token: { name: :standalone_refresh_token },
81
- received_scopes: { name: :standalone_received_scopes },
82
- access_token: { name: :standalone_access_token },
83
- token_retrieval_time: { name: :standalone_token_retrieval_time },
84
- expires_in: { name: :standalone_expires_in },
85
- smart_credentials: { name: :standalone_smart_credentials }
86
- }
87
- }
88
-
89
- group from: :smart_token_refresh,
90
- id: :smart_standalone_refresh_with_scopes,
91
- title: 'SMART Token Refresh With Scopes',
92
- config: {
93
- options: { include_scopes: true },
94
- inputs: {
95
- refresh_token: { name: :standalone_refresh_token },
96
- client_id: { name: :standalone_client_id },
97
- client_secret: { name: :standalone_client_secret },
98
- received_scopes: { name: :standalone_received_scopes }
99
- },
100
- outputs: {
101
- refresh_token: { name: :standalone_refresh_token },
102
- received_scopes: { name: :standalone_received_scopes },
103
- access_token: { name: :standalone_access_token },
104
- token_retrieval_time: { name: :standalone_token_retrieval_time },
105
- expires_in: { name: :standalone_expires_in },
106
- smart_credentials: { name: :standalone_smart_credentials }
107
- }
108
- }
109
- end
110
-
111
- group do
112
- title 'EHR Launch'
113
- id :smart_full_ehr_launch
114
-
115
- run_as_group
116
-
117
- group from: :smart_discovery
118
-
119
- group from: :smart_ehr_launch
120
-
121
- group from: :smart_openid_connect,
122
- config: {
123
- inputs: {
124
- id_token: { name: :ehr_id_token },
125
- client_id: { name: :ehr_client_id },
126
- requested_scopes: { name: :ehr_requested_scopes },
127
- access_token: { name: :ehr_access_token },
128
- smart_credentials: { name: :ehr_smart_credentials }
129
- }
130
- }
131
-
132
- group from: :smart_token_refresh,
133
- id: :smart_ehr_refresh_without_scopes,
134
- title: 'SMART Token Refresh Without Scopes',
135
- config: {
136
- inputs: {
137
- refresh_token: { name: :ehr_refresh_token },
138
- client_id: { name: :ehr_client_id },
139
- client_secret: { name: :ehr_client_secret },
140
- received_scopes: { name: :ehr_received_scopes }
141
- },
142
- outputs: {
143
- refresh_token: { name: :ehr_refresh_token },
144
- received_scopes: { name: :ehr_received_scopes },
145
- access_token: { name: :ehr_access_token },
146
- token_retrieval_time: { name: :ehr_token_retrieval_time },
147
- expires_in: { name: :ehr_expires_in },
148
- smart_credentials: { name: :ehr_smart_credentials }
149
- }
150
- }
151
-
152
- group from: :smart_token_refresh,
153
- id: :smart_ehr_refresh_with_scopes,
154
- title: 'SMART Token Refresh With Scopes',
155
- config: {
156
- options: { include_scopes: true },
157
- inputs: {
158
- refresh_token: { name: :ehr_refresh_token },
159
- client_id: { name: :ehr_client_id },
160
- client_secret: { name: :ehr_client_secret },
161
- received_scopes: { name: :ehr_received_scopes }
162
- },
163
- outputs: {
164
- refresh_token: { name: :ehr_refresh_token },
165
- received_scopes: { name: :ehr_received_scopes },
166
- access_token: { name: :ehr_access_token },
167
- token_retrieval_time: { name: :ehr_token_retrieval_time },
168
- expires_in: { name: :ehr_expires_in },
169
- smart_credentials: { name: :ehr_smart_credentials }
170
- }
171
- }
172
- end
173
- end
174
- end
3
+ require_relative 'smart_app_launch/smart_stu1_suite'
4
+ require_relative 'smart_app_launch/smart_stu2_suite'
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.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen MacVicar
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-05-09 00:00:00.000000000 Z
11
+ date: 2022-07-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: inferno_core
@@ -134,8 +134,10 @@ files:
134
134
  - lib/smart_app_launch/app_launch_test.rb
135
135
  - lib/smart_app_launch/app_redirect_test.rb
136
136
  - lib/smart_app_launch/code_received_test.rb
137
- - lib/smart_app_launch/discovery_group.rb
137
+ - lib/smart_app_launch/discovery_stu1_group.rb
138
+ - lib/smart_app_launch/discovery_stu2_group.rb
138
139
  - lib/smart_app_launch/ehr_launch_group.rb
140
+ - lib/smart_app_launch/ehr_launch_group_stu2.rb
139
141
  - lib/smart_app_launch/launch_received_test.rb
140
142
  - lib/smart_app_launch/openid_connect_group.rb
141
143
  - lib/smart_app_launch/openid_decode_id_token_test.rb
@@ -145,7 +147,10 @@ files:
145
147
  - lib/smart_app_launch/openid_retrieve_jwks_test.rb
146
148
  - lib/smart_app_launch/openid_token_header_test.rb
147
149
  - lib/smart_app_launch/openid_token_payload_test.rb
150
+ - lib/smart_app_launch/smart_stu1_suite.rb
151
+ - lib/smart_app_launch/smart_stu2_suite.rb
148
152
  - lib/smart_app_launch/standalone_launch_group.rb
153
+ - lib/smart_app_launch/standalone_launch_group_stu2.rb
149
154
  - lib/smart_app_launch/token_exchange_test.rb
150
155
  - lib/smart_app_launch/token_payload_validation.rb
151
156
  - lib/smart_app_launch/token_refresh_body_test.rb
@@ -154,6 +159,9 @@ files:
154
159
  - lib/smart_app_launch/token_response_body_test.rb
155
160
  - lib/smart_app_launch/token_response_headers_test.rb
156
161
  - lib/smart_app_launch/version.rb
162
+ - lib/smart_app_launch/well_known_capabilities_stu1_test.rb
163
+ - lib/smart_app_launch/well_known_capabilities_stu2_test.rb
164
+ - lib/smart_app_launch/well_known_endpoint_test.rb
157
165
  - lib/smart_app_launch_test_kit.rb
158
166
  homepage: https://github.com/inferno_framework/smart-app-launch-test-kit
159
167
  licenses: