smart_app_launch_test_kit 0.5.1 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/config/presets/SMART_RunClientAgainstServer.json.erb +31 -0
- data/config/presets/SMART_RunServerAgainstClient.json.erb +42 -0
- data/config/presets/inferno_reference_server_preset.json +15 -86
- data/config/presets/inferno_reference_server_stu2_2_preset.json +20 -69
- data/config/presets/inferno_reference_server_stu2_preset.json +20 -69
- data/lib/smart_app_launch/app_redirect_test.rb +12 -44
- data/lib/smart_app_launch/app_redirect_test_stu2.rb +2 -17
- data/lib/smart_app_launch/backend_services_authorization_group.rb +33 -59
- data/lib/smart_app_launch/backend_services_authorization_request_builder.rb +22 -9
- data/lib/smart_app_launch/backend_services_authorization_request_success_test.rb +32 -24
- data/lib/smart_app_launch/backend_services_authorization_response_body_test.rb +23 -5
- data/lib/smart_app_launch/backend_services_invalid_client_assertion_test.rb +30 -25
- data/lib/smart_app_launch/backend_services_invalid_grant_type_test.rb +30 -24
- data/lib/smart_app_launch/backend_services_invalid_jwt_test.rb +31 -26
- data/lib/smart_app_launch/client_assertion_builder.rb +27 -12
- data/lib/smart_app_launch/client_stu2_2_suite.rb +79 -0
- data/lib/smart_app_launch/client_suite/client_access_group.rb +26 -0
- data/lib/smart_app_launch/client_suite/client_access_interaction_test.rb +64 -0
- data/lib/smart_app_launch/client_suite/client_registration_group.rb +15 -0
- data/lib/smart_app_launch/client_suite/client_registration_verification_test.rb +52 -0
- data/lib/smart_app_launch/client_suite/client_token_request_verification_test.rb +146 -0
- data/lib/smart_app_launch/client_suite/client_token_use_verification_test.rb +47 -0
- data/lib/smart_app_launch/cors_openid_fhir_user_claim_test.rb +2 -2
- data/lib/smart_app_launch/cors_token_exchange_test.rb +2 -2
- data/lib/smart_app_launch/discovery_stu1_group.rb +6 -2
- data/lib/smart_app_launch/docs/demo/FHIR Request.postman_collection.json +81 -0
- data/lib/smart_app_launch/docs/smart_stu2_2_client_suite_description.md +121 -0
- data/lib/smart_app_launch/ehr_launch_group.rb +41 -24
- data/lib/smart_app_launch/ehr_launch_group_stu2.rb +26 -10
- data/lib/smart_app_launch/ehr_launch_group_stu2_2.rb +0 -16
- data/lib/smart_app_launch/endpoints/echoing_fhir_responder.rb +52 -0
- data/lib/smart_app_launch/endpoints/mock_smart_server/token.rb +27 -0
- data/lib/smart_app_launch/endpoints/mock_smart_server.rb +217 -0
- data/lib/smart_app_launch/metadata.rb +2 -2
- data/lib/smart_app_launch/openid_fhir_user_claim_test.rb +5 -4
- data/lib/smart_app_launch/openid_token_payload_test.rb +6 -8
- data/lib/smart_app_launch/smart_stu1_suite.rb +32 -24
- data/lib/smart_app_launch/smart_stu2_2_suite.rb +57 -30
- data/lib/smart_app_launch/smart_stu2_suite.rb +57 -31
- data/lib/smart_app_launch/smart_tls_test.rb +14 -0
- data/lib/smart_app_launch/standalone_launch_group.rb +42 -25
- data/lib/smart_app_launch/standalone_launch_group_stu2.rb +26 -10
- data/lib/smart_app_launch/standalone_launch_group_stu2_2.rb +0 -16
- data/lib/smart_app_launch/tags.rb +7 -0
- data/lib/smart_app_launch/token_exchange_stu2_2_test.rb +5 -17
- data/lib/smart_app_launch/token_exchange_stu2_test.rb +8 -67
- data/lib/smart_app_launch/token_exchange_test.rb +18 -38
- data/lib/smart_app_launch/token_introspection_access_token_group.rb +12 -4
- data/lib/smart_app_launch/token_introspection_access_token_group_stu2_2.rb +9 -1
- data/lib/smart_app_launch/token_introspection_group.rb +2 -4
- data/lib/smart_app_launch/token_introspection_request_group.rb +2 -4
- data/lib/smart_app_launch/token_introspection_response_group.rb +64 -49
- data/lib/smart_app_launch/token_refresh_body_test.rb +9 -2
- data/lib/smart_app_launch/token_refresh_stu2_test.rb +10 -17
- data/lib/smart_app_launch/token_refresh_test.rb +19 -20
- data/lib/smart_app_launch/token_response_body_test.rb +14 -4
- data/lib/smart_app_launch/token_response_body_test_stu2_2.rb +3 -2
- data/lib/smart_app_launch/urls.rb +40 -0
- data/lib/smart_app_launch/version.rb +2 -2
- data/lib/smart_app_launch/well_known_endpoint_test.rb +11 -1
- data/lib/smart_app_launch_test_kit.rb +1 -0
- metadata +21 -4
@@ -7,8 +7,18 @@ module SMARTAppLaunch
|
|
7
7
|
new(...).authorization_request
|
8
8
|
end
|
9
9
|
|
10
|
-
attr_reader :encryption_method,
|
11
|
-
:
|
10
|
+
attr_reader :encryption_method,
|
11
|
+
:scope,
|
12
|
+
:iss,
|
13
|
+
:sub,
|
14
|
+
:aud,
|
15
|
+
:content_type,
|
16
|
+
:grant_type,
|
17
|
+
:client_assertion_type,
|
18
|
+
:exp,
|
19
|
+
:jti,
|
20
|
+
:kid,
|
21
|
+
:custom_jwks
|
12
22
|
|
13
23
|
def initialize(
|
14
24
|
encryption_method:,
|
@@ -21,7 +31,8 @@ module SMARTAppLaunch
|
|
21
31
|
client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
|
22
32
|
exp: 5.minutes.from_now,
|
23
33
|
jti: SecureRandom.hex(32),
|
24
|
-
kid: nil
|
34
|
+
kid: nil,
|
35
|
+
custom_jwks: nil
|
25
36
|
)
|
26
37
|
@encryption_method = encryption_method
|
27
38
|
@scope = scope
|
@@ -34,6 +45,7 @@ module SMARTAppLaunch
|
|
34
45
|
@exp = exp
|
35
46
|
@jti = jti
|
36
47
|
@kid = kid
|
48
|
+
@custom_jwks = custom_jwks
|
37
49
|
end
|
38
50
|
|
39
51
|
def authorization_request_headers
|
@@ -54,13 +66,14 @@ module SMARTAppLaunch
|
|
54
66
|
|
55
67
|
def client_assertion
|
56
68
|
@client_assertion ||= ClientAssertionBuilder.build(
|
57
|
-
client_auth_encryption_method: encryption_method,
|
58
|
-
iss
|
59
|
-
sub
|
60
|
-
aud
|
69
|
+
client_auth_encryption_method: encryption_method,
|
70
|
+
iss:,
|
71
|
+
sub:,
|
72
|
+
aud:,
|
61
73
|
exp: exp.to_i,
|
62
|
-
jti
|
63
|
-
kid
|
74
|
+
jti:,
|
75
|
+
kid:,
|
76
|
+
custom_jwks:
|
64
77
|
)
|
65
78
|
end
|
66
79
|
|
@@ -1,8 +1,8 @@
|
|
1
1
|
require_relative 'backend_services_authorization_request_builder'
|
2
2
|
require_relative 'backend_services_authorization_group'
|
3
3
|
|
4
|
-
module SMARTAppLaunch
|
5
|
-
class BackendServicesAuthorizationRequestSuccessTest < Inferno::Test
|
4
|
+
module SMARTAppLaunch
|
5
|
+
class BackendServicesAuthorizationRequestSuccessTest < Inferno::Test
|
6
6
|
id :smart_backend_services_auth_request_success
|
7
7
|
title 'Authorization request succeeds when supplied correct information'
|
8
8
|
description <<~DESCRIPTION
|
@@ -10,32 +10,40 @@ module SMARTAppLaunch
|
|
10
10
|
states "If the access token request is valid and authorized, the authorization server SHALL issue an access token in response."
|
11
11
|
DESCRIPTION
|
12
12
|
|
13
|
-
input :
|
14
|
-
:
|
15
|
-
:
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
13
|
+
input :smart_auth_info,
|
14
|
+
type: :auth_info,
|
15
|
+
options: {
|
16
|
+
mode: 'auth',
|
17
|
+
components: [
|
18
|
+
{
|
19
|
+
name: :auth_type,
|
20
|
+
default: 'backend_services',
|
21
|
+
locked: 'true'
|
22
|
+
}
|
23
|
+
]
|
24
|
+
}
|
25
|
+
|
26
|
+
output :authentication_response, :smart_auth_info
|
25
27
|
|
26
28
|
run do
|
27
|
-
post_request_content = BackendServicesAuthorizationRequestBuilder.build(
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
29
|
+
post_request_content = BackendServicesAuthorizationRequestBuilder.build(
|
30
|
+
encryption_method: smart_auth_info.encryption_algorithm,
|
31
|
+
scope: smart_auth_info.requested_scopes,
|
32
|
+
iss: smart_auth_info.client_id,
|
33
|
+
sub: smart_auth_info.client_id,
|
34
|
+
aud: smart_auth_info.token_url,
|
35
|
+
kid: smart_auth_info.kid,
|
36
|
+
custom_jwks: smart_auth_info.jwks
|
37
|
+
)
|
38
|
+
|
39
|
+
authentication_response = post(smart_auth_info.token_url, **post_request_content)
|
35
40
|
|
36
41
|
assert_response_status([200, 201])
|
37
42
|
|
38
|
-
|
43
|
+
smart_auth_info.issue_time = Time.now
|
44
|
+
|
45
|
+
output authentication_response: authentication_response.response_body,
|
46
|
+
smart_auth_info: smart_auth_info
|
39
47
|
end
|
40
48
|
end
|
41
|
-
end
|
49
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require_relative 'backend_services_authorization_request_builder'
|
2
2
|
|
3
|
-
module SMARTAppLaunch
|
4
|
-
class BackendServicesAuthorizationResponseBodyTest < Inferno::Test
|
3
|
+
module SMARTAppLaunch
|
4
|
+
class BackendServicesAuthorizationResponseBodyTest < Inferno::Test
|
5
5
|
id :smart_backend_services_auth_response_body
|
6
6
|
title 'Authorization request response body contains required information encoded in JSON'
|
7
7
|
description <<~DESCRIPTION
|
@@ -17,7 +17,19 @@ module SMARTAppLaunch
|
|
17
17
|
DESCRIPTION
|
18
18
|
|
19
19
|
input :authentication_response
|
20
|
-
|
20
|
+
input :smart_auth_info,
|
21
|
+
type: :auth_info,
|
22
|
+
options: {
|
23
|
+
mode: 'auth',
|
24
|
+
components: [
|
25
|
+
{
|
26
|
+
name: :auth_type,
|
27
|
+
default: 'backend_services',
|
28
|
+
locked: 'true'
|
29
|
+
}
|
30
|
+
]
|
31
|
+
}
|
32
|
+
output :bearer_token, :smart_auth_info, :received_scopes
|
21
33
|
|
22
34
|
run do
|
23
35
|
skip_if authentication_response.blank?, 'No authentication response received.'
|
@@ -26,9 +38,15 @@ module SMARTAppLaunch
|
|
26
38
|
response_body = JSON.parse(authentication_response)
|
27
39
|
|
28
40
|
access_token = response_body['access_token']
|
41
|
+
received_scopes = response_body['scope']
|
42
|
+
expires_in = response_body['expires_in']
|
43
|
+
|
29
44
|
assert access_token.present?, 'Token response did not contain access_token as required'
|
30
45
|
|
31
|
-
|
46
|
+
smart_auth_info.access_token = access_token
|
47
|
+
smart_auth_info.expires_in = expires_in
|
48
|
+
|
49
|
+
output bearer_token: access_token, smart_auth_info: smart_auth_info, received_scopes: received_scopes
|
32
50
|
|
33
51
|
required_keys = ['token_type', 'expires_in', 'scope']
|
34
52
|
|
@@ -37,4 +55,4 @@ module SMARTAppLaunch
|
|
37
55
|
end
|
38
56
|
end
|
39
57
|
end
|
40
|
-
end
|
58
|
+
end
|
@@ -1,45 +1,50 @@
|
|
1
1
|
require_relative 'backend_services_authorization_request_builder'
|
2
2
|
|
3
|
-
module SMARTAppLaunch
|
4
|
-
class BackendServicesInvalidClientAssertionTest < Inferno::Test
|
3
|
+
module SMARTAppLaunch
|
4
|
+
class BackendServicesInvalidClientAssertionTest < Inferno::Test
|
5
5
|
id :smart_backend_services_invalid_client_assertion
|
6
6
|
title 'Authorization request fails when supplied invalid client_assertion_type'
|
7
7
|
description <<~DESCRIPTION
|
8
8
|
The [SMART App Launch 2.0.0 IG specification for Backend Services](https://hl7.org/fhir/smart-app-launch/STU2/backend-services.html#request-1)
|
9
|
-
defines the required fields for the authorization request, made via HTTP POST to authorization
|
9
|
+
defines the required fields for the authorization request, made via HTTP POST to authorization
|
10
10
|
token endpoint.
|
11
11
|
This includes the `client_assertion_type` parameter, where the value must be `urn:ietf:params:oauth:client-assertion-type:jwt-bearer`.
|
12
12
|
|
13
|
-
The [OAuth 2.0 Authorization Framework Section 4.3.3](https://datatracker.ietf.org/doc/html/rfc6749#section-4.3.3)
|
13
|
+
The [OAuth 2.0 Authorization Framework Section 4.3.3](https://datatracker.ietf.org/doc/html/rfc6749#section-4.3.3)
|
14
14
|
describes the proper response for an invalid request in the client credentials grant flow:
|
15
15
|
|
16
16
|
"If the request failed client authentication or is invalid, the authorization server returns an
|
17
17
|
error response as described in [Section 5.2](https://tools.ietf.org/html/rfc6749#section-5.2)."
|
18
18
|
DESCRIPTION
|
19
19
|
|
20
|
-
input :
|
21
|
-
:
|
22
|
-
:
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
20
|
+
input :smart_auth_info,
|
21
|
+
type: :auth_info,
|
22
|
+
options: {
|
23
|
+
mode: 'auth',
|
24
|
+
components: [
|
25
|
+
{
|
26
|
+
name: :auth_type,
|
27
|
+
default: 'backend_services',
|
28
|
+
locked: 'true'
|
29
|
+
}
|
30
|
+
]
|
31
|
+
}
|
30
32
|
|
31
33
|
run do
|
32
|
-
post_request_content = BackendServicesAuthorizationRequestBuilder.build(
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
34
|
+
post_request_content = BackendServicesAuthorizationRequestBuilder.build(
|
35
|
+
encryption_method: smart_auth_info.encryption_algorithm,
|
36
|
+
scope: smart_auth_info.requested_scopes,
|
37
|
+
iss: smart_auth_info.client_id,
|
38
|
+
sub: smart_auth_info.client_id,
|
39
|
+
aud: smart_auth_info.token_url,
|
40
|
+
client_assertion_type: 'not_an_assertion_type',
|
41
|
+
kid: smart_auth_info.kid,
|
42
|
+
custom_jwks: smart_auth_info.jwks
|
43
|
+
)
|
44
|
+
|
45
|
+
post(smart_auth_info.token_url, **post_request_content)
|
41
46
|
|
42
47
|
assert_response_status(400)
|
43
|
-
end
|
48
|
+
end
|
44
49
|
end
|
45
|
-
end
|
50
|
+
end
|
@@ -1,45 +1,51 @@
|
|
1
1
|
require_relative 'backend_services_authorization_request_builder'
|
2
2
|
|
3
|
-
module SMARTAppLaunch
|
4
|
-
class BackendServicesInvalidGrantTypeTest < Inferno::Test
|
3
|
+
module SMARTAppLaunch
|
4
|
+
class BackendServicesInvalidGrantTypeTest < Inferno::Test
|
5
5
|
id :smart_backend_services_invalid_grant_type
|
6
6
|
title 'Authorization request fails when client supplies invalid grant_type'
|
7
7
|
description <<~DESCRIPTION
|
8
8
|
The [SMART App Launch 2.0.0 IG section on Backend Services](https://hl7.org/fhir/smart-app-launch/STU2/backend-services.html#request-1)
|
9
|
-
defines the required fields for the authorization request, made via HTTP POST to authorization
|
9
|
+
defines the required fields for the authorization request, made via HTTP POST to authorization
|
10
10
|
token endpoint.
|
11
11
|
This includes the `grant_type` parameter, where the value must be `client_credentials`.
|
12
12
|
|
13
|
-
The [OAuth 2.0 Authorization Framework Section 4.3.3](https://datatracker.ietf.org/doc/html/rfc6749#section-4.3.3)
|
13
|
+
The [OAuth 2.0 Authorization Framework Section 4.3.3](https://datatracker.ietf.org/doc/html/rfc6749#section-4.3.3)
|
14
14
|
describes the proper response for an invalid request in the client credentials grant flow:
|
15
15
|
|
16
16
|
"If the request failed client authentication or is invalid, the authorization server returns an
|
17
17
|
error response as described in [Section 5.2](https://tools.ietf.org/html/rfc6749#section-5.2)."
|
18
18
|
DESCRIPTION
|
19
19
|
|
20
|
-
input :
|
21
|
-
:
|
22
|
-
:
|
23
|
-
|
24
|
-
|
25
|
-
|
20
|
+
input :smart_auth_info,
|
21
|
+
type: :auth_info,
|
22
|
+
options: {
|
23
|
+
mode: 'auth',
|
24
|
+
components: [
|
25
|
+
{
|
26
|
+
name: :auth_type,
|
27
|
+
default: 'backend_services',
|
28
|
+
locked: 'true'
|
29
|
+
}
|
30
|
+
]
|
31
|
+
}
|
26
32
|
|
27
|
-
http_client :token_endpoint do
|
28
|
-
url :smart_token_url
|
29
|
-
end
|
30
33
|
|
31
34
|
run do
|
32
|
-
post_request_content = BackendServicesAuthorizationRequestBuilder.build(
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
35
|
+
post_request_content = BackendServicesAuthorizationRequestBuilder.build(
|
36
|
+
encryption_method: smart_auth_info.encryption_algorithm,
|
37
|
+
scope: smart_auth_info.requested_scopes,
|
38
|
+
iss: smart_auth_info.client_id,
|
39
|
+
sub: smart_auth_info.client_id,
|
40
|
+
aud: smart_auth_info.token_url,
|
41
|
+
grant_type: 'not_a_grant_type',
|
42
|
+
kid: smart_auth_info.kid,
|
43
|
+
custom_jwks: smart_auth_info.jwks
|
44
|
+
)
|
45
|
+
|
46
|
+
post(smart_auth_info.token_url, **post_request_content)
|
41
47
|
|
42
48
|
assert_response_status(400)
|
43
|
-
end
|
49
|
+
end
|
44
50
|
end
|
45
|
-
end
|
51
|
+
end
|
@@ -1,15 +1,15 @@
|
|
1
1
|
require_relative 'backend_services_authorization_request_builder'
|
2
2
|
|
3
|
-
module SMARTAppLaunch
|
4
|
-
class BackendServicesInvalidJWTTest < Inferno::Test
|
3
|
+
module SMARTAppLaunch
|
4
|
+
class BackendServicesInvalidJWTTest < Inferno::Test
|
5
5
|
id :smart_backend_services_invalid_jwt
|
6
6
|
title 'Authorization request fails when client supplies invalid JWT token'
|
7
7
|
description <<~DESCRIPTION
|
8
8
|
The [SMART App Launch 2.0.0 IG section on Backend Services](https://hl7.org/fhir/smart-app-launch/STU2/backend-services.html#request-1)
|
9
|
-
defines the required fields for the authorization request, made via HTTP POST to authorization
|
9
|
+
defines the required fields for the authorization request, made via HTTP POST to authorization
|
10
10
|
token endpoint.
|
11
11
|
This includes the `client_assertion` parameter, where the value must be
|
12
|
-
a valid JWT as specified in
|
12
|
+
a valid JWT as specified in
|
13
13
|
[Asymmetric (public key) Client Authentication](https://hl7.org/fhir/smart-app-launch/STU2/client-confidential-asymmetric.html#authenticating-to-the-token-endpoint)
|
14
14
|
The JWT SHALL include the following claims, and SHALL be signed with the client’s private key.
|
15
15
|
|
@@ -21,36 +21,41 @@ module SMARTAppLaunch
|
|
21
21
|
| `exp` | required | Expiration time integer for this authentication JWT, expressed in seconds since the "Epoch" (1970-01-01T00:00:00Z UTC). This time SHALL be no more than five minutes in the future. |
|
22
22
|
| `jti` | required | A nonce string value that uniquely identifies this authentication JWT. |
|
23
23
|
|
24
|
-
The [OAuth 2.0 Authorization Framework Section 4.3.3](https://datatracker.ietf.org/doc/html/rfc6749#section-4.3.3)
|
24
|
+
The [OAuth 2.0 Authorization Framework Section 4.3.3](https://datatracker.ietf.org/doc/html/rfc6749#section-4.3.3)
|
25
25
|
describes the proper response for an invalid request in the client credentials grant flow:
|
26
26
|
|
27
27
|
"If the request failed client authentication or is invalid, the authorization server returns an
|
28
28
|
error response as described in [Section 5.2](https://tools.ietf.org/html/rfc6749#section-5.2)."
|
29
29
|
DESCRIPTION
|
30
30
|
|
31
|
-
input :
|
32
|
-
:
|
33
|
-
:
|
34
|
-
|
35
|
-
|
36
|
-
|
31
|
+
input :smart_auth_info,
|
32
|
+
type: :auth_info,
|
33
|
+
options: {
|
34
|
+
mode: 'auth',
|
35
|
+
components: [
|
36
|
+
{
|
37
|
+
name: :auth_type,
|
38
|
+
default: 'backend_services',
|
39
|
+
locked: 'true'
|
40
|
+
}
|
41
|
+
]
|
42
|
+
}
|
37
43
|
|
38
|
-
http_client :token_endpoint do
|
39
|
-
url :smart_token_url
|
40
|
-
end
|
41
|
-
|
42
44
|
run do
|
43
|
-
post_request_content = BackendServicesAuthorizationRequestBuilder.build(
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
45
|
+
post_request_content = BackendServicesAuthorizationRequestBuilder.build(
|
46
|
+
encryption_method: smart_auth_info.encryption_algorithm,
|
47
|
+
scope: smart_auth_info.requested_scopes,
|
48
|
+
iss: smart_auth_info.client_id,
|
49
|
+
sub: smart_auth_info.client_id,
|
50
|
+
aud: smart_auth_info.token_url,
|
51
|
+
client_assertion_type: 'not_an_assertion_type',
|
52
|
+
kid: smart_auth_info.kid,
|
53
|
+
custom_jwks: smart_auth_info.jwks
|
54
|
+
)
|
55
|
+
|
56
|
+
post(smart_auth_info.token_url, **post_request_content)
|
52
57
|
|
53
58
|
assert_response_status(400)
|
54
|
-
end
|
59
|
+
end
|
55
60
|
end
|
56
|
-
end
|
61
|
+
end
|
@@ -17,7 +17,8 @@ module SMARTAppLaunch
|
|
17
17
|
:iss,
|
18
18
|
:jti,
|
19
19
|
:sub,
|
20
|
-
:kid
|
20
|
+
:kid,
|
21
|
+
:custom_jwks
|
21
22
|
|
22
23
|
def initialize(
|
23
24
|
client_auth_encryption_method:,
|
@@ -26,7 +27,8 @@ module SMARTAppLaunch
|
|
26
27
|
aud:,
|
27
28
|
exp: 5.minutes.from_now.to_i,
|
28
29
|
jti: SecureRandom.hex(32),
|
29
|
-
kid: nil
|
30
|
+
kid: nil,
|
31
|
+
custom_jwks: nil
|
30
32
|
)
|
31
33
|
@client_auth_encryption_method = client_auth_encryption_method
|
32
34
|
@iss = iss
|
@@ -37,14 +39,25 @@ module SMARTAppLaunch
|
|
37
39
|
@client_assertion_type = client_assertion_type
|
38
40
|
@exp = exp
|
39
41
|
@jti = jti
|
40
|
-
@kid = kid
|
42
|
+
@kid = kid.presence
|
43
|
+
@custom_jwks = custom_jwks
|
44
|
+
end
|
45
|
+
|
46
|
+
def jwks
|
47
|
+
@jwks ||=
|
48
|
+
if custom_jwks.present?
|
49
|
+
JWT::JWK::Set.new(JSON.parse(custom_jwks))
|
50
|
+
else
|
51
|
+
JWKS.jwks
|
52
|
+
end
|
41
53
|
end
|
42
54
|
|
43
55
|
def private_key
|
44
|
-
@private_key ||=
|
45
|
-
|
46
|
-
|
47
|
-
|
56
|
+
@private_key ||=
|
57
|
+
jwks
|
58
|
+
.select { |key| key[:key_ops]&.include?('sign') }
|
59
|
+
.select { |key| key[:alg] == client_auth_encryption_method }
|
60
|
+
.find { |key| !kid || key[:kid] == kid }
|
48
61
|
end
|
49
62
|
|
50
63
|
def jwt_payload
|
@@ -52,11 +65,12 @@ module SMARTAppLaunch
|
|
52
65
|
end
|
53
66
|
|
54
67
|
def signing_key
|
55
|
-
private_key
|
56
|
-
|
57
|
-
|
68
|
+
if private_key.nil?
|
69
|
+
raise Inferno::Exceptions::AssertionException,
|
70
|
+
"No signing key found for inputs: encryption method = '#{client_auth_encryption_method}' and kid = '#{kid}'"
|
58
71
|
end
|
59
|
-
|
72
|
+
|
73
|
+
@private_key.signing_key
|
60
74
|
end
|
61
75
|
|
62
76
|
def key_id
|
@@ -65,7 +79,8 @@ module SMARTAppLaunch
|
|
65
79
|
|
66
80
|
def client_assertion
|
67
81
|
@client_assertion ||=
|
68
|
-
JWT.encode jwt_payload, signing_key, client_auth_encryption_method,
|
82
|
+
JWT.encode jwt_payload, signing_key, client_auth_encryption_method,
|
83
|
+
{ alg: client_auth_encryption_method, kid: key_id, typ: 'JWT' }
|
69
84
|
end
|
70
85
|
end
|
71
86
|
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require_relative 'endpoints/mock_smart_server/token'
|
2
|
+
require_relative 'endpoints/echoing_fhir_responder'
|
3
|
+
require_relative 'urls'
|
4
|
+
require_relative 'client_suite/client_registration_group'
|
5
|
+
require_relative 'client_suite/client_access_group'
|
6
|
+
|
7
|
+
module SMARTAppLaunch
|
8
|
+
class SMARTClientSTU22Suite < Inferno::TestSuite
|
9
|
+
id :smart_client_stu2_2 # rubocop:disable Naming/VariableNumber
|
10
|
+
title 'SMART App Launch STU2.2 Client'
|
11
|
+
description File.read(File.join(__dir__, 'docs', 'smart_stu2_2_client_suite_description.md'))
|
12
|
+
|
13
|
+
links [
|
14
|
+
{
|
15
|
+
type: 'source_code',
|
16
|
+
label: 'Open Source',
|
17
|
+
url: 'https://github.com/inferno-framework/smart-app-launch-test-kit/'
|
18
|
+
},
|
19
|
+
{
|
20
|
+
type: 'report_issue',
|
21
|
+
label: 'Report Issue',
|
22
|
+
url: 'https://github.com/inferno-framework/smart-app-launch-test-kit/issues/'
|
23
|
+
},
|
24
|
+
{
|
25
|
+
type: 'download',
|
26
|
+
label: 'Download',
|
27
|
+
url: 'https://github.com/inferno-framework/smart-app-launch-test-kit/releases/'
|
28
|
+
},
|
29
|
+
{
|
30
|
+
type: 'ig',
|
31
|
+
label: 'Implementation Guide',
|
32
|
+
url: 'https://hl7.org/fhir/smart-app-launch/STU2.2/'
|
33
|
+
}
|
34
|
+
]
|
35
|
+
|
36
|
+
route(:get, SMART_DISCOVERY_PATH, ->(_env) {MockSMARTServer.smart_server_metadata(id) })
|
37
|
+
suite_endpoint :post, TOKEN_PATH, MockSMARTServer::TokenEndpoint
|
38
|
+
suite_endpoint :get, FHIR_PATH, EchoingFHIRResponderEndpoint
|
39
|
+
suite_endpoint :post, FHIR_PATH, EchoingFHIRResponderEndpoint
|
40
|
+
suite_endpoint :put, FHIR_PATH, EchoingFHIRResponderEndpoint
|
41
|
+
suite_endpoint :delete, FHIR_PATH, EchoingFHIRResponderEndpoint
|
42
|
+
suite_endpoint :get, "#{FHIR_PATH}/:one", EchoingFHIRResponderEndpoint
|
43
|
+
suite_endpoint :post, "#{FHIR_PATH}/:one", EchoingFHIRResponderEndpoint
|
44
|
+
suite_endpoint :put, "#{FHIR_PATH}/:one", EchoingFHIRResponderEndpoint
|
45
|
+
suite_endpoint :delete, "#{FHIR_PATH}/:one", EchoingFHIRResponderEndpoint
|
46
|
+
suite_endpoint :get, "#{FHIR_PATH}/:one/:two", EchoingFHIRResponderEndpoint
|
47
|
+
suite_endpoint :post, "#{FHIR_PATH}/:one/:two", EchoingFHIRResponderEndpoint
|
48
|
+
suite_endpoint :put, "#{FHIR_PATH}/:one/:two", EchoingFHIRResponderEndpoint
|
49
|
+
suite_endpoint :delete, "#{FHIR_PATH}/:one/:two", EchoingFHIRResponderEndpoint
|
50
|
+
suite_endpoint :get, "#{FHIR_PATH}/:one/:two/:three", EchoingFHIRResponderEndpoint
|
51
|
+
suite_endpoint :post, "#{FHIR_PATH}/:one/:two/:three", EchoingFHIRResponderEndpoint
|
52
|
+
suite_endpoint :put, "#{FHIR_PATH}/:one/:two/:three", EchoingFHIRResponderEndpoint
|
53
|
+
suite_endpoint :delete, "#{FHIR_PATH}/:one/:two/:three", EchoingFHIRResponderEndpoint
|
54
|
+
|
55
|
+
resume_test_route :get, RESUME_PASS_PATH do |request|
|
56
|
+
request.query_parameters['token']
|
57
|
+
end
|
58
|
+
|
59
|
+
resume_test_route :get, RESUME_FAIL_PATH, result: 'fail' do |request|
|
60
|
+
request.query_parameters['token']
|
61
|
+
end
|
62
|
+
|
63
|
+
group do
|
64
|
+
title 'SMART Backend Services'
|
65
|
+
description %(
|
66
|
+
During these tests, the client will use SMART Backend Services
|
67
|
+
to access a FHIR API. Clients will provide registeration details,
|
68
|
+
obtain an access token, and use the access token when making a
|
69
|
+
request to a FHIR API.
|
70
|
+
)
|
71
|
+
|
72
|
+
input :smart_jwk_set,
|
73
|
+
optional: false
|
74
|
+
|
75
|
+
group from: :smart_client_registration
|
76
|
+
group from: :smart_client_access
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require_relative 'client_access_interaction_test'
|
2
|
+
require_relative 'client_token_request_verification_test'
|
3
|
+
require_relative 'client_token_use_verification_test'
|
4
|
+
|
5
|
+
module SMARTAppLaunch
|
6
|
+
class SMARTClientAccess < Inferno::TestGroup
|
7
|
+
id :smart_client_access
|
8
|
+
title 'Client Access'
|
9
|
+
description %(
|
10
|
+
During these tests, the client system will access Inferno's simulated
|
11
|
+
FHIR server by requesting an access token and making a FHIR request.
|
12
|
+
Inferno will then verify that any token requests made were conformant
|
13
|
+
and that a token returned from a token request was used on an access request.
|
14
|
+
)
|
15
|
+
|
16
|
+
run_as_group
|
17
|
+
|
18
|
+
input :smart_jwk_set,
|
19
|
+
optional: true,
|
20
|
+
locked: true
|
21
|
+
|
22
|
+
test from: :smart_client_access_interaction
|
23
|
+
test from: :smart_client_token_request_verification
|
24
|
+
test from: :smart_client_token_use_verification
|
25
|
+
end
|
26
|
+
end
|