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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/config/presets/SMART_RunClientAgainstServer.json.erb +31 -0
  3. data/config/presets/SMART_RunServerAgainstClient.json.erb +42 -0
  4. data/config/presets/inferno_reference_server_preset.json +15 -86
  5. data/config/presets/inferno_reference_server_stu2_2_preset.json +20 -69
  6. data/config/presets/inferno_reference_server_stu2_preset.json +20 -69
  7. data/lib/smart_app_launch/app_redirect_test.rb +12 -44
  8. data/lib/smart_app_launch/app_redirect_test_stu2.rb +2 -17
  9. data/lib/smart_app_launch/backend_services_authorization_group.rb +33 -59
  10. data/lib/smart_app_launch/backend_services_authorization_request_builder.rb +22 -9
  11. data/lib/smart_app_launch/backend_services_authorization_request_success_test.rb +32 -24
  12. data/lib/smart_app_launch/backend_services_authorization_response_body_test.rb +23 -5
  13. data/lib/smart_app_launch/backend_services_invalid_client_assertion_test.rb +30 -25
  14. data/lib/smart_app_launch/backend_services_invalid_grant_type_test.rb +30 -24
  15. data/lib/smart_app_launch/backend_services_invalid_jwt_test.rb +31 -26
  16. data/lib/smart_app_launch/client_assertion_builder.rb +27 -12
  17. data/lib/smart_app_launch/client_stu2_2_suite.rb +79 -0
  18. data/lib/smart_app_launch/client_suite/client_access_group.rb +26 -0
  19. data/lib/smart_app_launch/client_suite/client_access_interaction_test.rb +64 -0
  20. data/lib/smart_app_launch/client_suite/client_registration_group.rb +15 -0
  21. data/lib/smart_app_launch/client_suite/client_registration_verification_test.rb +52 -0
  22. data/lib/smart_app_launch/client_suite/client_token_request_verification_test.rb +146 -0
  23. data/lib/smart_app_launch/client_suite/client_token_use_verification_test.rb +47 -0
  24. data/lib/smart_app_launch/cors_openid_fhir_user_claim_test.rb +2 -2
  25. data/lib/smart_app_launch/cors_token_exchange_test.rb +2 -2
  26. data/lib/smart_app_launch/discovery_stu1_group.rb +6 -2
  27. data/lib/smart_app_launch/docs/demo/FHIR Request.postman_collection.json +81 -0
  28. data/lib/smart_app_launch/docs/smart_stu2_2_client_suite_description.md +121 -0
  29. data/lib/smart_app_launch/ehr_launch_group.rb +41 -24
  30. data/lib/smart_app_launch/ehr_launch_group_stu2.rb +26 -10
  31. data/lib/smart_app_launch/ehr_launch_group_stu2_2.rb +0 -16
  32. data/lib/smart_app_launch/endpoints/echoing_fhir_responder.rb +52 -0
  33. data/lib/smart_app_launch/endpoints/mock_smart_server/token.rb +27 -0
  34. data/lib/smart_app_launch/endpoints/mock_smart_server.rb +217 -0
  35. data/lib/smart_app_launch/metadata.rb +2 -2
  36. data/lib/smart_app_launch/openid_fhir_user_claim_test.rb +5 -4
  37. data/lib/smart_app_launch/openid_token_payload_test.rb +6 -8
  38. data/lib/smart_app_launch/smart_stu1_suite.rb +32 -24
  39. data/lib/smart_app_launch/smart_stu2_2_suite.rb +57 -30
  40. data/lib/smart_app_launch/smart_stu2_suite.rb +57 -31
  41. data/lib/smart_app_launch/smart_tls_test.rb +14 -0
  42. data/lib/smart_app_launch/standalone_launch_group.rb +42 -25
  43. data/lib/smart_app_launch/standalone_launch_group_stu2.rb +26 -10
  44. data/lib/smart_app_launch/standalone_launch_group_stu2_2.rb +0 -16
  45. data/lib/smart_app_launch/tags.rb +7 -0
  46. data/lib/smart_app_launch/token_exchange_stu2_2_test.rb +5 -17
  47. data/lib/smart_app_launch/token_exchange_stu2_test.rb +8 -67
  48. data/lib/smart_app_launch/token_exchange_test.rb +18 -38
  49. data/lib/smart_app_launch/token_introspection_access_token_group.rb +12 -4
  50. data/lib/smart_app_launch/token_introspection_access_token_group_stu2_2.rb +9 -1
  51. data/lib/smart_app_launch/token_introspection_group.rb +2 -4
  52. data/lib/smart_app_launch/token_introspection_request_group.rb +2 -4
  53. data/lib/smart_app_launch/token_introspection_response_group.rb +64 -49
  54. data/lib/smart_app_launch/token_refresh_body_test.rb +9 -2
  55. data/lib/smart_app_launch/token_refresh_stu2_test.rb +10 -17
  56. data/lib/smart_app_launch/token_refresh_test.rb +19 -20
  57. data/lib/smart_app_launch/token_response_body_test.rb +14 -4
  58. data/lib/smart_app_launch/token_response_body_test_stu2_2.rb +3 -2
  59. data/lib/smart_app_launch/urls.rb +40 -0
  60. data/lib/smart_app_launch/version.rb +2 -2
  61. data/lib/smart_app_launch/well_known_endpoint_test.rb +11 -1
  62. data/lib/smart_app_launch_test_kit.rb +1 -0
  63. metadata +21 -4
