smart_app_launch_test_kit 0.6.1 → 0.6.2

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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/config/presets/SMART_RunClientAgainstServer.json.erb +58 -10
  3. data/config/presets/SMART_RunServerAgainstClient_ConfidentialAsymmetric.json.erb +183 -0
  4. data/config/presets/SMART_RunServerAgainstClient_ConfidentialSymmetric.json.erb +157 -0
  5. data/config/presets/SMART_RunServerAgainstClient_Public.json.erb +155 -0
  6. data/lib/smart_app_launch/backend_services_invalid_client_assertion_test.rb +1 -1
  7. data/lib/smart_app_launch/backend_services_invalid_jwt_test.rb +1 -1
  8. data/lib/smart_app_launch/client_stu2_2_suite.rb +60 -19
  9. data/lib/smart_app_launch/client_suite/access_alca_interaction_test.rb +75 -0
  10. data/lib/smart_app_launch/client_suite/access_alcs_interaction_test.rb +75 -0
  11. data/lib/smart_app_launch/client_suite/access_alp_interaction_test.rb +75 -0
  12. data/lib/smart_app_launch/client_suite/access_bsca_interaction_test.rb +46 -0
  13. data/lib/smart_app_launch/client_suite/access_group.rb +85 -0
  14. data/lib/smart_app_launch/client_suite/authentication_verification.rb +86 -0
  15. data/lib/smart_app_launch/client_suite/authorization_request_verification_test.rb +108 -0
  16. data/lib/smart_app_launch/client_suite/client_descriptions.rb +114 -0
  17. data/lib/smart_app_launch/client_suite/client_options.rb +35 -0
  18. data/lib/smart_app_launch/client_suite/oidc_jwks.json +32 -0
  19. data/lib/smart_app_launch/client_suite/oidc_jwks.rb +27 -0
  20. data/lib/smart_app_launch/client_suite/registration_alca_group.rb +15 -0
  21. data/lib/smart_app_launch/client_suite/registration_alca_verification_test.rb +57 -0
  22. data/lib/smart_app_launch/client_suite/registration_alcs_group.rb +15 -0
  23. data/lib/smart_app_launch/client_suite/registration_alcs_verification_test.rb +56 -0
  24. data/lib/smart_app_launch/client_suite/registration_alp_group.rb +16 -0
  25. data/lib/smart_app_launch/client_suite/registration_alp_verification_test.rb +50 -0
  26. data/lib/smart_app_launch/client_suite/registration_bsca_group.rb +15 -0
  27. data/lib/smart_app_launch/client_suite/registration_bsca_verification_test.rb +40 -0
  28. data/lib/smart_app_launch/client_suite/registration_verification.rb +58 -0
  29. data/lib/smart_app_launch/client_suite/token_request_alca_verification_test.rb +53 -0
  30. data/lib/smart_app_launch/client_suite/token_request_alcs_verification_test.rb +53 -0
  31. data/lib/smart_app_launch/client_suite/token_request_alp_verification_test.rb +48 -0
  32. data/lib/smart_app_launch/client_suite/token_request_bsca_verification_test.rb +53 -0
  33. data/lib/smart_app_launch/client_suite/token_request_verification.rb +116 -0
  34. data/lib/smart_app_launch/client_suite/{client_token_use_verification_test.rb → token_use_verification_test.rb} +1 -8
  35. data/lib/smart_app_launch/docs/smart_stu2_2_client_suite_description.md +128 -41
  36. data/lib/smart_app_launch/endpoints/echoing_fhir_responder_endpoint.rb +96 -0
  37. data/lib/smart_app_launch/endpoints/mock_smart_server/authorization_endpoint.rb +27 -0
  38. data/lib/smart_app_launch/endpoints/mock_smart_server/introspection_endpoint.rb +33 -0
  39. data/lib/smart_app_launch/endpoints/mock_smart_server/smart_authorization_response_creation.rb +30 -0
  40. data/lib/smart_app_launch/endpoints/mock_smart_server/smart_introspection_response_creation.rb +46 -0
  41. data/lib/smart_app_launch/endpoints/mock_smart_server/smart_token_response_creation.rb +250 -0
  42. data/lib/smart_app_launch/endpoints/mock_smart_server/token_endpoint.rb +58 -0
  43. data/lib/smart_app_launch/endpoints/mock_smart_server.rb +128 -67
  44. data/lib/smart_app_launch/metadata.rb +19 -14
  45. data/lib/smart_app_launch/tags.rb +9 -1
  46. data/lib/smart_app_launch/token_payload_validation.rb +2 -2
  47. data/lib/smart_app_launch/urls.rb +12 -0
  48. data/lib/smart_app_launch/version.rb +2 -2
  49. metadata +38 -11
  50. data/config/presets/SMART_RunServerAgainstClient.json.erb +0 -42
  51. data/lib/smart_app_launch/client_suite/client_access_group.rb +0 -26
  52. data/lib/smart_app_launch/client_suite/client_access_interaction_test.rb +0 -64
  53. data/lib/smart_app_launch/client_suite/client_registration_group.rb +0 -15
  54. data/lib/smart_app_launch/client_suite/client_registration_verification_test.rb +0 -52
  55. data/lib/smart_app_launch/client_suite/client_token_request_verification_test.rb +0 -146
  56. data/lib/smart_app_launch/endpoints/echoing_fhir_responder.rb +0 -52
  57. data/lib/smart_app_launch/endpoints/mock_smart_server/token.rb +0 -27
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rack/utils'
4
+
5
+ module SMARTAppLaunch
6
+ RE_RUN_REGISTRATION_SUFFIX =
7
+ 'Create a new session and re-run the Client Registration group if you need to change this value.'
8
+ INPUT_CLIENT_ID_DESCRIPTION =
9
+ 'Testers may provide a specific value for Inferno to assign as the client id. If no value is provided, ' \
10
+ 'the Inferno session id will be used.'
11
+ INPUT_CLIENT_ID_DESCRIPTION_LOCKED =
12
+ "The registered Client Id for use in obtaining access tokens. #{RE_RUN_REGISTRATION_SUFFIX}".freeze
13
+ INPUT_SMART_LAUNCH_URLS_DESCRIPTION =
14
+ 'If the client app supports EHR launch, a comma-delimited list of one or more URLs that Inferno can ' \
15
+ 'use to launch the app.'
16
+ INPUT_SMART_LAUNCH_URLS_DESCRIPTION_LOCKED =
17
+ 'Registered Launch URLs in the form of a comma-separated list of zero or more URLs. If present, Inferno ' \
18
+ "will provide an option to use each to launch the app. #{RE_RUN_REGISTRATION_SUFFIX}".freeze
19
+ INPUT_SMART_REDIRECT_URIS_DESCRIPTION =
20
+ 'A comma-separated list of one or more URIs that the app will sepcify as the target of the redirect for ' \
21
+ 'Inferno to use when providing the authorization code.'
22
+ INPUT_SMART_REDIRECT_URIS_DESCRIPTION_LOCKED =
23
+ 'Registered Redirect URIs in the form of a comma-separated list of one or more URIs. Redirect URIs ' \
24
+ "specified in authorization requests must come from this list. #{RE_RUN_REGISTRATION_SUFFIX}".freeze
25
+ INPUT_CLIENT_SECRET_DESCRIPTION =
26
+ 'Provide the client secret that the confidential symmetric client will send with token requests ' \
27
+ 'to authenticate the client to Inferno.'
28
+ INPUT_CLIENT_SECRET_DESCRIPTION_LOCKED =
29
+ 'The registered client secret that will be provided during token requests to authenticate the client ' \
30
+ "to Inferno. #{RE_RUN_REGISTRATION_SUFFIX}".freeze
31
+ INPUT_CLIENT_JWKS_DESCRIPTION =
32
+ 'The SMART client\'s JSON Web Key Set including the key(s) Inferno will need to verify signatures ' \
33
+ 'on token requests made by the client. May be provided as either a publicly accessible url containing ' \
34
+ 'the JWKS, or the raw JWKS JSON.'
35
+ INPUT_CLIENT_JWKS_DESCRIPTION_LOCKED =
36
+ 'The SMART client\'s JSON Web Key Set in the form of either a publicly accessible url containing the ' \
37
+ 'JWKS, or the raw JWKS JSON. Must include the key(s) Inferno will need to verify signatures on token ' \
38
+ "requests made by the client. #{RE_RUN_REGISTRATION_SUFFIX}".freeze
39
+
40
+ INPUT_LAUNCH_CONTEXT_DESCRIPTION =
41
+ 'Launch context details to be included in access token responses, specified as a JSON array. If provided, ' \
42
+ 'the contents will be merged into Inferno\'s token responses.'
43
+ INPUT_FHIR_USER_RELATIVE_REFERENCE =
44
+ 'A FHIR relative reference (<resource type>/<id>) for the FHIR user record to return when the openid ' \
45
+ 'and fhirUser scopes are requested. Include this resource in the **Available Resources** input so ' \
46
+ 'that it can be accessed via FHIR read.'
47
+ INPUT_FHIR_READ_RESOURCES_BUNDLE_DESCRIPTION =
48
+ 'Resources to make available in Inferno\'s simulated FHIR server provided as a FHIR bundle. Each entry ' \
49
+ 'must contain a resource with the id element populated. Each instance present will be available for ' \
50
+ 'retrieval from Inferno at the endpoint: <fhir-base>/<resource type>/<instance id>. These will only ' \
51
+ 'be available through the read interaction.'
52
+ INPUT_ECHOED_FHIR_RESPONSE_DESCRIPTION =
53
+ 'JSON representation of a default FHIR resource for Inferno to echo when a request is made to the ' \
54
+ 'simulated FHIR server. Reads targetting resources in the **Available Resources** input will return ' \
55
+ 'that resource instead of this. Otherwise, the content here will be echoed back exactly and no check ' \
56
+ 'will be made that it is appropriate for the request made. If nothing is provided, an OperationOutcome ' \
57
+ 'indicating nothing to echo will be returned.'
58
+
59
+ module ClientWaitDialogDescriptions
60
+ def access_wait_dialog_backend_services_access_prefix(client_id, fhir_base_url)
61
+ <<~PREFIX
62
+ **Access**
63
+
64
+ Use the registered client id (#{client_id}) to obtain an access
65
+ token using SMART Backend Services
66
+ and use that token to access a FHIR endpoint under the simulated server's base URL:
67
+
68
+ `#{fhir_base_url}`
69
+
70
+ PREFIX
71
+ end
72
+
73
+ def access_wait_dialog_app_launch_access_prefix(client_id, authentication_approach, fhir_base_url)
74
+ <<~PREFIX
75
+ **Launch and Access**
76
+
77
+ The app has been registered with Inferno's simulated SMART server as a
78
+ #{authentication_approach} client with client id `#{client_id}`.
79
+
80
+ Perform a standalone launch to connect to Inferno's simulated FHIR server at:
81
+
82
+ `#{fhir_base_url}`
83
+
84
+ PREFIX
85
+ end
86
+
87
+ def access_wait_dialog_ehr_launch_instructions(smart_launch_urls, fhir_base_url)
88
+ if smart_launch_urls.present?
89
+ launch_key = SecureRandom.hex(32)
90
+ output(launch_key:)
91
+
92
+ launch_query_string = Rack::Utils.build_query({ iss: fhir_base_url, launch: launch_key })
93
+ ehr_launch_locations = smart_launch_urls.split(',').map { |launch_url| "#{launch_url}?#{launch_query_string}" }
94
+ ehr_launch_links_string = ehr_launch_locations.map { |url| "- [launch](#{url})" }.join("\n")
95
+
96
+ "\n\nOr open one of the following links in a new tab to perform an EHR launch:\n#{ehr_launch_links_string}\n\n"
97
+ else
98
+ ''
99
+ end
100
+ end
101
+
102
+ def access_wait_dialog_access_response_and_continue_suffix(client_id, resume_pass_url)
103
+ <<~SUFFIX
104
+ Inferno will respond to requests with either:
105
+ - A resource from the Bundle in the **Available Resources** input if the request is a read matching
106
+ a resource type and id found in the Bundle.
107
+ - Otherwise, the contents of the **Default FHIR Response** if provided.
108
+ - Otherwise, an OperationOutcome indicating nothing to echo.
109
+
110
+ [Click here](#{resume_pass_url}?token=#{client_id}) once the client has made a data access request.
111
+ SUFFIX
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../tags'
4
+
5
+ module SMARTAppLaunch
6
+ module SMARTClientOptions
7
+ module_function
8
+
9
+ SMART_APP_LAUNCH_PUBLIC = "#{SMART_TAG},#{AUTHORIZATION_CODE_TAG},#{PUBLIC_TAG}".freeze
10
+ SMART_APP_LAUNCH_CONFIDENTIAL_SYMMETRIC =
11
+ "#{SMART_TAG},#{AUTHORIZATION_CODE_TAG},#{CONFIDENTIAL_SYMMETRIC_TAG}".freeze
12
+ SMART_APP_LAUNCH_CONFIDENTIAL_ASYMMETRIC =
13
+ "#{SMART_TAG},#{AUTHORIZATION_CODE_TAG},#{CONFIDENTIAL_ASYMMETRIC_TAG}".freeze
14
+ SMART_BACKEND_SERVICES_CONFIDENTIAL_ASYMMETRIC =
15
+ "#{SMART_TAG},#{CLIENT_CREDENTIALS_TAG},#{CONFIDENTIAL_ASYMMETRIC_TAG}".freeze
16
+
17
+ def oauth_flow(suite_options)
18
+ if suite_options[:client_type].include?(AUTHORIZATION_CODE_TAG)
19
+ AUTHORIZATION_CODE_TAG
20
+ elsif suite_options[:client_type].include?(CLIENT_CREDENTIALS_TAG)
21
+ CLIENT_CREDENTIALS_TAG
22
+ end
23
+ end
24
+
25
+ def smart_authentication_approach(suite_options)
26
+ if suite_options[:client_type].include?(PUBLIC_TAG)
27
+ PUBLIC_TAG
28
+ elsif suite_options[:client_type].include?(CONFIDENTIAL_SYMMETRIC_TAG)
29
+ CONFIDENTIAL_SYMMETRIC_TAG
30
+ elsif suite_options[:client_type].include?(CONFIDENTIAL_ASYMMETRIC_TAG)
31
+ CONFIDENTIAL_ASYMMETRIC_TAG
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,32 @@
1
+ {
2
+ "keys": [
3
+ {
4
+ "kty": "RSA",
5
+ "use": "sig",
6
+ "key_ops": [
7
+ "verify"
8
+ ],
9
+ "alg": "RS256",
10
+ "kid": "618e76fc7eb7c3401c7fd47b9af5e94d",
11
+ "n": "uoYzdYZQDp7yHTfY4XL1PFCFPfijG1rP2dNgMmsqKbm9qpMjiUuXCpdoOXWE838pcu3Ahi6L3dGgGS3Aeys3YrnF6h9uRTD1DHf-UWlOq4iPCzk26a-bBuyVZDVqS7Y5HsyF7i_UvYV1W0EaHSpAF911Vo_W_FVyiSkAZP1yN0ECbbjnSqxhaa44SGw90oh6XnyNBLu7a11aLCURkaRJOKXHj_vf8UyaD-IA9YkvM80St7QF6u6LRVYmCShbpfWRmejh_Swf4zdqcPl-duUsZE-OBysoTq92FaXr605igM30Rdkx8hs-HTwZtjn5hB3XNTiG2Wka1cUXKNyH59u8Yw",
12
+ "e": "AQAB"
13
+ },
14
+ {
15
+ "kty": "RSA",
16
+ "use": "sig",
17
+ "key_ops": [
18
+ "sign"
19
+ ],
20
+ "alg": "RS256",
21
+ "kid": "618e76fc7eb7c3401c7fd47b9af5e94d",
22
+ "d": "k2SxDVHRuXwIvuX-0EjTWZIXeF0eJuOgE_VgsvbUHpzUMBKNplTBSnFSvvUK1o_J5TPTSzVE-UhJRxxMWghQgAdlShkEPlDtk6jOou6gaBRFVQ0lQ4ys6M_TTZiYIrQgdyIPQ6Uwa4MmtbHAPQPCGhm6O2j27fdnxtNLqIJO2zF9OzF4VP5_v63bbMfwbKECvB_bbcGLmJrq2ClI1iTOw-GawQ3I8sfwf52D3mb-qDJyQwtomNxf7EhvmGBC9u_8JVY48qsCCYWr2KAauID02WtCVepxM4ltKOLJb6BL5QpKmJ8svYLWjSZvNJSdk-EoB9grqechyDOIa2DuU9g-QQ",
23
+ "n": "uoYzdYZQDp7yHTfY4XL1PFCFPfijG1rP2dNgMmsqKbm9qpMjiUuXCpdoOXWE838pcu3Ahi6L3dGgGS3Aeys3YrnF6h9uRTD1DHf-UWlOq4iPCzk26a-bBuyVZDVqS7Y5HsyF7i_UvYV1W0EaHSpAF911Vo_W_FVyiSkAZP1yN0ECbbjnSqxhaa44SGw90oh6XnyNBLu7a11aLCURkaRJOKXHj_vf8UyaD-IA9YkvM80St7QF6u6LRVYmCShbpfWRmejh_Swf4zdqcPl-duUsZE-OBysoTq92FaXr605igM30Rdkx8hs-HTwZtjn5hB3XNTiG2Wka1cUXKNyH59u8Yw",
24
+ "e": "AQAB",
25
+ "p": "x3syoDXXDJne_-aSeHMm3muQ1KfIGewMhklvGNZ6LVTIJWUE5UYiGshABmgn86GfS-uZc_oUo3WcXiCLUlZSBqZFTIyXgy9y979VWA0whPCYcVSLzSGOiUtv3Ys4VGYCmGSuJnyLDp0CNN63Gf_iGpjCRbaPCPdRnyBgfSEvETs",
26
+ "q": "718z8hC_AQXrhQxvTsCEv7FXSX0Ev10Tjdq94DcsD91g34KTbrF9K9wtPfBUQaUx5Z2rMOTdLxbtHKvP-YQ4YMoQioA9qsclcnwdvKoRcRWim9oBnLa3Iuqttdwc9U2FWEQ4wqv16rsQw7URU4qlYkiVjrHvRJb8Nx_AJsA8Tvk",
27
+ "dp": "waDGDVj1exfIq-ClgCFWM0N5-9E4nGDR729MVXGqemH3PMUHsX0YEaMa8p0bWpMhStJPy5GNgvTgaUVxtuRvDmFKlvlJAF-IWw7vyl5TIFdhwW_tm5nc_0uoNAW1EcdK8Z2YpWbym6avw54DYUtNr79jo8OGp49ZPPpybkNNqo0",
28
+ "dq": "ge3mL02Br9d7yKNAQ7niFH75Ry1yB0FJXOVPzUWFSDM84vVoe1wh-k2vzQAHa_50ABO-GXMQz_-cwsRLxj9LrtXfdp43Wtxv6h2OspqJjx1UP05tM5hF_dDua1lH6qqiZ4_YU2qtuDTD28cL2ZHXRWrqqyLQIiXmTzGPxjjwQ1k",
29
+ "qi": "n3fMznRlCCwuoCq0kv4tL0cravVhcg33rA-CQXlIxgqj3h1yiBJ4p76pHceV6tvYsPyX3YsUKNOBP9HcxuHK3I3kFqMN5Y4C4r8dTE4fbJK9QT_guo_k_GKijNp0NDNCCeKxU78SRYvA527VY-3Z6TUC_K6O-5Pd6aPJN1QDtoA"
30
+ }
31
+ ]
32
+ }
@@ -0,0 +1,27 @@
1
+ require 'jwt'
2
+
3
+ module SMARTAppLaunch
4
+ class OIDCJWKS
5
+ class << self
6
+ def jwks_json
7
+ @jwks_json ||=
8
+ JSON.pretty_generate(
9
+ { keys: jwks.export[:keys].select { |key| key[:key_ops]&.include?('verify') } }
10
+ )
11
+ end
12
+
13
+ def default_jwks_path
14
+ @default_jwks_path ||= File.join(__dir__, 'oidc_jwks.json')
15
+ end
16
+
17
+ def jwks_path
18
+ @jwks_path ||=
19
+ ENV.fetch('SIMULATED_OIDC_JWKS_PATH', default_jwks_path)
20
+ end
21
+
22
+ def jwks
23
+ @jwks ||= JWT::JWK::Set.new(JSON.parse(File.read(jwks_path)))
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,15 @@
1
+ require_relative 'registration_alca_verification_test'
2
+
3
+ module SMARTAppLaunch
4
+ class SMARTClientRegistrationAppLaunchConfidentialAsymmetric < Inferno::TestGroup
5
+ id :smart_client_registration_alca
6
+ title 'SMART App Launch Confidential Symmetric Client Registration'
7
+ description %(
8
+ During these tests, Inferno will verify the provided registration details for the
9
+ SMART App Launch client using Confidential Asymmetric authentication.
10
+ )
11
+ run_as_group
12
+
13
+ test from: :smart_client_registration_alca_verification
14
+ end
15
+ end
@@ -0,0 +1,57 @@
1
+ require_relative '../tags'
2
+ require_relative '../endpoints/mock_smart_server'
3
+ require_relative 'client_options'
4
+ require_relative 'registration_verification'
5
+ require_relative 'client_descriptions'
6
+
7
+ module SMARTAppLaunch
8
+ class SMARTClientRegistrationAppLaunchConfidentialAsymmetricVerification < Inferno::Test
9
+ include RegistrationVerification
10
+
11
+ id :smart_client_registration_alca_verification
12
+ title 'Verify SMART App Launch Confidential Asymmetric Client Registration'
13
+ description %(
14
+ During this test, Inferno will verify that the registration details
15
+ provided for a [SMART App Launch](https://hl7.org/fhir/smart-app-launch/STU2.2/app-launch.html)
16
+ confidential client using [asymmetric authentication](https://hl7.org/fhir/smart-app-launch/STU2.2/client-confidential-asymmetric.html)
17
+ are conformant.
18
+ )
19
+
20
+ input :client_id,
21
+ title: 'Client Id',
22
+ type: 'text',
23
+ optional: true,
24
+ description: INPUT_CLIENT_ID_DESCRIPTION
25
+ input :smart_launch_urls,
26
+ title: 'SMART App Launch URL(s)',
27
+ type: 'textarea',
28
+ optional: true,
29
+ description: INPUT_SMART_LAUNCH_URLS_DESCRIPTION
30
+ input :smart_redirect_uris,
31
+ title: 'SMART App Launch Redirect URI(s)',
32
+ type: 'textarea',
33
+ description: INPUT_SMART_REDIRECT_URIS_DESCRIPTION
34
+ input :smart_jwk_set,
35
+ title: 'SMART Confidential Asymmetric JSON Web Key Set (JWKS)',
36
+ type: 'textarea',
37
+ description: INPUT_CLIENT_JWKS_DESCRIPTION
38
+
39
+ output :client_id
40
+ output :smart_launch_urls # normalized
41
+ output :smart_redirect_uris # normalized
42
+
43
+ run do
44
+ if client_id.blank?
45
+ client_id = test_session_id
46
+ output(client_id:)
47
+ end
48
+
49
+ verify_registered_launch_urls(smart_launch_urls)
50
+ verify_registered_redirect_uris(smart_redirect_uris)
51
+ verify_registered_jwks(smart_jwk_set)
52
+
53
+ assert messages.none? { |msg| msg[:type] == 'error' },
54
+ 'Invalid registration details provided. See messages for details'
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,15 @@
1
+ require_relative 'registration_alcs_verification_test'
2
+
3
+ module SMARTAppLaunch
4
+ class SMARTClientRegistrationAppLaunchConfidentialSymmetric < Inferno::TestGroup
5
+ id :smart_client_registration_alcs
6
+ title 'SMART App Launch Confidential Symmetric Client Registration'
7
+ description %(
8
+ During these tests, Inferno will verify the provided registration details for the
9
+ SMART App Launch client using Confidential Symmetric authentication.
10
+ )
11
+ run_as_group
12
+
13
+ test from: :smart_client_registration_alcs_verification
14
+ end
15
+ end
@@ -0,0 +1,56 @@
1
+ require_relative '../tags'
2
+ require_relative '../endpoints/mock_smart_server'
3
+ require_relative 'client_options'
4
+ require_relative 'registration_verification'
5
+ require_relative 'client_descriptions'
6
+
7
+ module SMARTAppLaunch
8
+ class SMARTClientRegistrationAppLaunchConfidentialSymmetricVerification < Inferno::Test
9
+ include RegistrationVerification
10
+
11
+ id :smart_client_registration_alcs_verification
12
+ title 'Verify SMART App Launch Confidential Symmetric Client Registration'
13
+ description %(
14
+ During this test, Inferno will verify that the registration details
15
+ provided for a [SMART App Launch](https://hl7.org/fhir/smart-app-launch/STU2.2/app-launch.html)
16
+ confidential client using [symmetric authentication](https://hl7.org/fhir/smart-app-launch/STU2.2/client-confidential-symmetric.html)
17
+ are conformant.
18
+ )
19
+
20
+ input :client_id,
21
+ title: 'Client Id',
22
+ type: 'text',
23
+ optional: true,
24
+ description: INPUT_CLIENT_ID_DESCRIPTION
25
+ input :smart_launch_urls,
26
+ title: 'SMART App Launch URL(s)',
27
+ type: 'textarea',
28
+ optional: true,
29
+ description: INPUT_SMART_LAUNCH_URLS_DESCRIPTION
30
+ input :smart_redirect_uris,
31
+ title: 'SMART App Launch Redirect URI(s)',
32
+ type: 'textarea',
33
+ description: INPUT_SMART_REDIRECT_URIS_DESCRIPTION
34
+ input :smart_client_secret,
35
+ title: 'SMART Confidential Symmetric Client Secret',
36
+ type: 'text',
37
+ description: INPUT_CLIENT_SECRET_DESCRIPTION
38
+
39
+ output :client_id
40
+ output :smart_launch_urls # normalized
41
+ output :smart_redirect_uris # normalized
42
+
43
+ run do
44
+ if client_id.blank?
45
+ client_id = test_session_id
46
+ output(client_id:)
47
+ end
48
+
49
+ verify_registered_launch_urls(smart_launch_urls)
50
+ verify_registered_redirect_uris(smart_redirect_uris)
51
+
52
+ assert messages.none? { |msg| msg[:type] == 'error' },
53
+ 'Invalid registration details provided. See messages for details'
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,16 @@
1
+
2
+ require_relative 'registration_alp_verification_test'
3
+
4
+ module SMARTAppLaunch
5
+ class SMARTClientRegistrationAppLaunchPublic < Inferno::TestGroup
6
+ id :smart_client_registration_alp
7
+ title 'SMART App Launch Public Client Registration'
8
+ description %(
9
+ During these tests, Inferno will verify the provided registration details for the
10
+ SMART App Launch client using Confidential Symmetric authentication.
11
+ )
12
+ run_as_group
13
+
14
+ test from: :smart_client_registration_alp_verification
15
+ end
16
+ end
@@ -0,0 +1,50 @@
1
+ require_relative '../tags'
2
+ require_relative '../endpoints/mock_smart_server'
3
+ require_relative 'client_options'
4
+ require_relative 'registration_verification'
5
+
6
+ module SMARTAppLaunch
7
+ class SMARTClientRegistrationAppLaunchPublicVerification < Inferno::Test
8
+ include RegistrationVerification
9
+
10
+ id :smart_client_registration_alp_verification
11
+ title 'Verify SMART App Launch Public Client Registration'
12
+ description %(
13
+ During this test, Inferno will verify that the registration details
14
+ provided for a [SMART App Launch](https://hl7.org/fhir/smart-app-launch/STU2.2/app-launch.html)
15
+ public client using are conformant.
16
+ )
17
+
18
+ input :client_id,
19
+ title: 'Client Id',
20
+ type: 'text',
21
+ optional: true,
22
+ description: INPUT_CLIENT_ID_DESCRIPTION
23
+ input :smart_launch_urls,
24
+ title: 'SMART App Launch URL(s)',
25
+ type: 'textarea',
26
+ optional: true,
27
+ description: INPUT_SMART_LAUNCH_URLS_DESCRIPTION
28
+ input :smart_redirect_uris,
29
+ title: 'SMART App Launch Redirect URI(s)',
30
+ type: 'textarea',
31
+ description: INPUT_SMART_REDIRECT_URIS_DESCRIPTION
32
+
33
+ output :client_id
34
+ output :smart_launch_urls # normalized
35
+ output :smart_redirect_uris # normalized
36
+
37
+ run do
38
+ if client_id.blank?
39
+ client_id = test_session_id
40
+ output(client_id:)
41
+ end
42
+
43
+ verify_registered_launch_urls(smart_launch_urls)
44
+ verify_registered_redirect_uris(smart_redirect_uris)
45
+
46
+ assert messages.none? { |msg| msg[:type] == 'error' },
47
+ 'Invalid registration details provided. See messages for details'
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,15 @@
1
+ require_relative 'registration_bsca_verification_test'
2
+
3
+ module SMARTAppLaunch
4
+ class SMARTClientRegistrationBackendServicesConfidentialAsymmetric < Inferno::TestGroup
5
+ id :smart_client_registration_bsca
6
+ title 'Backend Services Confidential Asymmetric Client Registration'
7
+ description %(
8
+ During these tests, Inferno will verify the provided registration details for the
9
+ SMART Backend Services client using Confidential Asymmetric authentication.
10
+ )
11
+ run_as_group
12
+
13
+ test from: :smart_client_registration_bsca_verification
14
+ end
15
+ end
@@ -0,0 +1,40 @@
1
+ require_relative '../tags'
2
+ require_relative '../endpoints/mock_smart_server'
3
+ require_relative 'registration_verification'
4
+
5
+ module SMARTAppLaunch
6
+ class SMARTClientBackendServicesRegistrationVerification < Inferno::Test
7
+ include RegistrationVerification
8
+
9
+ id :smart_client_registration_bsca_verification
10
+ title 'Verify SMART Backend Services Confidential Asymmetric Client Registration'
11
+ description %(
12
+ During this test, Inferno will verify that the registration details
13
+ provided for a [SMART Backend Services](https://hl7.org/fhir/smart-app-launch/STU2.2/backend-services.html)
14
+ client using [asymmetric authentication](https://hl7.org/fhir/smart-app-launch/STU2.2/client-confidential-asymmetric.html)
15
+ are valid.
16
+ )
17
+ input :client_id,
18
+ title: 'Client Id',
19
+ type: 'text',
20
+ optional: true,
21
+ description: INPUT_CLIENT_ID_DESCRIPTION
22
+ input :smart_jwk_set,
23
+ title: 'SMART Confidential Asymmetric JSON Web Key Set (JWKS)',
24
+ type: 'textarea',
25
+ description: INPUT_CLIENT_JWKS_DESCRIPTION
26
+
27
+ output :client_id
28
+
29
+ run do
30
+ if client_id.blank?
31
+ client_id = test_session_id
32
+ output(client_id:)
33
+ end
34
+
35
+ verify_registered_jwks(smart_jwk_set)
36
+
37
+ assert messages.none? { |msg| msg[:type] == 'error' }, 'Invalid key set provided. See messages for details'
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,58 @@
1
+ module SMARTAppLaunch
2
+ module RegistrationVerification
3
+ def verify_registered_jwks(jwks_input)
4
+
5
+ jwks_warnings = []
6
+ parsed_smart_jwk_set = MockSMARTServer.jwk_set(smart_jwk_set, jwks_warnings)
7
+ jwks_warnings.each { |warning| add_message('warning', warning) }
8
+
9
+ # TODO: add key-specific verification per end of https://build.fhir.org/ig/HL7/smart-app-launch/client-confidential-asymmetric.html#registering-a-client-communicating-public-keys
10
+
11
+ unless parsed_smart_jwk_set.length.positive?
12
+ add_message(
13
+ 'error',
14
+ 'JWKS content for Confidential Asymmetric authentication does not include any valid keys.'
15
+ )
16
+ end
17
+ end
18
+
19
+ def verify_registered_launch_urls(launch_urls)
20
+ return unless launch_urls.present?
21
+
22
+ normalized_launch_urls = normalize_urls(launch_urls, 'launch URL')
23
+ output smart_launch_urls: normalized_launch_urls.join(',').strip
24
+ end
25
+
26
+ def verify_registered_redirect_uris(redirect_uris)
27
+ return unless redirect_uris.present?
28
+
29
+ normalized_redirect_uris = normalize_urls(redirect_uris, 'redirect URI')
30
+
31
+ output smart_redirect_uris: normalized_redirect_uris.join(',').strip
32
+ end
33
+
34
+ def normalize_urls(url_list, type_for_error)
35
+ url_list.split(',').map(&:strip).each_with_object([]) do |url, normalized_urls|
36
+ next if url.blank?
37
+
38
+ parsed_uri =
39
+ begin
40
+ URI.parse(url)
41
+ rescue URI::InvalidURIError
42
+ add_message('error', "Registered #{type_for_error} '#{url}' is not a valid URI.")
43
+ nil
44
+ end
45
+ next unless parsed_uri.present?
46
+ unless parsed_uri.scheme == 'https' || parsed_uri.scheme == 'http'
47
+ add_message('error', "Registered #{type_for_error} '#{url}' is not a valid http address.")
48
+ next
49
+ end
50
+
51
+ normalized_urls << url
52
+ unless parsed_uri.scheme == 'https'
53
+ add_message('error', "Registered #{type_for_error} '#{url}' is not a valid https URI.")
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,53 @@
1
+ require_relative '../tags'
2
+ require_relative '../urls'
3
+ require_relative '../endpoints/mock_smart_server'
4
+ require_relative 'authentication_verification'
5
+ require_relative 'client_descriptions'
6
+ require_relative 'client_options'
7
+ require_relative 'token_request_verification'
8
+
9
+ module SMARTAppLaunch
10
+ class SMARTClientTokenRequestAppLaunchConfidentialAsymmetricVerification < Inferno::Test
11
+ include URLs
12
+ include AuthenticationVerification
13
+ include TokenRequestVerification
14
+
15
+ id :smart_client_token_request_alca_verification
16
+ title 'Verify SMART Token Requests'
17
+ description %(
18
+ Check that SMART token requests are conformant.
19
+ )
20
+
21
+ input :client_id,
22
+ title: 'Client Id',
23
+ type: 'text',
24
+ optional: false,
25
+ locked: true,
26
+ description: INPUT_CLIENT_ID_DESCRIPTION_LOCKED
27
+ input :smart_jwk_set,
28
+ title: 'JSON Web Key Set (JWKS)',
29
+ type: 'textarea',
30
+ locked: true,
31
+ description: INPUT_CLIENT_JWKS_DESCRIPTION_LOCKED
32
+
33
+ output :smart_tokens
34
+
35
+ def client_suite_id
36
+ return config.options[:endpoint_suite_id] if config.options[:endpoint_suite_id].present?
37
+
38
+ SMARTAppLaunch::SMARTClientSTU22Suite.id
39
+ end
40
+
41
+ run do
42
+ load_tagged_requests(TOKEN_TAG, SMART_TAG, AUTHORIZATION_CODE_TAG)
43
+ skip_if requests.blank?, 'No SMART authorization code token requests made.'
44
+ load_tagged_requests(TOKEN_TAG, SMART_TAG, REFRESH_TOKEN_TAG) # verify refresh_requests as well
45
+
46
+ verify_token_requests(AUTHORIZATION_CODE_TAG, CONFIDENTIAL_ASYMMETRIC_TAG)
47
+
48
+ assert messages.none? { |msg|
49
+ msg[:type] == 'error'
50
+ }, 'Invalid token requests received. See messages for details.'
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,53 @@
1
+ require_relative '../tags'
2
+ require_relative '../urls'
3
+ require_relative '../endpoints/mock_smart_server'
4
+ require_relative 'authentication_verification'
5
+ require_relative 'client_descriptions'
6
+ require_relative 'client_options'
7
+ require_relative 'token_request_verification'
8
+
9
+ module SMARTAppLaunch
10
+ class SMARTClientTokenRequestAppLaunchConfidentialSymmetricVerification < Inferno::Test
11
+ include URLs
12
+ include AuthenticationVerification
13
+ include TokenRequestVerification
14
+
15
+ id :smart_client_token_request_alcs_verification
16
+ title 'Verify SMART Token Requests'
17
+ description %(
18
+ Check that SMART token requests are conformant.
19
+ )
20
+
21
+ input :client_id,
22
+ title: 'Client Id',
23
+ type: 'text',
24
+ optional: false,
25
+ locked: true,
26
+ description: INPUT_CLIENT_ID_DESCRIPTION_LOCKED
27
+ input :smart_client_secret,
28
+ title: 'SMART Confidential Symmetric Client Secret',
29
+ type: 'text',
30
+ locked: true,
31
+ description: INPUT_CLIENT_SECRET_DESCRIPTION_LOCKED
32
+
33
+ output :smart_tokens
34
+
35
+ def client_suite_id
36
+ return config.options[:endpoint_suite_id] if config.options[:endpoint_suite_id].present?
37
+
38
+ SMARTAppLaunch::SMARTClientSTU22Suite.id
39
+ end
40
+
41
+ run do
42
+ load_tagged_requests(TOKEN_TAG, SMART_TAG, AUTHORIZATION_CODE_TAG)
43
+ skip_if requests.blank?, 'No SMART authorization code token requests made.'
44
+ load_tagged_requests(TOKEN_TAG, SMART_TAG, REFRESH_TOKEN_TAG) # verify refresh_requests as well
45
+
46
+ verify_token_requests(AUTHORIZATION_CODE_TAG, CONFIDENTIAL_SYMMETRIC_TAG)
47
+
48
+ assert messages.none? { |msg|
49
+ msg[:type] == 'error'
50
+ }, 'Invalid token requests received. See messages for details.'
51
+ end
52
+ end
53
+ end