Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FI 3093: Transition to use auth info (WIP) #84

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env
Original file line number Diff line number Diff line change
@@ -1 +1 @@
JS_HOST=""
JS_HOST=""
68 changes: 38 additions & 30 deletions lib/smart_app_launch/app_redirect_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,37 +9,44 @@ class AppRedirectTest < Inferno::Test
)
id :smart_app_redirect

input :client_id, :requested_scopes, :url, :smart_authorization_url
input :use_pkce,
title: 'Proof Key for Code Exchange (PKCE)',
type: 'radio',
default: 'false',
input :url, :smart_authorization_url
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The authorization url is one of the auth_info fields.

input :auth_info,
type: :auth_info,
options: {
list_options: [
mode: 'auth',
components: [
{
label: 'Enabled',
value: 'true'
name: :auth_type,
type: 'select',
default: 'public',
options: {
list_options: [
{
label: 'Public',
value: 'public'
},
{
label: 'Confidential Symmetric',
value: 'symmetric'
}
]
}
},
{
label: 'Disabled',
value: 'false'
}
]
}
input :pkce_code_challenge_method,
optional: true,
title: 'PKCE Code Challenge Method',
type: 'radio',
default: 'S256',
options: {
list_options: [
name: :pkce_support,
default: 'disabled'
},
{
name: :auth_request_method,
locked: true
},
{
label: 'S256',
value: 'S256'
name: :requested_scopes,
type: 'textarea'
},
{
label: 'plain',
value: 'plain'
name: :use_discovery,
locked: true
}
]
}
Expand Down Expand Up @@ -93,9 +100,9 @@ def authorization_url_builder(url, params)

oauth2_params = {
'response_type' => 'code',
'client_id' => client_id,
'client_id' => auth_info.client_id,
'redirect_uri' => config.options[:redirect_uri],
'scope' => requested_scopes,
'scope' => auth_info.requested_scopes,
'state' => state,
'aud' => aud
}
Expand All @@ -106,19 +113,20 @@ def authorization_url_builder(url, params)
oauth2_params['launch'] = launch
end

if use_pkce == 'true'
if auth_info.pkce_support == 'enabled'
# code verifier must be between 43 and 128 characters
code_verifier = SecureRandom.uuid + '-' + SecureRandom.uuid
code_verifier = "#{SecureRandom.uuid}-#{SecureRandom.uuid}"
code_challenge =
if pkce_code_challenge_method == 'S256'
if auth_info.pkce_code_challenge_method == 'S256'
self.class.calculate_s256_challenge(code_verifier)
else
code_verifier
end

output pkce_code_verifier: code_verifier, pkce_code_challenge: code_challenge

oauth2_params.merge!('code_challenge' => code_challenge, 'code_challenge_method' => pkce_code_challenge_method)
oauth2_params.merge!('code_challenge' => code_challenge,
'code_challenge_method' => auth_info.pkce_code_challenge_method)
end

authorization_url = authorization_url_builder(
Expand Down
50 changes: 40 additions & 10 deletions lib/smart_app_launch/app_redirect_test_stu2.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,55 @@ class AppRedirectTestSTU2 < AppRedirectTest
Request](http://hl7.org/fhir/smart-app-launch/STU2/app-launch.html#request-4)
)

