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-3175 Allow testers to specify new or updated client registration attempt #17

Merged
merged 7 commits into from
Dec 20, 2024
4 changes: 4 additions & 0 deletions lib/udap_security_test_kit/authorization_code_group.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ class AuthorizationCodeGroup < Inferno::TestGroup
default: 'authorization_code',
locked: true
},
udap_client_registration_status: {
name: :udap_auth_code_flow_client_registration_status
},
udap_client_cert_pem: {
name: :udap_auth_code_flow_client_cert_pem,
title: 'Authorization Code Client Certificate(s) (PEM Format)'
Expand Down Expand Up @@ -90,6 +93,7 @@ class AuthorizationCodeGroup < Inferno::TestGroup
} do
input_order :udap_registration_endpoint,
:udap_auth_code_flow_registration_grant_type,
:udap_auth_code_flow_client_registration_status,
:udap_auth_code_flow_client_cert_pem,
:udap_auth_code_flow_client_private_key,
:udap_auth_code_flow_cert_iss,
Expand Down
4 changes: 4 additions & 0 deletions lib/udap_security_test_kit/client_credentials_group.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ class ClientCredentialsGroup < Inferno::TestGroup
default: 'client_credentials',
locked: true
},
udap_client_registration_status: {
name: :udap_client_credentials_flow_client_registration_status
},
udap_client_cert_pem: {
name: :udap_client_credentials_flow_client_cert_pem,
title: 'Client Credentials Client Certificate(s) (PEM Format)'
Expand Down Expand Up @@ -92,6 +95,7 @@ class ClientCredentialsGroup < Inferno::TestGroup
} do
input_order :udap_registration_endpoint,
:udap_client_credentials_flow_registration_grant_type,
:udap_client_credentials_flow_client_registration_status,
:udap_client_credentials_flow_client_cert_pem,
:udap_client_credentials_flow_client_private_key,
:udap_cert_iss_client_creds_flow,
Expand Down
33 changes: 26 additions & 7 deletions lib/udap_security_test_kit/dynamic_client_registration_group.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,11 @@ def self.dynamic_client_registration_input_instructions
establish a trust chain.

Cancelling a UDAP client's registration is not a required server capability and as such the Inferno client has no
way of resetting state on the authorization server after a successful registration attempt. Testers wishing to
run the Dynamic Client Registration tests more than once must do one of the following:
- Remove the Inferno test client's registration out-of-band before re-running tests, to register the original
client URI anew
- Specifiy a different client URI as the issuer input (if the client cert has more than one Subject Alternative
Name (SAN) URI entry), to register a different logical client with the original certificate
- Provide a different client certificate and its associated URI to register a new logical client
way of resetting state on the authorization server after a successful registration attempt. If a given
certificate and issuer URI identity combination has already been registered with the authorization server, testers
may select the "Update Registration" option under Client Registration Status. This option will expect a `200 OK`
return status that indicates a registration modification, instead of the
`201 Created` return status required for a new registration entry.
)
end

Expand Down Expand Up @@ -57,6 +55,27 @@ def self.dynamic_client_registration_input_instructions
]
}

input :udap_client_registration_status,
title: 'Client Registration Status',
description: %(
If the client's iss and certificate combination has already been registered with the authorization server
prior to this test run, select 'Update'.
),
type: 'radio',
options: {
list_options: [
{
label: 'New Registration (201 Response Code Expected)',
value: 'new'
},
{
label: 'Update Registration (200 Response Code Expected)',
value: 'update'
}
]
},
default: 'new'