@@ -7,8 +7,18 @@ module SMARTAppLaunch
7
7
  new(...).authorization_request
8
8
  end
9
9
 
10
- attr_reader :encryption_method, :scope, :iss, :sub, :aud, :content_type, :grant_type, :client_assertion_type, :exp,
11
- :jti, :kid
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: iss,
59
- sub: sub,
60
- aud: aud,
69
+ client_auth_encryption_method: encryption_method,
70
+ iss:,
71
+ sub:,
72
+ aud:,
61
73
  exp: exp.to_i,
62
- jti: jti,
63
- kid: 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 :client_auth_encryption_method,
14
- :backend_services_requested_scope,
15
- :backend_services_client_id,
16
- :smart_token_url
17
- input :backend_services_jwks_kid,
18
- optional: true
19
-
20
- output :authentication_response
21
-
22
- http_client :token_endpoint do
23
- url :smart_token_url
24
- end
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(encryption_method: client_auth_encryption_method,
28
- scope: backend_services_requested_scope,
29
- iss: backend_services_client_id,
30
- sub: backend_services_client_id,
31
- aud: smart_token_url,
32
- kid: backend_services_jwks_kid)
33
-
34
- authentication_response = post(**{ client: :token_endpoint }.merge(post_request_content))
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
- output authentication_response: authentication_response.response_body
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
- output :bearer_token
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
- output bearer_token: access_token
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 :client_auth_encryption_method,
21
- :backend_services_requested_scope,
22
- :backend_services_client_id,
23
- :smart_token_url
24
- input :backend_services_jwks_kid,
25
- optional: true
26
-
27
- http_client :token_endpoint do
28
- url :smart_token_url
29
- end
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(encryption_method: client_auth_encryption_method,
33
- scope: backend_services_requested_scope,
34
- iss: backend_services_client_id,
35
- sub: backend_services_client_id,
36
- aud: smart_token_url,
37
- client_assertion_type: 'not_an_assertion_type',
38
- kid: backend_services_jwks_kid)
39
-
40
- post(**{ client: :token_endpoint }.merge(post_request_content))
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 :client_auth_encryption_method,
21
- :backend_services_requested_scope,
22
- :backend_services_client_id,
23
- :smart_token_url
24
- input :backend_services_jwks_kid,
25
- optional: true
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(encryption_method: client_auth_encryption_method,
33
- scope: backend_services_requested_scope,
34
- iss: backend_services_client_id,
35
- sub: backend_services_client_id,
36
- aud: smart_token_url,
37
- grant_type: 'not_a_grant_type',
38
- kid: backend_services_jwks_kid)
39
-
40
- post(**{ client: :token_endpoint }.merge(post_request_content))
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 :client_auth_encryption_method,
32
- :backend_services_requested_scope,
33
- :backend_services_client_id,
34
- :smart_token_url
35
- input :backend_services_jwks_kid,
36
- optional: true
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(encryption_method: client_auth_encryption_method,
44
- scope: backend_services_requested_scope,
45
- iss: backend_services_client_id,
46
- sub: backend_services_client_id,
47
- aud: smart_token_url,
48
- client_assertion_type: 'not_an_assertion_type',
49
- kid: backend_services_jwks_kid)
50
-
51
- post(**{ client: :token_endpoint }.merge(post_request_content))
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 ||= JWKS.jwks
45
- .select { |key| key[:key_ops]&.include?('sign') }
46
- .select { |key| key[:alg] == client_auth_encryption_method }
47
- .find { |key| !kid || key[:kid] == kid }
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
- if @private_key.nil?
57
- raise Inferno::Exceptions::AssertionException, "No signing key found for inputs: encryption method = '#{client_auth_encryption_method}' and kid = '#{kid}'"
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
- return @private_key.signing_key
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, { alg: client_auth_encryption_method, kid: key_id, typ: 'JWT' }
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