udap_security_test_kit 0.11.2 → 0.11.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/config/presets/UDAP_RunClientAgainstServer.json.erb +20 -0
- data/config/presets/UDAP_RunServerAgainstClient.json.erb +272 -0
- data/lib/udap_security_test_kit/client_credentials_token_exchange_test.rb +1 -1
- data/lib/udap_security_test_kit/client_suite/access_ac_group.rb +25 -0
- data/lib/udap_security_test_kit/client_suite/access_ac_interaction_test.rb +59 -0
- data/lib/udap_security_test_kit/client_suite/access_cc_group.rb +23 -0
- data/lib/udap_security_test_kit/client_suite/access_cc_interaction_test.rb +49 -0
- data/lib/udap_security_test_kit/client_suite/authorization_request_verification_test.rb +83 -0
- data/lib/udap_security_test_kit/client_suite/client_descriptions.rb +70 -0
- data/lib/udap_security_test_kit/client_suite/client_options.rb +20 -0
- data/lib/udap_security_test_kit/client_suite/oidc_jwks.json +32 -0
- data/lib/udap_security_test_kit/client_suite/oidc_jwks.rb +27 -0
- data/lib/udap_security_test_kit/client_suite/registration_ac_group.rb +18 -0
- data/lib/udap_security_test_kit/client_suite/registration_ac_verification_test.rb +38 -0
- data/lib/udap_security_test_kit/client_suite/registration_cc_group.rb +18 -0
- data/lib/udap_security_test_kit/client_suite/registration_cc_verification_test.rb +38 -0
- data/lib/udap_security_test_kit/client_suite/registration_interaction_test.rb +57 -0
- data/lib/udap_security_test_kit/client_suite/registration_request_verification.rb +242 -0
- data/lib/udap_security_test_kit/client_suite/token_request_ac_verification_test.rb +49 -0
- data/lib/udap_security_test_kit/client_suite/token_request_cc_verification_test.rb +49 -0
- data/lib/udap_security_test_kit/client_suite/token_request_verification.rb +223 -0
- data/lib/udap_security_test_kit/client_suite/token_use_verification_test.rb +40 -0
- data/lib/udap_security_test_kit/client_suite.rb +107 -0
- data/lib/udap_security_test_kit/docs/demo/FHIR Request.postman_collection.json +81 -0
- data/lib/udap_security_test_kit/docs/udap_client_suite_description.md +163 -0
- data/lib/udap_security_test_kit/endpoints/echoing_fhir_responder_endpoint.rb +96 -0
- data/lib/udap_security_test_kit/endpoints/mock_udap_server/authorization_endpoint.rb +28 -0
- data/lib/udap_security_test_kit/endpoints/mock_udap_server/registration_endpoint.rb +31 -0
- data/lib/udap_security_test_kit/endpoints/mock_udap_server/token_endpoint.rb +56 -0
- data/lib/udap_security_test_kit/endpoints/mock_udap_server/udap_authorization_response_creation.rb +63 -0
- data/lib/udap_security_test_kit/endpoints/mock_udap_server/udap_registration_response_creation.rb +28 -0
- data/lib/udap_security_test_kit/endpoints/mock_udap_server/udap_token_response_creation.rb +218 -0
- data/lib/udap_security_test_kit/endpoints/mock_udap_server.rb +382 -0
- data/lib/udap_security_test_kit/metadata.rb +4 -4
- data/lib/udap_security_test_kit/tags.rb +12 -0
- data/lib/udap_security_test_kit/urls.rb +52 -0
- data/lib/udap_security_test_kit/version.rb +2 -2
- data/lib/udap_security_test_kit.rb +8 -2
- metadata +36 -2
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../urls'
|
4
|
+
require_relative '../tags'
|
5
|
+
require_relative 'mock_udap_server'
|
6
|
+
|
7
|
+
module UDAPSecurityTestKit
|
8
|
+
class EchoingFHIRResponderEndpoint < Inferno::DSL::SuiteEndpoint
|
9
|
+
def test_run_identifier
|
10
|
+
MockUDAPServer.issued_token_to_client_id(request.headers['authorization']&.delete_prefix('Bearer '))
|
11
|
+
end
|
12
|
+
|
13
|
+
def make_response
|
14
|
+
return if response.status == 401 # set in update_result (expired token handling there)
|
15
|
+
|
16
|
+
response.content_type = 'application/fhir+json'
|
17
|
+
response.headers['Access-Control-Allow-Origin'] = '*'
|
18
|
+
response.status = 200
|
19
|
+
|
20
|
+
# look for read of provided resources
|
21
|
+
read_response = tester_provided_read_response_body
|
22
|
+
if read_response.present?
|
23
|
+
response.body = read_response.to_json
|
24
|
+
return
|
25
|
+
end
|
26
|
+
|
27
|
+
# If the tester provided a response, echo it
|
28
|
+
# otherwise, operation outcome
|
29
|
+
echo_response = JSON.parse(result.input_json)
|
30
|
+
.find { |input| input['name'].include?('echoed_fhir_response') }
|
31
|
+
&.dig('value')
|
32
|
+
if echo_response.present?
|
33
|
+
response.body = echo_response
|
34
|
+
return
|
35
|
+
end
|
36
|
+
|
37
|
+
response.status = 400
|
38
|
+
response.body = FHIR::OperationOutcome.new(
|
39
|
+
issue: FHIR::OperationOutcome::Issue.new(
|
40
|
+
severity: 'fatal', code: 'required',
|
41
|
+
details: FHIR::CodeableConcept.new(text: 'No response provided to echo.')
|
42
|
+
)
|
43
|
+
).to_json
|
44
|
+
end
|
45
|
+
|
46
|
+
def update_result
|
47
|
+
if MockUDAPServer.request_has_expired_token?(request)
|
48
|
+
MockUDAPServer.update_response_for_expired_token(response, 'Bearer token')
|
49
|
+
return
|
50
|
+
end
|
51
|
+
|
52
|
+
nil # never update for now
|
53
|
+
end
|
54
|
+
|
55
|
+
def tags
|
56
|
+
[ACCESS_TAG]
|
57
|
+
end
|
58
|
+
|
59
|
+
def tester_provided_read_response_body
|
60
|
+
resource_type = request.params[:one]
|
61
|
+
id = request.params[:two]
|
62
|
+
|
63
|
+
return unless resource_type.present? && id.present?
|
64
|
+
|
65
|
+
resource_type_class =
|
66
|
+
begin
|
67
|
+
FHIR.const_get(resource_type)
|
68
|
+
rescue NameError
|
69
|
+
nil
|
70
|
+
end
|
71
|
+
return unless resource_type_class.present?
|
72
|
+
|
73
|
+
resource_bundle = ehr_input_bundle
|
74
|
+
return unless resource_bundle.present?
|
75
|
+
|
76
|
+
find_resource_in_bundle(resource_bundle, resource_type_class, id)
|
77
|
+
end
|
78
|
+
|
79
|
+
def ehr_input_bundle
|
80
|
+
ehr_bundle_input =
|
81
|
+
JSON.parse(result.input_json).find { |input| input['name'] == 'fhir_read_resources_bundle' }&.dig('value')
|
82
|
+
ehr_bundle = FHIR.from_contents(ehr_bundle_input) if ehr_bundle_input.present?
|
83
|
+
return ehr_bundle if ehr_bundle.is_a?(FHIR::Bundle)
|
84
|
+
|
85
|
+
nil
|
86
|
+
rescue StandardError
|
87
|
+
nil
|
88
|
+
end
|
89
|
+
|
90
|
+
def find_resource_in_bundle(bundle, resource_type_class, id)
|
91
|
+
bundle.entry&.find do |entry|
|
92
|
+
entry.resource.is_a?(resource_type_class) && entry.resource.id == id
|
93
|
+
end&.resource
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../tags'
|
4
|
+
require_relative 'udap_authorization_response_creation'
|
5
|
+
|
6
|
+
module UDAPSecurityTestKit
|
7
|
+
module MockUDAPServer
|
8
|
+
class AuthorizationEndpoint < Inferno::DSL::SuiteEndpoint
|
9
|
+
include UDAPAuthorizationResponseCreation
|
10
|
+
|
11
|
+
def test_run_identifier
|
12
|
+
request.params[:client_id]
|
13
|
+
end
|
14
|
+
|
15
|
+
def make_response
|
16
|
+
make_udap_authorization_response
|
17
|
+
end
|
18
|
+
|
19
|
+
def update_result
|
20
|
+
nil # never update for now
|
21
|
+
end
|
22
|
+
|
23
|
+
def tags
|
24
|
+
[AUTHORIZATION_TAG, AUTHORIZATION_CODE_TAG, UDAP_TAG]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../tags'
|
4
|
+
require_relative '../mock_udap_server'
|
5
|
+
require_relative 'udap_registration_response_creation'
|
6
|
+
|
7
|
+
module UDAPSecurityTestKit
|
8
|
+
module MockUDAPServer
|
9
|
+
class RegistrationEndpoint < Inferno::DSL::SuiteEndpoint
|
10
|
+
include UDAPRegistrationResponseCreation
|
11
|
+
|
12
|
+
def test_run_identifier
|
13
|
+
MockUDAPServer.client_uri_to_client_id(
|
14
|
+
MockUDAPServer.udap_client_uri_from_registration_payload(MockUDAPServer.parsed_io_body(request))
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
def make_response
|
19
|
+
make_udap_registration_response
|
20
|
+
end
|
21
|
+
|
22
|
+
def update_result
|
23
|
+
nil # never update for now
|
24
|
+
end
|
25
|
+
|
26
|
+
def tags
|
27
|
+
[REGISTRATION_TAG, UDAP_TAG]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../tags'
|
4
|
+
require_relative '../../urls'
|
5
|
+
require_relative '../mock_udap_server'
|
6
|
+
require_relative 'udap_token_response_creation'
|
7
|
+
|
8
|
+
module UDAPSecurityTestKit
|
9
|
+
module MockUDAPServer
|
10
|
+
class TokenEndpoint < Inferno::DSL::SuiteEndpoint
|
11
|
+
include UDAPTokenResponseCreation
|
12
|
+
include URLs
|
13
|
+
|
14
|
+
def test_run_identifier
|
15
|
+
case request.params[:grant_type]
|
16
|
+
when CLIENT_CREDENTIALS_TAG
|
17
|
+
MockUDAPServer.client_id_from_client_assertion(request.params[:client_assertion])
|
18
|
+
when AUTHORIZATION_CODE_TAG
|
19
|
+
MockUDAPServer.issued_token_to_client_id(request.params[:code])
|
20
|
+
when REFRESH_TOKEN_TAG
|
21
|
+
MockUDAPServer.issued_token_to_client_id(
|
22
|
+
MockUDAPServer.refresh_token_to_authorization_code(request.params[:refresh_token])
|
23
|
+
)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def make_response
|
28
|
+
case request.params[:grant_type]
|
29
|
+
when CLIENT_CREDENTIALS_TAG
|
30
|
+
make_udap_client_credential_token_response
|
31
|
+
when AUTHORIZATION_CODE_TAG
|
32
|
+
make_udap_authorization_code_token_response
|
33
|
+
when REFRESH_TOKEN_TAG
|
34
|
+
make_udap_refresh_token_response
|
35
|
+
else
|
36
|
+
MockUDAPServer.update_response_for_error(
|
37
|
+
response,
|
38
|
+
"unsupported grant_type: #{request.params[:grant_type]}"
|
39
|
+
)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def update_result
|
44
|
+
nil # never update for now
|
45
|
+
end
|
46
|
+
|
47
|
+
def tags
|
48
|
+
tags = [TOKEN_TAG, UDAP_TAG]
|
49
|
+
if [CLIENT_CREDENTIALS_TAG, AUTHORIZATION_CODE_TAG, REFRESH_TOKEN_TAG].include?(request.params[:grant_type])
|
50
|
+
tags << request.params[:grant_type]
|
51
|
+
end
|
52
|
+
tags
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/udap_security_test_kit/endpoints/mock_udap_server/udap_authorization_response_creation.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'jwt'
|
2
|
+
require_relative '../mock_udap_server'
|
3
|
+
|
4
|
+
module UDAPSecurityTestKit
|
5
|
+
module MockUDAPServer
|
6
|
+
module UDAPAuthorizationResponseCreation
|
7
|
+
def make_udap_authorization_response
|
8
|
+
redirect_uri = request.params[:redirect_uri]
|
9
|
+
registered_redirect_uri_list = udap_registered_redirect_uris
|
10
|
+
|
11
|
+
if redirect_uri.blank?
|
12
|
+
# need one from the registered list
|
13
|
+
if registered_redirect_uri_list.blank?
|
14
|
+
response.status = 400
|
15
|
+
response.body = {
|
16
|
+
error: 'Bad request',
|
17
|
+
message: 'Missing required redirect_uri parameter with no default provided in the registration.'
|
18
|
+
}.to_json
|
19
|
+
response.content_type = 'application/json'
|
20
|
+
return
|
21
|
+
elsif registered_redirect_uri_list.length > 1
|
22
|
+
response.status = 400
|
23
|
+
response.body = {
|
24
|
+
error: 'Bad request',
|
25
|
+
message: 'Missing required redirect_uri parameter with multiple options provided in the registration.'
|
26
|
+
}.to_json
|
27
|
+
response.content_type = 'application/json'
|
28
|
+
return
|
29
|
+
else
|
30
|
+
redirect_uri = registered_redirect_uri_list.first
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
client_id = request.params[:client_id]
|
35
|
+
state = request.params[:state]
|
36
|
+
|
37
|
+
exp_min = 10
|
38
|
+
token = MockUDAPServer.client_id_to_token(client_id, exp_min)
|
39
|
+
code_query_string = "code=#{ERB::Util.url_encode(token)}"
|
40
|
+
query_string =
|
41
|
+
if state.present?
|
42
|
+
"#{code_query_string}&state=#{ERB::Util.url_encode(state)}"
|
43
|
+
else
|
44
|
+
code_query_string
|
45
|
+
end
|
46
|
+
response.headers['Location'] = "#{redirect_uri}#{redirect_uri.include?('?') ? '&' : '?'}#{query_string}"
|
47
|
+
response.status = 302
|
48
|
+
end
|
49
|
+
|
50
|
+
def udap_registered_redirect_uris
|
51
|
+
registered_software_statement = MockUDAPServer.udap_registration_software_statement(test_run.test_session_id)
|
52
|
+
return unless registered_software_statement.present?
|
53
|
+
|
54
|
+
registration_jwt_body, _registration_jwt_header = JWT.decode(registered_software_statement, nil, false)
|
55
|
+
return [] unless registration_jwt_body['redirect'].present?
|
56
|
+
return registration_jwt_body['redirect'] if registration_jwt_body['redirect'].is_a?(Array)
|
57
|
+
|
58
|
+
# invalid registration, but we'll succeed here and fail during registration verification
|
59
|
+
[registration_jwt_body['redirect']]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/udap_security_test_kit/endpoints/mock_udap_server/udap_registration_response_creation.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative '../mock_udap_server'
|
2
|
+
|
3
|
+
module UDAPSecurityTestKit
|
4
|
+
module MockUDAPServer
|
5
|
+
module UDAPRegistrationResponseCreation
|
6
|
+
def make_udap_registration_response
|
7
|
+
parsed_body = MockUDAPServer.parsed_io_body(request)
|
8
|
+
client_id = MockUDAPServer.client_uri_to_client_id(
|
9
|
+
MockUDAPServer.udap_client_uri_from_registration_payload(parsed_body)
|
10
|
+
)
|
11
|
+
ss_jwt = MockUDAPServer.udap_software_statement_jwt(parsed_body)
|
12
|
+
|
13
|
+
response_body = {
|
14
|
+
client_id:,
|
15
|
+
software_statement: ss_jwt
|
16
|
+
}
|
17
|
+
response_body.merge!(MockUDAPServer.jwt_claims(ss_jwt).except(['iss', 'sub', 'exp', 'iat', 'jti']))
|
18
|
+
|
19
|
+
response.body = response_body.to_json
|
20
|
+
response.headers['Cache-Control'] = 'no-store'
|
21
|
+
response.headers['Pragma'] = 'no-cache'
|
22
|
+
response.headers['Access-Control-Allow-Origin'] = '*'
|
23
|
+
response.content_type = 'application/json'
|
24
|
+
response.status = 201
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,218 @@
|
|
1
|
+
require_relative '../../urls'
|
2
|
+
require_relative '../../tags'
|
3
|
+
require_relative '../mock_udap_server'
|
4
|
+
require_relative '../../client_suite/oidc_jwks'
|
5
|
+
|
6
|
+
module UDAPSecurityTestKit
|
7
|
+
module MockUDAPServer
|
8
|
+
module UDAPTokenResponseCreation
|
9
|
+
def make_udap_authorization_code_token_response # rubocop:disable Metrics/CyclomaticComplexity
|
10
|
+
authorization_code = request.params[:code]
|
11
|
+
client_id = MockUDAPServer.issued_token_to_client_id(authorization_code)
|
12
|
+
software_statement = MockUDAPServer.udap_registration_software_statement(test_run.test_session_id)
|
13
|
+
return unless udap_authenticated?(request.params[:client_assertion], software_statement)
|
14
|
+
|
15
|
+
if MockUDAPServer.token_expired?(authorization_code)
|
16
|
+
MockUDAPServer.update_response_for_expired_token(response, 'Authorization code')
|
17
|
+
return
|
18
|
+
end
|
19
|
+
|
20
|
+
return if request.params[:code_verifier].present? && !udap_pkce_valid?(authorization_code)
|
21
|
+
|
22
|
+
exp_min = 60
|
23
|
+
response_body = {
|
24
|
+
access_token: MockUDAPServer.client_id_to_token(client_id, exp_min),
|
25
|
+
token_type: 'Bearer',
|
26
|
+
expires_in: 60 * exp_min
|
27
|
+
}
|
28
|
+
|
29
|
+
launch_context =
|
30
|
+
begin
|
31
|
+
input_string = JSON.parse(result.input_json)&.find do |input|
|
32
|
+
input['name'] == 'launch_context'
|
33
|
+
end&.dig('value')
|
34
|
+
JSON.parse(input_string) if input_string.present?
|
35
|
+
rescue JSON::ParserError
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
additional_context = udap_requested_scope_context(udap_registered_scope(software_statement), authorization_code,
|
39
|
+
launch_context)
|
40
|
+
|
41
|
+
response.body = additional_context.merge(response_body).to_json # response body values take priority
|
42
|
+
response.headers['Cache-Control'] = 'no-store'
|
43
|
+
response.headers['Pragma'] = 'no-cache'
|
44
|
+
response.headers['Access-Control-Allow-Origin'] = '*'
|
45
|
+
response.content_type = 'application/json'
|
46
|
+
response.status = 200
|
47
|
+
end
|
48
|
+
|
49
|
+
def make_udap_refresh_token_response # rubocop:disable Metrics/CyclomaticComplexity
|
50
|
+
refresh_token = request.params[:refresh_token]
|
51
|
+
authorization_code = MockUDAPServer.refresh_token_to_authorization_code(refresh_token)
|
52
|
+
client_id = MockUDAPServer.issued_token_to_client_id(authorization_code)
|
53
|
+
software_statement = MockUDAPServer.udap_registration_software_statement(test_run.test_session_id)
|
54
|
+
return unless udap_authenticated?(request.params[:client_assertion], software_statement)
|
55
|
+
|
56
|
+
# no expiration checks for refresh tokens
|
57
|
+
|
58
|
+
authorization_request = MockUDAPServer.authorization_request_for_code(authorization_code,
|
59
|
+
test_run.test_session_id)
|
60
|
+
if authorization_request.blank?
|
61
|
+
MockUDAPServer.update_response_for_error(
|
62
|
+
response,
|
63
|
+
"no authorization request found for refresh token #{refresh_token}"
|
64
|
+
)
|
65
|
+
return
|
66
|
+
end
|
67
|
+
auth_code_request_inputs = MockUDAPServer.authorization_code_request_details(authorization_request)
|
68
|
+
if auth_code_request_inputs.blank?
|
69
|
+
MockUDAPServer.update_response_for_error(
|
70
|
+
response,
|
71
|
+
'invalid authorization request details'
|
72
|
+
)
|
73
|
+
return
|
74
|
+
end
|
75
|
+
|
76
|
+
exp_min = 60
|
77
|
+
response_body = {
|
78
|
+
access_token: MockUDAPServer.client_id_to_token(client_id, exp_min),
|
79
|
+
token_type: 'Bearer',
|
80
|
+
expires_in: 60 * exp_min
|
81
|
+
}
|
82
|
+
|
83
|
+
launch_context =
|
84
|
+
begin
|
85
|
+
input_string = JSON.parse(result.input_json)&.find do |input|
|
86
|
+
input['name'] == 'launch_context'
|
87
|
+
end&.dig('value')
|
88
|
+
JSON.parse(input_string) if input_string.present?
|
89
|
+
rescue JSON::ParserError
|
90
|
+
nil
|
91
|
+
end
|
92
|
+
additional_context = udap_requested_scope_context(udap_registered_scope(software_statement), authorization_code,
|
93
|
+
launch_context)
|
94
|
+
|
95
|
+
response.body = additional_context.merge(response_body).to_json # response body values take priority
|
96
|
+
response.headers['Cache-Control'] = 'no-store'
|
97
|
+
response.headers['Pragma'] = 'no-cache'
|
98
|
+
response.headers['Access-Control-Allow-Origin'] = '*'
|
99
|
+
response.content_type = 'application/json'
|
100
|
+
response.status = 200
|
101
|
+
end
|
102
|
+
|
103
|
+
def make_udap_client_credential_token_response
|
104
|
+
assertion = request.params[:client_assertion]
|
105
|
+
client_id = MockUDAPServer.client_id_from_client_assertion(assertion)
|
106
|
+
software_statement = MockUDAPServer.udap_registration_software_statement(test_run.test_session_id)
|
107
|
+
return unless udap_authenticated?(request.params[:client_assertion], software_statement)
|
108
|
+
|
109
|
+
exp_min = 60
|
110
|
+
response_body = {
|
111
|
+
access_token: MockUDAPServer.client_id_to_token(client_id, exp_min),
|
112
|
+
token_type: 'Bearer',
|
113
|
+
expires_in: 60 * exp_min
|
114
|
+
}
|
115
|
+
|
116
|
+
response.body = response_body.to_json
|
117
|
+
response.headers['Cache-Control'] = 'no-store'
|
118
|
+
response.headers['Pragma'] = 'no-cache'
|
119
|
+
response.headers['Access-Control-Allow-Origin'] = '*'
|
120
|
+
response.content_type = 'application/json'
|
121
|
+
response.status = 200
|
122
|
+
end
|
123
|
+
|
124
|
+
def udap_authenticated?(assertion, software_statement)
|
125
|
+
signature_error = MockUDAPServer.udap_token_signature_verification(assertion, software_statement)
|
126
|
+
|
127
|
+
if signature_error.present?
|
128
|
+
MockUDAPServer.update_response_for_error(response, signature_error)
|
129
|
+
return false
|
130
|
+
end
|
131
|
+
|
132
|
+
true
|
133
|
+
end
|
134
|
+
|
135
|
+
def udap_requested_scope_context(requested_scopes, authorization_code, launch_context)
|
136
|
+
context = launch_context.present? ? launch_context : {}
|
137
|
+
scopes_list = requested_scopes&.split || []
|
138
|
+
|
139
|
+
if scopes_list.include?('offline_access') || scopes_list.include?('online_access')
|
140
|
+
context[:refresh_token] = MockUDAPServer.authorization_code_to_refresh_token(authorization_code)
|
141
|
+
end
|
142
|
+
|
143
|
+
context[:id_token] = udap_construct_id_token(scopes_list.include?('fhirUser')) if scopes_list.include?('openid')
|
144
|
+
|
145
|
+
context
|
146
|
+
end
|
147
|
+
|
148
|
+
def udap_construct_id_token(include_fhir_user) # rubocop:disable Metrics/CyclomaticComplexity
|
149
|
+
client_id = JSON.parse(result.input_json)&.find do |input|
|
150
|
+
input['name'] == 'client_id'
|
151
|
+
end&.dig('value')
|
152
|
+
fhir_user_relative_reference = JSON.parse(result.input_json)&.find do |input|
|
153
|
+
input['name'] == 'fhir_user_relative_reference'
|
154
|
+
end&.dig('value')
|
155
|
+
|
156
|
+
subject_id = if fhir_user_relative_reference.present?
|
157
|
+
fhir_user_relative_reference.downcase.gsub('/', '-')
|
158
|
+
else
|
159
|
+
SecureRandom.uuid
|
160
|
+
end
|
161
|
+
|
162
|
+
claims = {
|
163
|
+
iss: client_fhir_base_url,
|
164
|
+
sub: subject_id,
|
165
|
+
aud: client_id,
|
166
|
+
exp: 1.year.from_now.to_i,
|
167
|
+
iat: Time.now.to_i
|
168
|
+
}
|
169
|
+
if include_fhir_user && fhir_user_relative_reference.present?
|
170
|
+
claims[:fhirUser] = "#{client_fhir_base_url}/#{fhir_user_relative_reference}"
|
171
|
+
end
|
172
|
+
|
173
|
+
algorithm = 'RS256'
|
174
|
+
private_key = OIDCJWKS.jwks
|
175
|
+
.select { |key| key[:key_ops]&.include?('sign') }
|
176
|
+
.select { |key| key[:alg] == algorithm }
|
177
|
+
.first
|
178
|
+
|
179
|
+
JWT.encode claims, private_key.signing_key, algorithm, { alg: algorithm, kid: private_key.kid, typ: 'JWT' }
|
180
|
+
end
|
181
|
+
|
182
|
+
def udap_pkce_valid?(authorization_code)
|
183
|
+
authorization_request = MockUDAPServer.authorization_request_for_code(authorization_code,
|
184
|
+
test_run.test_session_id)
|
185
|
+
if authorization_request.blank?
|
186
|
+
MockUDAPServer.update_response_for_error(
|
187
|
+
response,
|
188
|
+
"Could not check code_verifier: no authorization request found that returned code #{authorization_code}"
|
189
|
+
)
|
190
|
+
return false
|
191
|
+
end
|
192
|
+
auth_code_request_inputs = MockUDAPServer.authorization_code_request_details(authorization_request)
|
193
|
+
if auth_code_request_inputs.blank?
|
194
|
+
MockUDAPServer.update_response_for_error(
|
195
|
+
response,
|
196
|
+
"Could not check code_verifier: invalid authorization request details for code #{authorization_code}"
|
197
|
+
)
|
198
|
+
return false
|
199
|
+
end
|
200
|
+
|
201
|
+
verifier = request.params[:code_verifier]
|
202
|
+
challenge = auth_code_request_inputs&.dig('code_challenge')
|
203
|
+
method = auth_code_request_inputs&.dig('code_challenge_method')
|
204
|
+
MockUDAPServer.pkce_valid?(verifier, challenge, method, response)
|
205
|
+
end
|
206
|
+
|
207
|
+
def udap_registered_scope(software_statement_jwt)
|
208
|
+
claims, _headers = begin
|
209
|
+
JWT.decode(software_statement_jwt, nil, false)
|
210
|
+
rescue StandardError
|
211
|
+
return nil
|
212
|
+
end
|
213
|
+
|
214
|
+
claims['scope']
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|