smart_app_launch_test_kit 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
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: