From b99d0a349525183aa3250cc31df4627287c16f87 Mon Sep 17 00:00:00 2001 From: Nimisha Asthagiri Date: Sun, 4 Feb 2018 18:26:26 -0500 Subject: [PATCH] OAuth docs: minor fixes --- .../0006-enforce-scopes-in-LMS-APIs.rst | 113 ++++++++++-------- ... 0007-include-organizations-in-tokens.rst} | 10 +- 2 files changed, 67 insertions(+), 56 deletions(-) rename openedx/core/djangoapps/oauth_dispatch/docs/decisions/{0007-include-organization-in-tokens.rst => 0007-include-organizations-in-tokens.rst} (97%) diff --git a/openedx/core/djangoapps/oauth_dispatch/docs/decisions/0006-enforce-scopes-in-LMS-APIs.rst b/openedx/core/djangoapps/oauth_dispatch/docs/decisions/0006-enforce-scopes-in-LMS-APIs.rst index 46162bd987..b5379d20b7 100644 --- a/openedx/core/djangoapps/oauth_dispatch/docs/decisions/0006-enforce-scopes-in-LMS-APIs.rst +++ b/openedx/core/djangoapps/oauth_dispatch/docs/decisions/0006-enforce-scopes-in-LMS-APIs.rst @@ -50,22 +50,22 @@ payload. 3. Restricted Applications receive *unexpired* JWTs, signed with a *new key* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -* We will no longer return expired *JWTs as access tokens* to Restricted - Applications. We will sign them with a *new key* that is not shared with - unprotected microservices. +We will no longer return expired *JWTs as access tokens* to Restricted +Applications. We will sign them with a *new key* that is not shared with +unprotected microservices. * API endpoints that are exposed by other microservices and that support OAuth2 requests are vulnerable to exploitation until they are also updated to enforce scopes. -* We do not want a lock-step deployment across all of our microservices. - We want to enable these changes without blocking on updating all - microservices. + * We do not want a lock-step deployment across all of our microservices. + We want to enable these changes without blocking on updating all + microservices. -* We do not want to issue unexpired *Bearer tokens* to Restricted - Applications since they will be accepted by unprotected microservices. - There's no way to retroactively inform existing microservices - to reject scope-limiting *Bearer tokens*. + * We do not want to issue unexpired *Bearer tokens* to Restricted + Applications since they will be accepted by unprotected microservices. + There's no way to retroactively inform existing microservices + to reject scope-limiting *Bearer tokens*. * On the other hand, existing unprotected microservices will reject *JWT tokens* signed with new keys that they do not know about. We will @@ -90,20 +90,22 @@ payload. JWT tokens for Restricted Applications, but ONLY if: * the token_type in the request equals *"jwt"* and - * a `feature toggle (switch)`_ named "oauth2.unexpired_restricted_applications" is enabled. + * a `feature toggle (switch)`_ named "oauth2.unexpired_restricted_applications" + is enabled. .. _edx_rest_framework_extensions.settings: https://github.com/edx/edx-drf-extensions/blob/1db9f5e3e5130a1e0f43af2035489b3ed916d245/edx_rest_framework_extensions/settings.py#L73 .. _edx-platform settings: https://github.com/edx/edx-platform/blob/master/lms/envs/docs/README.rst .. _example: https://github.com/edx/edx-drf-extensions/blob/1db9f5e3e5130a1e0f43af2035489b3ed916d245/test_settings.py#L51 +.. _JwtBuilder: https://github.com/edx/edx-platform/blob/d3d64970c36f36a96d684571ec5b48ed645618d8/openedx/core/lib/token_utils.py#L15 .. _oauth_dispatch.views.AccessTokenView.dispatch: https://github.com/edx/edx-platform/blob/d21a09828072504bc97a2e05883c1241e3a35da9/openedx/core/djangoapps/oauth_dispatch/views.py#L100 .. _oauth_dispatch.validators: https://github.com/edx/edx-platform/blob/master/openedx/core/djangoapps/oauth_dispatch/dot_overrides/validators.py 4. Associate Available Scopes with Applications ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -* In order to allow open edX operators to a priori limit the - types of access an Application can request, we will allow them - to configure Application-specific "available scopes". +In order to allow open edX operators to a priori limit the +types of access an Application can request, we will allow them +to configure Application-specific "available scopes". * Introduce a new data model that associates available scopes with DOT Applications. @@ -120,61 +122,70 @@ payload. 5. Associate Available Organizations with Applications ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -* See 0007-include-organizations-in-tokens_ for decisions on this. +See 0007-include-organizations-in-tokens_ for decisions on this. 6. Introduce a new Permission class to enforce scopes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -* The new `custom Permission`_ class will extend DOT's TokenHasScope_ - Permission class. +* New Permission class -* The TokenHasScope_ permission allows API endpoints to declare the - scopes that they require in a *required_scopes* class variable. + * The new `custom Permission`_ class will extend DOT's TokenHasScope_ + Permission class. -* The permission class will verify that the scopes in the provided JWT - are a proper superset of the scopes required by the requested view. + * The TokenHasScope_ permission allows API endpoints to declare the + scopes that they require in a *required_scopes* class variable. -* For now, the permission class will skip this verification if the - application is not a Restricted Application or if the token_type - was not a JWT token. +* Enforcement by the Permission class - * **Note:** This will be an issue when microservices want to verify - scopes. Determining whether an access token is associated with a - Restricted Application is an LMS-specific capability. Given this, - we may need to include a field in the token that indicates whether - it was issued to a Restricted Application. + * The permission class will verify that the scopes in the provided JWT + are a proper superset of the *required_scopes* field set by the requested + view. -* If the scopes verify, the permission class will update the request - object with any organization values found in the token in an attribute - called *allowed_organizations*. The view can then limit its access - and resources by the allowed organizations. + * For now, the permission class will skip this verification if the + application is not a Restricted Application or if the token_type + was not a JWT token. -* In order to have higher confidence that we don't inadvertently miss - protecting any API endpoints, add the new Permission class to the - `REST_FRAMEWORK's DEFAULT_PERMISSION_CLASSES`_ setting. + * **Note:** This will be an issue when microservices want to verify + scopes. Determining whether an access token is associated with a + Restricted Application is an LMS-specific capability. Given this, + we may need to include a field in the token that indicates whether + it was issued to a Restricted Application. + + * If the scopes verify, the permission class will update the request + object with any organization values found in the token in an attribute + called *allowed_organizations*. The view can then limit its access + and resources by the allowed organizations. + +* Using the Permission class + + * In order to have higher confidence that we don't inadvertently miss + protecting any API endpoints, add the new Permission class to the + `REST_FRAMEWORK's DEFAULT_PERMISSION_CLASSES`_ setting. * **Note:** Many of our API endpoints currently override this default - by setting the *permission_classes* field on their own View or ViewSet. + by overriding the *permission_classes* field on their own View or ViewSet. So in addition to setting this default value, we will update all (15 or so) places that include JwtAuthentication_ in their *authentication_classes* field. -* In case of an unexpected failure with this approach in production, - use a `feature toggle (switch)`_ named "oauth2.enforce_token_scopes". - When the switch is disabled, the new Permission class fails verification - of all Restricted Application requests. + * **Note:** We currently have both `function-based Django views`_ and + class-based `Django Rest Framework (DRF)`_ views in the platform. -* **Note:** We currently have both `function-based Django views`_ and - class-based `Django Rest Framework (DRF)`_ views in the platform. + * Authorization enforcement using Django Permission classes is + supported only for DRF views. DRF does provide a `Python decorator`_ + to add DRF support to function-based views. - * Authorization enforcement using Django Permission classes is - supported only for DRF views. DRF does provide a `Python decorator`_ - to add DRF support to function-based views. - - * Only DRF enhanced views support JWT based authentication in our - system. They do so via the DRF-based JwtAuthentication_ class. - So we can **safely assume** that all JWT-supporting API endpoints - can be protected via DRF's Permission class. + * Only DRF enhanced views support JWT based authentication in our + system. They do so via the DRF-based JwtAuthentication_ class. + So we can **safely assume** that all JWT-supporting API endpoints + can be protected via DRF's Permission class. + +* Easy to disable + + * In case of an unexpected failure with this approach in production, use a + `feature toggle (switch)`_ named "oauth2.enforce_token_scopes". When the + switch is disabled, the new Permission class fails verification of all + Restricted Application requests. .. _custom Permission: http://www.django-rest-framework.org/api-guide/permissions/#custom-permissions .. _TokenHasScope: https://github.com/evonove/django-oauth-toolkit/blob/50e4df7d97af90439d27a73c5923f2c06a4961f2/oauth2_provider/contrib/rest_framework/permissions.py#L13 diff --git a/openedx/core/djangoapps/oauth_dispatch/docs/decisions/0007-include-organization-in-tokens.rst b/openedx/core/djangoapps/oauth_dispatch/docs/decisions/0007-include-organizations-in-tokens.rst similarity index 97% rename from openedx/core/djangoapps/oauth_dispatch/docs/decisions/0007-include-organization-in-tokens.rst rename to openedx/core/djangoapps/oauth_dispatch/docs/decisions/0007-include-organizations-in-tokens.rst index 9ca0a791a0..6f1fd19994 100644 --- a/openedx/core/djangoapps/oauth_dispatch/docs/decisions/0007-include-organization-in-tokens.rst +++ b/openedx/core/djangoapps/oauth_dispatch/docs/decisions/0007-include-organizations-in-tokens.rst @@ -1,5 +1,5 @@ -7. Include Organization in Tokens ---------------------------------- +7. Include Organizations in Tokens +---------------------------------- Status ------ @@ -74,7 +74,7 @@ embedded in the scopes. * Create a configurable Application-specific "available organizations" setting, which is akin to Application-specific "available scopes" - (as described in 0006-enforce-scopes-in-LMS-APIs_. + (as described in 0006-enforce-scopes-in-LMS-APIs_). * Introduce a new data model that associates available organizations with DOT Applications. @@ -112,7 +112,7 @@ embedded in the scopes. When the interstitial authorization approval form is presented to the user for granting access to a DOT Application, if the Application is associated with an Organization, the Organization value(s) should be -presented to the user. This will make it clear to the user that the +presented to the user. This makes it clear to the user that the granted access is limited to the Organization's affiliations. 4. Embed Organization Limitation Types in Scopes @@ -164,7 +164,7 @@ single user (via Authorization Code). Consequences ------------ -* By associating Organizations with DOT Applications and not Restricted +* By associating organizations with DOT Applications and not Restricted Applications, we can eventually eliminate Restricted Applications altogether.