-
Notifications
You must be signed in to change notification settings - Fork 37
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
Added authenticated dynamic client registration #123
Added authenticated dynamic client registration #123
Conversation
Identity Providers support two ways how new clients can be registered through Client Registration Service. Authenticated requests - Request to register new client must contain either Initial Access Token or Bearer Token. Anonymous requests - Request to register new client doesn’t need to contain any token at all. This feature adds support for Authenticated requests.
…_config is passed from OIDCAuthentication
This reverts commit 84d0419.
…rect_uri_config is passed from OIDCAuthentication" This reverts commit 3ae93d4.
Flask-pyoidc/src/flask_pyoidc/pyoidc_facade.py Lines 72 to 73 in 40b1163
When the client is dynamically registered, the user may want to register multiple redirect_uris but in this line we are adding only one here.
Then this line adds only one redirect_uri in the registration request. This is also causing redirect to logout view to fail because |
User can add custom redirect uris for dynamic client registration through ClientRegistrationInfo class. This feature also ensures atleast one redirect uri is always registered with IdP.
Comply with 4.4.2. Access Token Request of Client Credentials Grant in RFC 6749
Yet another great contribution! 🙌 I've just had a brief look at the changes, but to get a better overview (and make it easier to review and track the changes) could you please split it into separate PRs?
|
This will break lot of code if I split this. 1 & 2 are related to each other. I can write a review on my code to reference which are 1 & 2. Perhaps if you view from Files changed section, it'll be much easier to review changes. |
src/flask_pyoidc/pyoidc_facade.py
Outdated
""" | ||
client_credentials_payload = { | ||
'grant_type': 'client_credentials' | ||
} | ||
if scope: | ||
client_credentials_payload['scope'] = ' '.join(scope) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A minor change, client credentials flow as per ouath specs can also take scope as an argument which takes string as a parameter delimited by spaces to separate multiple scopes. E.g. scope='read write'
. Just to make it easier for the user, I'm taking it as a list and convert it into the string for the payload.
While your comments help, I'd strongly prefer it to be split to follow the principle of "1 PR per change". It will help with the reviewing process (making it go faster) to have smaller chunks of changes to review. |
I'm not sure if 1 & 2 are separable. Even their test cases are common. They may look different but they are part of the same flow that collects all redirect uris from the user code & then send them in the registration request. I implemented the 1st one but during testing I found it incomplete because it was registering only one redirect uri which lead to I should rather update PR description and title so it doesn't look like 2 different independent features. I can separate 3rd one, it's completely exclusive. |
I might be misunderstanding something, but I don't see how 1 & 2 are related 🙂
Is it something specific for your use case/setup (which I gather is using Keycloak)? |
I think about new features and raise pull request. Keycloak is happen to be the one I'm using to test my features. It's not worth the time to rework 1 & 2 separately because their test cases also have to be reverted and reworked. It broke existing test cases as well which I had to address them before writing new ones. It will duplicate my work to fix test cases again if I separate them. I can draft a pull request for review purpose only. For merge, this pull request is the accurate one. |
Ok, leave it be for now. 🙂 I'm having a pretty busy week, but I'll try to review it later this week. 👍 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great addition, thanks for taking the time to put this together! 🎉
I think there's some unnecessary inconsistencies with how the redirect URIs are handled, both in implementation and behavior:
- specifying redirect_uris in client registration info should be passed unmodified to the provider
post_logout_redirect_uris
should not be mixed withredirect_uris
- determining whether redirect URIs have been specified should happen in
OIDCAuthentication._register_client
Other than that it's mostly minor questions 🙂
@@ -102,3 +218,9 @@ def test_copy_should_overwrite_existing_value(self): | |||
data = OIDCData(abc='xyz') | |||
copy_data = data.copy(qwe='rty', abc='123') | |||
assert copy_data == {'abc': '123', 'qwe': 'rty'} | |||
|
|||
def test_del_and_len(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this test is needed since those methods are directly delegated to the underlying storage.
(That's why there's only the case for OIDCData.__str__
above - it's the only method except copy which has some custom behavior.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It has increased the coverage.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above, it's unrelated to this PR and I really don't think it's necessary to test builtin dict-behavior.
@@ -31,6 +31,7 @@ def test_redirect_uri_config(self): | |||
redirect_uri_config = RedirectUriConfig.from_config(config) | |||
assert redirect_uri_config.full_uri == 'https://myexample.com:6000/callback' | |||
assert redirect_uri_config.endpoint == 'callback' | |||
assert repr(redirect_uri_config) == f'({redirect_uri_config.full_uri}, {redirect_uri_config.endpoint})' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unrelated to this PR and I'm not sure it's needed to test the string representation for this class (which isn't intended to be used for anything).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To increase the test coverage. It's at 98.86% now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While I appreciate your effort, striving for 100 % coverage is not really necessary. 🙂
I think it's pushing against the limits of the trade-off of having more test code to maintain.
'registration_client_uri': 'https://op.example.com/register/client1', | ||
'registration_access_token': 'registration_access_token1' | ||
} | ||
responses.add(responses.POST, registration_endpoint, json=client_registration_response) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To mock client registration response.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What was the difference needed for mocking the response like that instead of using the existing CLIENT_METADATA
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The response must have redirect_uris
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The response is not used/verified in this test.
So this would be an unrelated change that is not needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The response still has to be mocked to initialize ClientMetadata
by using the parameters taken from the response. Then only it can be considered that assert facade.is_registered() is True
is actually true.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then only it can be considered that assert facade.is_registered() is True is actually true.
Not sure I'm following - the tests passed before this change and the actual values in the response does not matter.
What makes this change necessary for this tests?
…ed-dynamic-client-registration
1. Improved provider_configuration's register_client 2. Added and moved test cases.
Requested changes are done. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is getting real close now!
The only main thing left is the mixing of redirect_uris
and post_logout_redirect_uris
(both in registration request and response).
I think this extension should opt for standards compliance so that special handling should not be included (and specifically Keycloak has an open issue for fixing this in their provider implementation).
'registration_client_uri': 'https://op.example.com/register/client1', | ||
'registration_access_token': 'registration_access_token1' | ||
} | ||
responses.add(responses.POST, registration_endpoint, json=client_registration_response) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The response is not used/verified in this test.
So this would be an unrelated change that is not needed.
To comply with OIDC specs
Have you abandoned this project? |
Sorry for the long turnaround time on these PRs! 😞 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for sticking with it! 👏
This looks ready to go with the minor fix of reverting the use of requests_session
in ProviderConfiguration.register_client
Until keycloak approves the feature to register post logout redirect uris keycloak/keycloak#9950, there's a workaround to register ...
auth = OIDCAuthentication({'default': provider_config})
client = auth.clients['default']
with app.app_context():
auth._register_client(client)
client._provider_configuration._client_metadata[
'post_logout_redirect_uris'] = ['https://client.example.com/logout', 'https://client.example.com/logout2']
client.register() |
Identity Providers support two ways how new clients can be registered through Client Registration Service.
Authenticated requests - Request to register new client must contain either Initial Access Token or Bearer Token.
Anonymous requests - Request to register new client doesn’t need to contain any token at all.
Features:
Support for authenticated requests by using Initial Access Token.
Register multiple redirect URIs when the client is dynamically created.
Refer to Client Registration and Keycloak's Client Registration Service for more info.