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-2247 backend services migration #59

Merged
merged 41 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
0cc043b
First pass at backend services refactor, page loads but tests fail
alisawallace Dec 11, 2023
fcc900a
Included missing JSON file, all tests pass with ref server
alisawallace Dec 11, 2023
b35b418
Changed input name to use existing SMART token endpoint
alisawallace Dec 11, 2023
6293689
Moved Backend Services tests before Token Introspection tests
alisawallace Dec 19, 2023
5c002fd
WIP refactor
alisawallace Dec 27, 2023
261b182
Moved backend services tests to individual files
alisawallace Dec 29, 2023
39429fe
Renamed inputs to reflect general backend services instead of bulk data
alisawallace Dec 29, 2023
e3d251b
Updated test descriptions and links
alisawallace Dec 29, 2023
25d947a
Updated presets for backend services.
alisawallace Dec 29, 2023
927e21e
Added SMART on FHIR Discovery to Backend Services group
alisawallace Jan 2, 2024
b8c1941
Removed smart_token_url as a preset
alisawallace Jan 2, 2024
0525bc0
Added SMART to group title
alisawallace Jan 2, 2024
24af3f5
Revert "Added SMART to group title"
alisawallace Jan 2, 2024
1df356e
Made common input for asymmetric confidential client encryption method
alisawallace Jan 2, 2024
7482711
Added SMART toBackend Services group name
alisawallace Jan 3, 2024
bf35427
Added ClientAssertionBuilder into AuthorizationRequestBuilder to elim…
alisawallace Jan 4, 2024
bc931bc
Updated input used in token introspection test with new name
alisawallace Jan 4, 2024
4cec6be
Added kid as parameter for ClientAssertionBuilder and improved error …
alisawallace Jan 4, 2024
1b87257
Fixed exception so error is properly propogated to calling object
alisawallace Jan 4, 2024
b725194
Renamed AuthorizationRequestBuilder to include backend services
alisawallace Jan 4, 2024
00e4459
Fixed bug with kid in ClientAssertionBuilder
alisawallace Jan 12, 2024
ecf8270
WIP refactored ClientAssertionBuilder spec tests
alisawallace Jan 12, 2024
73f5a4b
Exception test for ClientAssertionBuilder
alisawallace Jan 12, 2024
d747c71
Fixing input name references for token exchange stu2 test
alisawallace Jan 12, 2024
f25c9e0
Stub for backend auth request builder spec
alisawallace Jan 12, 2024
afb00c0
Copy/paste of bulk data auth tests from g10
alisawallace Jan 12, 2024
a18707b
Updated c/p spec to work for backend services group
alisawallace Jan 12, 2024
670c5c9
Updated value of expected error message, all tests pass
alisawallace Jan 12, 2024
754a8f6
Spec test for BackendServicesAuthorizationRequestBuilder
alisawallace Jan 13, 2024
f6d9d1a
Correcting references to SMART App Launch IG in descriptions
alisawallace Jan 22, 2024
e94a0bc
Improved suite and group names for backend services
alisawallace Jan 22, 2024
5c76829
Fixed rspec test references
alisawallace Jan 22, 2024
871a10e
File names andclass names aligned
alisawallace Jan 22, 2024
a807acb
Reverted authentication method input name back to original
alisawallace Jan 22, 2024
f0c762e
Fixed bug in which exp and jti values not included in client assertion
alisawallace Jan 23, 2024
d6252f2
WIP spec refactor, individual test won't pass
alisawallace Jan 25, 2024
5632d71
Improved client assertion exception for bad inputs
alisawallace Jan 27, 2024
633c774
Individual backend services spec test working
alisawallace Jan 30, 2024
3913d4b
All backend services specs refactored to individual tests/files
alisawallace Jan 30, 2024
04c7092
Cleared up confusion from duplicated input
alisawallace Jan 30, 2024
e517050
Removed pry references
alisawallace Jan 30, 2024
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
12 changes: 11 additions & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ PATH
specs:
smart_app_launch_test_kit (0.4.0)
inferno_core (>= 0.4.2)
json-jwt (~> 1.15.3)
jwt (~> 2.6)
tls_test_kit (~> 0.2.0)

Expand All @@ -17,9 +18,11 @@ GEM
zeitwerk (~> 2.3)
addressable (2.8.5)
public_suffix (>= 2.0.2, < 6.0)
aes_key_wrap (1.1.0)
base62-rb (0.3.1)
bcp47 (0.3.3)
i18n
bindata (2.4.15)
blueprinter (0.25.2)
byebug (11.1.3)
coderay (1.1.3)
Expand Down Expand Up @@ -141,6 +144,7 @@ GEM
http-accept (1.7.0)
http-cookie (1.0.5)
domain_name (~> 0.5)
httpclient (2.8.3)
i18n (1.14.1)
concurrent-ruby (~> 1.0)
ice_nine (0.11.2)
Expand Down Expand Up @@ -172,6 +176,11 @@ GEM
io-console (0.5.11)
irb (1.4.2)
reline (>= 0.3.0)
json-jwt (1.15.3)
activesupport (>= 4.2)
aes_key_wrap
bindata
httpclient
jwt (2.7.1)
kramdown (2.4.0)
rexml
Expand Down Expand Up @@ -282,6 +291,7 @@ GEM
PLATFORMS
arm64-darwin-21
arm64-darwin-22
arm64-darwin-23
x86_64-darwin-20
x86_64-darwin-22
x86_64-linux
Expand All @@ -296,4 +306,4 @@ DEPENDENCIES
webmock (~> 3.11)