input :udap_client_cert_pem,
title: 'X.509 Client Certificate(s) (PEM Format)',
description: %(
Expand Down
14 changes: 13 additions & 1 deletion lib/udap_security_test_kit/registration_success_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,20 @@ class RegistrationSuccessTest < Inferno::Test
The [UDAP IG Section 3.2.3](https://hl7.org/fhir/us/udap-security/STU1/registration.html#request-body) states:
> If a new registration is successful, the Authorization Server SHALL return a registration response with a 201
> Created HTTP response code as per Section 5.1 of UDAP Dynamic Client Registration

If the tester indicated this registration attempt represents a modification of an existing registration entry,
this test will expect a 200 response code instead, as indicated in
the [UDAP IG Section 3.4](https://hl7.org/fhir/us/udap-security/STU1/registration.html#modifying-and-cancelling-registrations):
> If the Authorization Server returns the same client_id in the registration response for a modification request,
> it SHOULD also return a 200 OK HTTP response code.
)

input :udap_client_cert_pem
input :udap_client_private_key_pem
input :udap_cert_iss

input :udap_registration_endpoint
input :udap_client_registration_status
input :udap_jwt_signing_alg
input :udap_registration_requested_scope
input :udap_registration_grant_type
Expand Down Expand Up @@ -60,7 +67,12 @@ class RegistrationSuccessTest < Inferno::Test

post(udap_registration_endpoint, body: reg_body, headers: reg_headers)

assert_response_status(201)
if udap_client_registration_status == 'new'
assert_response_status(201)
elsif udap_client_registration_status == 'update'
assert_response_status(200)
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 considerably more strict than the spec, which just says:

If the Authorization Server returns the same client_id in the registration response for a modification request, it SHOULD also return a 200 OK HTTP response code.

Copy link
Collaborator Author

@alisawallace alisawallace Dec 18, 2024

Choose a reason for hiding this comment

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

I felt more comfortable with this strict behavior because this is really more of a convenience option for testers that they can opt in to use, rather than a behavior the tests are requiring them to demonstrate. The only thing they are technically required to demonstrate is that a new registration returns a 201 response, it's just that in practice this can be difficult to do (especially in repeated test runs) since we're always working with a limited set of client identities that are likely to already be in the system.

However, we could expand the acceptable range of valid response status codes for the "update" case to make it a little more flexible (accept either 201 or 200? Anything in the 200 range?) What I don't want to do is open it up too much to where the test can technically pass when the modification attempt didn't actually succeed. The next test (registration success contents) would likely catch that but still.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think 201 or 200 for an update would be fine.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Alright, I updated the code, unit tests, and descriptions to reflect this.

end

assert_valid_json(response[:body])
output udap_registration_response: response[:body]
end
Expand Down
48 changes: 37 additions & 11 deletions spec/udap_security_test_kit/registration_success_test_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
let(:udap_registration_requested_scope) { 'system/*' }
let(:udap_registration_grant_type) { 'client_credentials' }
let(:udap_registration_certifications) { '' }
let(:udap_client_registration_status) { 'new' }
let(:input) do
{
udap_client_cert_pem:,
Expand All @@ -30,7 +31,8 @@
udap_jwt_signing_alg:,
udap_registration_requested_scope:,
udap_registration_grant_type:,
udap_registration_certifications:
udap_registration_certifications:,
udap_client_registration_status:
}
end

Expand All @@ -48,21 +50,45 @@ def run(runnable, inputs = {})
Inferno::TestRunner.new(test_session:, test_run:).run(runnable)
end

it 'fails if response status is not 201' do
stub_request(:post, udap_registration_endpoint)
.to_return(status: 400, body: {}.to_json)
context 'when new client is being registered' do
it 'fails if response status is not 201' do
stub_request(:post, udap_registration_endpoint)
.to_return(status: 200, body: {}.to_json)

result = run(runnable, input)
result = run(runnable, input)

expect(result.result).to eq('fail')
expect(result.result).to eq('fail')
end

it 'passes when response status is 201' do
stub_request(:post, udap_registration_endpoint)
.to_return(status: 201, body: {}.to_json)

result = run(runnable, input)

expect(result.result).to eq('pass')
end
end

it 'passes when response status is 201' do
stub_request(:post, udap_registration_endpoint)
.to_return(status: 201, body: {}.to_json)
context 'when existing client is updating its registration data' do
it 'fails if response status is not 200' do
stub_request(:post, udap_registration_endpoint)
.to_return(status: 201, body: {}.to_json)

result = run(runnable, input)
input[:udap_client_registration_status] = 'update'
result = run(runnable, input)

expect(result.result).to eq('pass')
expect(result.result).to eq('fail')
end

it 'passes when response status is 200' do
stub_request(:post, udap_registration_endpoint)
.to_return(status: 200, body: {}.to_json)

input[:udap_client_registration_status] = 'update'
result = run(runnable, input)

expect(result.result).to eq('pass')
end
end
end
Loading