input :authorization_method,
title: 'Authorization Request Method',
type: 'radio',
default: 'get',
input :auth_info,
type: :auth_info,
options: {
list_options: [
mode: 'auth',
components: [
{
label: 'GET',
value: 'get'
name: :auth_type,
type: 'select',
default: 'public',
options: {
list_options: [
{
label: 'Public',
value: 'public'
},
{
label: 'Confidential Symmetric',
value: 'symmetric'
},
{
label: 'Confidential Asymmetric',
value: 'asymmetric'
}
]
}
},
{
label: 'POST',
value: 'post'
name: :pkce_support,
default: 'enabled',
locked: true
},
{
name: :pkce_code_challenge_method,
default: 'S256',
locked: true
},
{
name: :requested_scopes,
type: 'textarea'
},
{
name: :use_discovery,
locked: true
}
]
}

def authorization_url_builder(url, params)
return super if authorization_method == 'get'
return super if auth_info.auth_request_method == 'get'

post_params = params.merge(auth_url: url)

Expand Down
78 changes: 37 additions & 41 deletions lib/smart_app_launch/backend_services_authorization_group.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,65 +13,61 @@ class BackendServicesAuthorizationGroup < Inferno::TestGroup

id :backend_services_authorization

input :auth_info,
type: :auth_info,
options: {
mode: 'auth',
components: [
{
name: :auth_type,
type: 'select',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You shouldn't have to specify the type for these, right?

default: 'backend_services',
locked: 'true'
},
{
name: :pkce_support,
default: 'enabled',
locked: true
},
{
name: :pkce_code_challenge_method,
default: 'S256',
locked: true
},
{
name: :requested_scopes,
default: 'system/*.read'
},
{
name: :use_discovery,
locked: true
}
]
}

input :smart_token_url,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an auth_info field.

title: 'Backend Services Token Endpoint',
description: <<~DESCRIPTION
The OAuth 2.0 Token Endpoint used by the Backend Services specification to provide bearer tokens.
DESCRIPTION

input :backend_services_client_id,
title: 'Backend Services Client ID',
description: 'Client ID provided at registration to the Inferno application.'
input :backend_services_requested_scope,
title: 'Backend Services Requested Scopes',
description: 'Backend Services Scopes provided at registration to the Inferno application; will be `system/` scopes',
default: 'system/*.read'

input :client_auth_encryption_method,
title: 'Encryption Method for Asymmetric Confidential Client Authorization',
description: <<~DESCRIPTION,
The server is required to suport either ES384 or RS384 encryption methods for JWT signature verification.
Select which method to use.
DESCRIPTION
type: 'radio',
default: 'ES384',
options: {
list_options: [
{
label: 'ES384',
value: 'ES384'
},
{
label: 'RS384',
value: 'RS384'
}
]
}
input :backend_services_jwks_kid,
title: 'Backend Services JWKS kid',
description: <<~DESCRIPTION,
The key ID of the JWKS private key to use for signing the client assertion when fetching an auth token.
Defaults to the first JWK in the list if no kid is supplied.
DESCRIPTION
optional: true

output :bearer_token

test from: :tls_version_test do
title 'Authorization service token endpoint secured by transport layer security'
description <<~DESCRIPTION
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)
states "the client SHALL use the Transport Layer Security (TLS) Protocol Version 1.2 (RFC5246)
or a more recent version of TLS to authenticate the identity of the FHIR authorization server and to
establish an encrypted, integrity-protected link for securing all exchanges between the client and the
FHIR authorization server’s token endpoint. All exchanges described herein between the client and the
states "the client SHALL use the Transport Layer Security (TLS) Protocol Version 1.2 (RFC5246)
or a more recent version of TLS to authenticate the identity of the FHIR authorization server and to
establish an encrypted, integrity-protected link for securing all exchanges between the client and the
FHIR authorization server’s token endpoint. All exchanges described herein between the client and the
FHIR server SHALL be secured using TLS V1.2 or a more recent version of TLS."
DESCRIPTION
id :smart_backend_services_token_tls_version

config(
inputs: { url: { name: :smart_token_url } },
options: { minimum_allowed_version: OpenSSL::SSL::TLS1_2_VERSION }
options: { minimum_allowed_version: OpenSSL::SSL::TLS1_2_VERSION }
)
end

Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
require_relative 'backend_services_authorization_request_builder'
require_relative 'backend_services_authorization_group'

module SMARTAppLaunch
class BackendServicesAuthorizationRequestSuccessTest < Inferno::Test
module SMARTAppLaunch
class BackendServicesAuthorizationRequestSuccessTest < Inferno::Test
id :smart_backend_services_auth_request_success
title 'Authorization request succeeds when supplied correct information'
description <<~DESCRIPTION
The [SMART App Launch 2.0.0 IG specification for Backend Services](https://hl7.org/fhir/smart-app-launch/STU2/backend-services.html#issue-access-token)
states "If the access token request is valid and authorized, the authorization server SHALL issue an access token in response."
DESCRIPTION

input :client_auth_encryption_method,
:backend_services_requested_scope,
:backend_services_client_id,
:smart_token_url
input :backend_services_jwks_kid,
optional: true
input :smart_token_url
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an auth_info field.

input :auth_info,
type: :auth_info,
options: {
mode: 'auth',
components: [
{
name: :auth_type,
type: 'select',
default: 'backend_services',
locked: 'true'
}
]
}

output :authentication_response

Expand All @@ -24,12 +32,14 @@ class BackendServicesAuthorizationRequestSuccessTest < Inferno::Test
end

run do
post_request_content = BackendServicesAuthorizationRequestBuilder.build(encryption_method: client_auth_encryption_method,
scope: backend_services_requested_scope,
iss: backend_services_client_id,
sub: backend_services_client_id,
aud: smart_token_url,
kid: backend_services_jwks_kid)
post_request_content = BackendServicesAuthorizationRequestBuilder.build(
encryption_method: auth_info.encryption_algorithm,
scope: auth_info.requested_scopes,
iss: auth_info.client_id,
sub: auth_info.client_id,
aud: smart_token_url,
kid: auth_info.kid
)

authentication_response = post(**{ client: :token_endpoint }.merge(post_request_content))

Expand All @@ -38,4 +48,4 @@ class BackendServicesAuthorizationRequestSuccessTest < Inferno::Test
output authentication_response: authentication_response.response_body
end
end
end
end
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
require_relative 'backend_services_authorization_request_builder'

module SMARTAppLaunch
class BackendServicesAuthorizationResponseBodyTest < Inferno::Test
module SMARTAppLaunch
class BackendServicesAuthorizationResponseBodyTest < Inferno::Test
id :smart_backend_services_auth_response_body
title 'Authorization request response body contains required information encoded in JSON'
description <<~DESCRIPTION
Expand Down Expand Up @@ -30,11 +30,11 @@ class BackendServicesAuthorizationResponseBodyTest < Inferno::Test

output bearer_token: access_token

required_keys = ['token_type', 'expires_in', 'scope']
required_keys = %w[token_type expires_in scope]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not use the %whatever array forms.


required_keys.each do |key|
assert response_body[key].present?, "Token response did not contain #{key} as required"
end
end
end
end
end
Loading
Loading