BUNDLED WITH
2.3.23
2.5.3
5 changes: 5 additions & 0 deletions config/presets/inferno_reference_server_stu2_preset.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@
}
]
}
},
{
"name": "backend_services_client_id",
"type": "text",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InJlZ2lzdHJhdGlvbi10b2tlbiJ9.eyJqd2tzX3VybCI6Imh0dHA6Ly8xMC4xNS4yNTIuNzMvaW5mZXJuby8ud2VsbC1rbm93bi9qd2tzLmpzb24iLCJhY2Nlc3NUb2tlbnNFeHBpcmVJbiI6MTUsImlhdCI6MTU5NzQxMzE5NX0.q4v4Msc74kN506KTZ0q_minyapJw0gwlT6M_uiL73S4"
}
]
}
88 changes: 88 additions & 0 deletions lib/smart_app_launch/backend_services_authorization_group.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
require_relative 'backend_services_authorization_request_builder'
require_relative 'backend_services_invalid_grant_type_test'
require_relative 'backend_services_invalid_client_assertion_test'
require_relative 'backend_services_invalid_jwt_test'
require_relative 'backend_services_authorization_request_success_test'
require_relative 'backend_services_authorization_response_body_test'
require_relative 'token_exchange_stu2_test'

module SMARTAppLaunch
class BackendServicesAuthorizationGroup < Inferno::TestGroup
title 'SMART Backend Services Authorization'
short_description 'Demonstrate SMART Backend Services Authorization'

id :backend_services_authorization

input :smart_token_url,
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
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 }
)
end

test from: :smart_backend_services_invalid_grant_type

test from: :smart_backend_services_invalid_client_assertion

test from: :smart_backend_services_invalid_jwt

test from: :smart_backend_services_auth_request_success

test from: :smart_backend_services_auth_response_body
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
require 'json/jwt'
require_relative 'client_assertion_builder'

module SMARTAppLaunch
class BackendServicesAuthorizationRequestBuilder
def self.build(...)
new(...).authorization_request
end

attr_reader :encryption_method, :scope, :iss, :sub, :aud, :content_type, :grant_type, :client_assertion_type, :exp,
:jti, :kid

def initialize(
encryption_method:,
scope:,
iss:,
sub:,
aud:,
content_type: 'application/x-www-form-urlencoded',
grant_type: 'client_credentials',
client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
exp: 5.minutes.from_now,
jti: SecureRandom.hex(32),
kid: nil
)
@encryption_method = encryption_method
@scope = scope
@iss = iss
@sub = sub
@aud = aud
@content_type = content_type
@grant_type = grant_type
@client_assertion_type = client_assertion_type
@exp = exp
@jti = jti
@kid = kid
end

def authorization_request_headers
{
content_type:,
accept: 'application/json'
}.compact
end

def authorization_request_query_values
{
'scope' => scope,
'grant_type' => grant_type,
'client_assertion_type' => client_assertion_type,
'client_assertion' => client_assertion.to_s
}.compact
end

def client_assertion
@client_assertion ||= ClientAssertionBuilder.build(
client_auth_encryption_method: encryption_method,
iss: iss,
sub: sub,
aud: aud,
exp: exp.to_i,
jti: jti,
kid: kid
)
end

def authorization_request
uri = Addressable::URI.new
uri.query_values = authorization_request_query_values

{ body: uri.query, headers: authorization_request_headers }
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
require_relative 'backend_services_authorization_request_builder'
require_relative 'backend_services_authorization_group'

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,
:backend_services_jwks_kid

output :authentication_response

http_client :token_endpoint do
url :smart_token_url
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)

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

assert_response_status([200, 201])

output authentication_response: authentication_response.response_body
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
require_relative 'backend_services_authorization_request_builder'

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
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 The access token response SHALL be a JSON object with the following properties:

| Token Property | Required? | Description |
| --- | --- | --- |
| `access_token` | required | The access token issued by the authorization server. |
| `token_type` | required | Fixed value: `bearer`. |
| `expires_in` | required | The lifetime in seconds of the access token. The recommended value is `300`, for a five-minute token lifetime. |
| `scope` | required | Scope of access authorized. Note that this can be different from the scopes requested by the app. |
DESCRIPTION

input :authentication_response
output :bearer_token

run do
skip_if authentication_response.blank?, 'No authentication response received.'

assert_valid_json(authentication_response)
response_body = JSON.parse(authentication_response)

access_token = response_body['access_token']
assert access_token.present?, 'Token response did not contain access_token as required'

output bearer_token: access_token

required_keys = ['token_type', 'expires_in', 'scope']

required_keys.each do |key|
assert response_body[key].present?, "Token response did not contain #{key} as required"
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
require_relative 'backend_services_authorization_request_builder'

module SMARTAppLaunch
class BackendServicesInvalidClientAssertionTest < Inferno::Test
id :smart_backend_services_invalid_client_assertion
title 'Authorization request fails when supplied invalid client_assertion_type'
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)
defines the required fields for the authorization request, made via HTTP POST to authorization
token endpoint.
This includes the `client_assertion_type` parameter, where the value must be `urn:ietf:params:oauth:client-assertion-type:jwt-bearer`.

The [OAuth 2.0 Authorization Framework Section 4.3.3](https://datatracker.ietf.org/doc/html/rfc6749#section-4.3.3)
describes the proper response for an invalid request in the client credentials grant flow:

"If the request failed client authentication or is invalid, the authorization server returns an
error response as described in [Section 5.2](https://tools.ietf.org/html/rfc6749#section-5.2)."
DESCRIPTION

input :client_auth_encryption_method,
:backend_services_requested_scope,
:backend_services_client_id,
:smart_token_url,
:backend_services_jwks_kid

http_client :token_endpoint do
url :smart_token_url
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,
client_assertion_type: 'not_an_assertion_type',
kid: backend_services_jwks_kid)

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

assert_response_status(400)
end
end
end
Loading
Loading