-
Notifications
You must be signed in to change notification settings - Fork 3.9k
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
feat: add user_id in the default scopes if no scopes are requested from payload #34300
Merged
justEhmadSaeed
merged 10 commits into
master
from
asaeed/add-application-scopes-to-default-scopes
Mar 19, 2024
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
4a8cc1e
feat: add application scopes into default
justEhmadSaeed 51de2c6
feat: add user_id in the default scopes
justEhmadSaeed 14199e5
chore: use feature-flag, remove mocks for tests and add ADR in decisions
justEhmadSaeed 2694f82
fix: copy default scopes to prevent mutating the original list
justEhmadSaeed 5be6ed6
fix: update ADRs and add test cases with ENABLE_USER_ID_SCOPE flag off
justEhmadSaeed f07f8f2
fix: quality check for trailing whitespace
justEhmadSaeed 070582a
docs: add ENABLE_USER_ID_SCOPE feature flag documentation
justEhmadSaeed fe1fcc2
fix: toggle annotations for ENABLE_USER_ID_SCOPE
justEhmadSaeed c48fbb9
fix: toggle_use_cases and public ticket for its removal
justEhmadSaeed de60471
fix: add toggle_target_removal_date annotation
justEhmadSaeed File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
4 changes: 2 additions & 2 deletions
4
...isions/0015-add-scope-user-id-for-jwt.rst → ...-scope-user-id-for-jwt-password-grant.rst
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
40 changes: 40 additions & 0 deletions
40
...h_dispatch/docs/decisions/0016-add-scope-user-id-for-jwt-client-credentials.rst
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
16. Add scope user_id for JWT token and client credentials grant type | ||
##################################################################### | ||
|
||
Status | ||
------ | ||
|
||
Accepted | ||
|
||
Context | ||
------- | ||
|
||
This ADR builds upon ADR 0015: Add Scope user_id for JWT Token. It inherits the context from that ADR, including the initial addition of the user_id claim for analytics and the rationale for using a scope to control its inclusion. | ||
|
||
However, this ADR focuses specifically on the enterprise context highlighted in the consequences of ADR 0015. External organizations often utilize the LMS API with the grant_type: client_credentials to request tokens on behalf of their users. These tokens are crucial for enterprise integrations and require access to the user's user_id for proper functionality. | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could add something like the following:
|
||
Based on 0005-restricted-application-for-SSO_ and 0006-enforce-scopes-in-LMS-APIs_ ADRs, it may be that the original purpose of the user_id scope was to protect against leakage of the user_id in the case of some combination of Authorization Grant and Restricted Applications. More investigation would be required to refactor based on this context, but it may be useful for future iterations. | ||
|
||
.. _0005-restricted-application-for-SSO: 0005-restricted-application-for-SSO.rst | ||
.. _0006-enforce-scopes-in-LMS-APIs: 0006-enforce-scopes-in-LMS-APIs.rst | ||
|
||
The current implementation requires clients to remember to request using the scope, which is not a trustworthy solution. | ||
|
||
Decisions | ||
--------- | ||
|
||
- The scope ``user_id`` will be added to all requests having grant_type ``client_credentials`` in the API `/oauth2/access_token/`, if it is an allowed scope in the DOT Application and the payload request does not have `scopes` attribute in it. | ||
|
||
Consequences | ||
------------ | ||
|
||
- Enterprises will have the flexibility to request the user_id claim in JWT tokens issued through the client_credentials grant. This enables them to integrate seamlessly with LMS functionalities requiring user identification. | ||
- The existing behavior for other grant types (like password) defined in ADR 0015 remains unchanged. | ||
- This pattern will be used to clean-up the manually added ``user_id`` scope for oauth clients using the enterprise public APIs. | ||
|
||
Deferred/Rejected Alternatives | ||
------------------------------ | ||
|
||
- Adding all allowable scopes as default: Including all allowable scopes by default, which would include the user_id scope, would not follow the principle of least privilege and would make every default token have more access than required. Instead, we will minimize the additional access. | ||
- Default Inclusion with Opt-Out: This alternative explores automatically adding user_id for all cases and introducing an opt-out mechanism for specific scenarios where it's not required. Deferring this option allows for further analysis of potential security implications and user experience trade-offs. | ||
robrap marked this conversation as resolved.
Show resolved
Hide resolved
|
||
- See the context section for additional thoughts around Authorization Grant type and Restricted Applications. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,13 +5,15 @@ | |
# pylint: disable=protected-access | ||
|
||
import datetime | ||
from unittest import mock | ||
|
||
from django.conf import settings | ||
from django.test import RequestFactory, TestCase | ||
from django.utils import timezone | ||
|
||
from common.djangoapps.student.tests.factories import UserFactory | ||
from openedx.core.djangolib.testing.utils import skip_unless_lms | ||
from openedx.core.djangoapps.oauth_dispatch.tests.factories import ApplicationAccessFactory | ||
|
||
# oauth_dispatch is not in CMS' INSTALLED_APPS so these imports will error during test collection | ||
if settings.ROOT_URLCONF == 'lms.urls': | ||
|
@@ -55,6 +57,7 @@ class CustomValidationTestCase(TestCase): | |
|
||
In particular, inactive users should be able to validate. | ||
""" | ||
|
||
def setUp(self): | ||
super().setUp() | ||
self.TEST_PASSWORD = 'Password1234' | ||
|
@@ -65,6 +68,7 @@ def setUp(self): | |
) | ||
self.validator = EdxOAuth2Validator() | ||
self.request_factory = RequestFactory() | ||
self.default_scopes = list(settings.OAUTH2_DEFAULT_SCOPES.keys()) | ||
|
||
def test_active_user_validates(self): | ||
assert self.user.is_active | ||
|
@@ -77,6 +81,41 @@ def test_inactive_user_validates(self): | |
request = self.request_factory.get('/') | ||
assert self.validator.validate_user('darkhelmet', self.TEST_PASSWORD, client=None, request=request) | ||
|
||
@mock.patch.dict(settings.FEATURES, ENABLE_USER_ID_SCOPE=True) | ||
def test_get_default_scopes_with_user_id(self): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add tests for the False version as well, to make this really clear. |
||
""" | ||
Test that get_default_scopes returns the default scopes plus the user_id scope if it's available. | ||
""" | ||
application_access = ApplicationAccessFactory(scopes=['user_id']) | ||
|
||
request = mock.Mock(grant_type='client_credentials', client=application_access.application, scopes=None) | ||
overriden_default_scopes = self.validator.get_default_scopes(request=request, client_id='client_id') | ||
|
||
self.assertEqual(overriden_default_scopes, self.default_scopes + ['user_id']) | ||
|
||
@mock.patch.dict(settings.FEATURES, ENABLE_USER_ID_SCOPE=False) | ||
def test_get_default_scopes_without_user_id(self): | ||
""" | ||
Test that if `ENABLE_USER_ID_SCOPE` flag is turned off, the get_default_scopes returns | ||
the default scopes without `user_id` even if it's allowed. | ||
""" | ||
application_access = ApplicationAccessFactory(scopes=['user_id']) | ||
|
||
request = mock.Mock(grant_type='client_credentials', client=application_access.application, scopes=None) | ||
overriden_default_scopes = self.validator.get_default_scopes(request=request, client_id='client_id') | ||
|
||
self.assertEqual(overriden_default_scopes, self.default_scopes) | ||
|
||
@mock.patch.dict(settings.FEATURES, ENABLE_USER_ID_SCOPE=True) | ||
def test_get_default_scopes(self): | ||
""" | ||
Test that get_default_scopes returns the default scopes if user_id scope is not available. | ||
""" | ||
request = mock.Mock(grant_type='client_credentials', client=None, scopes=None) | ||
overriden_default_scopes = self.validator.get_default_scopes(request=request, client_id='client_id') | ||
|
||
self.assertEqual(overriden_default_scopes, self.default_scopes) | ||
|
||
|
||
@skip_unless_lms | ||
class CustomAuthorizationViewTestCase(TestCase): | ||
|
@@ -87,6 +126,7 @@ class CustomAuthorizationViewTestCase(TestCase): | |
an application even if the access token is expired. | ||
(This is a temporary override until Auth Scopes is implemented.) | ||
""" | ||
|
||
def setUp(self): | ||
super().setUp() | ||
self.TEST_PASSWORD = 'Password1234' | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Maybe something like the following addition?:
The current implementation requires clients to remember to request using the scope, which is not a trustworthy solution.