oauthlib 3.2.2__tar.gz → 3.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {oauthlib-3.2.2 → oauthlib-3.3.0}/CHANGELOG.rst +24 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/LICENSE +5 -5
- {oauthlib-3.2.2/oauthlib.egg-info → oauthlib-3.3.0}/PKG-INFO +37 -18
- {oauthlib-3.2.2 → oauthlib-3.3.0}/README.rst +10 -6
- oauthlib-3.3.0/examples/device_code_flow.py +260 -0
- oauthlib-3.3.0/examples/skeleton_oauth2_web_application_server.py +114 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/__init__.py +13 -13
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/common.py +6 -5
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth1/rfc5849/__init__.py +3 -2
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth1/rfc5849/endpoints/base.py +3 -8
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth1/rfc5849/signature.py +4 -4
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth1/rfc5849/utils.py +3 -2
- oauthlib-3.3.0/oauthlib/oauth2/__init__.py +70 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth2/rfc6749/clients/base.py +14 -21
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth2/rfc6749/clients/mobile_application.py +1 -1
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth2/rfc6749/clients/service_application.py +2 -2
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth2/rfc6749/clients/web_application.py +3 -3
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth2/rfc6749/endpoints/base.py +2 -2
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth2/rfc6749/endpoints/introspect.py +1 -1
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth2/rfc6749/endpoints/metadata.py +2 -2
- oauthlib-3.3.0/oauthlib/oauth2/rfc6749/endpoints/pre_configured.py +287 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth2/rfc6749/endpoints/resource.py +1 -1
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth2/rfc6749/errors.py +3 -4
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py +3 -4
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth2/rfc6749/grant_types/base.py +2 -5
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py +4 -5
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth2/rfc6749/grant_types/implicit.py +1 -4
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py +3 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py +5 -6
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth2/rfc6749/parameters.py +83 -28
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth2/rfc6749/request_validator.py +2 -2
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth2/rfc6749/tokens.py +3 -9
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth2/rfc8628/__init__.py +6 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth2/rfc8628/clients/device.py +3 -3
- oauthlib-3.3.0/oauthlib/oauth2/rfc8628/endpoints/__init__.py +10 -0
- oauthlib-3.3.0/oauthlib/oauth2/rfc8628/endpoints/device_authorization.py +232 -0
- oauthlib-3.3.0/oauthlib/oauth2/rfc8628/endpoints/pre_configured.py +36 -0
- oauthlib-3.3.0/oauthlib/oauth2/rfc8628/errors.py +55 -0
- oauthlib-3.3.0/oauthlib/oauth2/rfc8628/grant_types/__init__.py +1 -0
- oauthlib-3.3.0/oauthlib/oauth2/rfc8628/grant_types/device_code.py +111 -0
- oauthlib-3.3.0/oauthlib/oauth2/rfc8628/request_validator.py +25 -0
- oauthlib-3.3.0/oauthlib/openid/connect/core/endpoints/pre_configured.py +139 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/openid/connect/core/exceptions.py +5 -4
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/openid/connect/core/grant_types/base.py +5 -1
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/openid/connect/core/grant_types/dispatchers.py +3 -3
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/openid/connect/core/grant_types/hybrid.py +5 -6
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/openid/connect/core/request_validator.py +1 -1
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/openid/connect/core/tokens.py +1 -4
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/signals.py +3 -2
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/uri_validate.py +1 -2
- {oauthlib-3.2.2 → oauthlib-3.3.0/oauthlib.egg-info}/PKG-INFO +37 -18
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib.egg-info/SOURCES.txt +17 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib.egg-info/top_level.txt +1 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/setup.cfg +1 -1
- {oauthlib-3.2.2 → oauthlib-3.3.0}/setup.py +11 -12
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/oauth1/rfc5849/endpoints/test_base.py +1 -1
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/oauth1/rfc5849/test_signatures.py +37 -30
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/oauth1/rfc5849/test_utils.py +2 -2
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/oauth2/rfc6749/clients/test_base.py +48 -25
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/oauth2/rfc6749/clients/test_service_application.py +1 -1
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/oauth2/rfc6749/clients/test_web_application.py +4 -9
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/oauth2/rfc6749/endpoints/test_metadata.py +5 -4
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/oauth2/rfc6749/grant_types/test_refresh_token.py +17 -1
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/oauth2/rfc6749/test_parameters.py +22 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/oauth2/rfc6749/test_tokens.py +1 -1
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/oauth2/rfc6749/test_utils.py +1 -1
- oauthlib-3.3.0/tests/oauth2/rfc8628/endpoints/test_device_application_server.py +26 -0
- oauthlib-3.3.0/tests/oauth2/rfc8628/endpoints/test_error_responses.py +95 -0
- oauthlib-3.3.0/tests/oauth2/rfc8628/grant_types/test_device_code.py +172 -0
- oauthlib-3.3.0/tests/oauth2/rfc8628/test_server.py +113 -0
- oauthlib-3.3.0/tests/openid/connect/core/__init__.py +0 -0
- oauthlib-3.3.0/tests/openid/connect/core/endpoints/__init__.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/openid/connect/core/endpoints/test_openid_connect_params_handling.py +3 -1
- oauthlib-3.3.0/tests/openid/connect/core/endpoints/test_refresh_token.py +32 -0
- oauthlib-3.3.0/tests/openid/connect/core/grant_types/__init__.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/test_uri_validate.py +2 -1
- oauthlib-3.2.2/oauthlib/oauth2/__init__.py +0 -36
- oauthlib-3.2.2/oauthlib/oauth2/rfc6749/endpoints/pre_configured.py +0 -216
- oauthlib-3.2.2/oauthlib/openid/connect/core/endpoints/pre_configured.py +0 -97
- {oauthlib-3.2.2 → oauthlib-3.3.0}/MANIFEST.in +0 -0
- {oauthlib-3.2.2/oauthlib/openid/connect → oauthlib-3.3.0/examples}/__init__.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth1/__init__.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth1/rfc5849/endpoints/__init__.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth1/rfc5849/endpoints/access_token.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth1/rfc5849/endpoints/authorization.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth1/rfc5849/endpoints/pre_configured.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth1/rfc5849/endpoints/request_token.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth1/rfc5849/endpoints/resource.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth1/rfc5849/endpoints/signature_only.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth1/rfc5849/errors.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth1/rfc5849/parameters.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth1/rfc5849/request_validator.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth2/rfc6749/__init__.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth2/rfc6749/clients/__init__.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth2/rfc6749/clients/backend_application.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth2/rfc6749/clients/legacy_application.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth2/rfc6749/endpoints/__init__.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth2/rfc6749/endpoints/authorization.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth2/rfc6749/endpoints/revocation.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth2/rfc6749/endpoints/token.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth2/rfc6749/grant_types/__init__.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth2/rfc6749/utils.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/oauth2/rfc8628/clients/__init__.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/openid/__init__.py +0 -0
- {oauthlib-3.2.2/oauthlib/openid/connect/core → oauthlib-3.3.0/oauthlib/openid/connect}/__init__.py +0 -0
- {oauthlib-3.2.2/tests/oauth1 → oauthlib-3.3.0/oauthlib/openid/connect/core}/__init__.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/openid/connect/core/endpoints/__init__.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/openid/connect/core/endpoints/userinfo.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/openid/connect/core/grant_types/__init__.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/openid/connect/core/grant_types/authorization_code.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/openid/connect/core/grant_types/implicit.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib/openid/connect/core/grant_types/refresh_token.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib.egg-info/dependency_links.txt +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/oauthlib.egg-info/requires.txt +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/__init__.py +0 -0
- {oauthlib-3.2.2/tests/oauth1/rfc5849 → oauthlib-3.3.0/tests/oauth1}/__init__.py +0 -0
- {oauthlib-3.2.2/tests/oauth1/rfc5849/endpoints → oauthlib-3.3.0/tests/oauth1/rfc5849}/__init__.py +0 -0
- {oauthlib-3.2.2/tests/oauth2 → oauthlib-3.3.0/tests/oauth1/rfc5849/endpoints}/__init__.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/oauth1/rfc5849/endpoints/test_access_token.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/oauth1/rfc5849/endpoints/test_authorization.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/oauth1/rfc5849/endpoints/test_request_token.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/oauth1/rfc5849/endpoints/test_resource.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/oauth1/rfc5849/endpoints/test_signature_only.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/oauth1/rfc5849/test_client.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/oauth1/rfc5849/test_parameters.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/oauth1/rfc5849/test_request_validator.py +0 -0
- {oauthlib-3.2.2/tests/oauth2/rfc6749 → oauthlib-3.3.0/tests/oauth2}/__init__.py +0 -0
- {oauthlib-3.2.2/tests/oauth2/rfc6749/clients → oauthlib-3.3.0/tests/oauth2/rfc6749}/__init__.py +0 -0
- {oauthlib-3.2.2/tests/oauth2/rfc6749/endpoints → oauthlib-3.3.0/tests/oauth2/rfc6749/clients}/__init__.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/oauth2/rfc6749/clients/test_backend_application.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/oauth2/rfc6749/clients/test_legacy_application.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/oauth2/rfc6749/clients/test_mobile_application.py +0 -0
- {oauthlib-3.2.2/tests/oauth2/rfc6749/grant_types → oauthlib-3.3.0/tests/oauth2/rfc6749/endpoints}/__init__.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/oauth2/rfc6749/endpoints/test_base_endpoint.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/oauth2/rfc6749/endpoints/test_client_authentication.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/oauth2/rfc6749/endpoints/test_credentials_preservation.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/oauth2/rfc6749/endpoints/test_error_responses.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/oauth2/rfc6749/endpoints/test_extra_credentials.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/oauth2/rfc6749/endpoints/test_introspect_endpoint.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/oauth2/rfc6749/endpoints/test_resource_owner_association.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/oauth2/rfc6749/endpoints/test_revocation_endpoint.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/oauth2/rfc6749/endpoints/test_scope_handling.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/oauth2/rfc6749/endpoints/test_utils.py +0 -0
- {oauthlib-3.2.2/tests/oauth2/rfc8628 → oauthlib-3.3.0/tests/oauth2/rfc6749/grant_types}/__init__.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/oauth2/rfc6749/grant_types/test_authorization_code.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/oauth2/rfc6749/grant_types/test_client_credentials.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/oauth2/rfc6749/grant_types/test_implicit.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/oauth2/rfc6749/grant_types/test_resource_owner_password.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/oauth2/rfc6749/test_request_validator.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/oauth2/rfc6749/test_server.py +0 -0
- {oauthlib-3.2.2/tests/oauth2/rfc8628/clients → oauthlib-3.3.0/tests/oauth2/rfc8628}/__init__.py +0 -0
- {oauthlib-3.2.2/tests/openid → oauthlib-3.3.0/tests/oauth2/rfc8628/clients}/__init__.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/oauth2/rfc8628/clients/test_device.py +0 -0
- {oauthlib-3.2.2/tests/openid/connect → oauthlib-3.3.0/tests/oauth2/rfc8628/endpoints}/__init__.py +0 -0
- {oauthlib-3.2.2/tests/openid/connect/core → oauthlib-3.3.0/tests/oauth2/rfc8628/grant_types}/__init__.py +0 -0
- {oauthlib-3.2.2/tests/openid/connect/core/endpoints → oauthlib-3.3.0/tests/openid}/__init__.py +0 -0
- {oauthlib-3.2.2/tests/openid/connect/core/grant_types → oauthlib-3.3.0/tests/openid/connect}/__init__.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/openid/connect/core/endpoints/test_claims_handling.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/openid/connect/core/endpoints/test_userinfo_endpoint.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/openid/connect/core/grant_types/test_authorization_code.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/openid/connect/core/grant_types/test_base.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/openid/connect/core/grant_types/test_dispatchers.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/openid/connect/core/grant_types/test_hybrid.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/openid/connect/core/grant_types/test_implicit.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/openid/connect/core/grant_types/test_refresh_token.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/openid/connect/core/test_request_validator.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/openid/connect/core/test_server.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/openid/connect/core/test_tokens.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/test_common.py +0 -0
- {oauthlib-3.2.2 → oauthlib-3.3.0}/tests/unittest/__init__.py +0 -0
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
Changelog
|
|
2
2
|
=========
|
|
3
3
|
|
|
4
|
+
3.3.0 (2025-06-17):
|
|
5
|
+
------------------
|
|
6
|
+
OAuth2.0 Provider:
|
|
7
|
+
* OIDC: #879 Changed in how ui_locales is parsed
|
|
8
|
+
* RFC8628: Added OAuth2.0 Device Authorization Grant support
|
|
9
|
+
* PKCE: #876, #893 Fixed `create_code_verifier` length
|
|
10
|
+
* OIDC: Pre-configured OIDC server to use Refresh Token by default
|
|
11
|
+
|
|
12
|
+
OAuth2.0 Common:
|
|
13
|
+
* OAuth2Error: Allow 0 to be a valid state
|
|
14
|
+
|
|
15
|
+
OAuth2.0 Client:
|
|
16
|
+
* #745: expires_at is forced to be an int
|
|
17
|
+
* #899: expires_at clarification
|
|
18
|
+
|
|
19
|
+
General:
|
|
20
|
+
* Removed Python 3.5, 3.6, 3.7 support
|
|
21
|
+
* #859, #883: Added Python 3.12, 3.13 Support
|
|
22
|
+
* Added dependency-review GitHub Action
|
|
23
|
+
* Updated various references of license (SPDX identifier..)
|
|
24
|
+
* Added GitHub Action for lint, replaced bandy with ruff, removed isort...
|
|
25
|
+
* Migrated to GitHub Actions from Travis
|
|
26
|
+
* Added Security Policy
|
|
27
|
+
|
|
4
28
|
3.2.2 (2022-10-17)
|
|
5
29
|
------------------
|
|
6
30
|
OAuth2.0 Provider:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Copyright (c)
|
|
1
|
+
Copyright (c) The OAuthlib Community
|
|
2
2
|
All rights reserved.
|
|
3
3
|
|
|
4
4
|
Redistribution and use in source and binary forms, with or without
|
|
@@ -11,14 +11,14 @@ modification, are permitted provided that the following conditions are met:
|
|
|
11
11
|
notice, this list of conditions and the following disclaimer in the
|
|
12
12
|
documentation and/or other materials provided with the distribution.
|
|
13
13
|
|
|
14
|
-
3. Neither the name of
|
|
15
|
-
be used to endorse or promote products derived from this
|
|
16
|
-
specific prior written permission.
|
|
14
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
15
|
+
contributors may be used to endorse or promote products derived from this
|
|
16
|
+
software without specific prior written permission.
|
|
17
17
|
|
|
18
18
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
19
19
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
20
20
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
21
|
-
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
21
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
22
22
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
23
23
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
24
24
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
@@ -1,50 +1,65 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: oauthlib
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.3.0
|
|
4
4
|
Summary: A generic, spec-compliant, thorough implementation of the OAuth request-signing logic
|
|
5
5
|
Home-page: https://github.com/oauthlib/oauthlib
|
|
6
6
|
Author: The OAuthlib Community
|
|
7
|
-
|
|
8
|
-
Maintainer:
|
|
9
|
-
|
|
10
|
-
License: BSD
|
|
7
|
+
Maintainer: Jonathan Huot
|
|
8
|
+
Maintainer-email: jonathan.huot@gmail.com
|
|
9
|
+
License: BSD-3-Clause
|
|
11
10
|
Platform: any
|
|
12
11
|
Classifier: Development Status :: 5 - Production/Stable
|
|
13
12
|
Classifier: Environment :: Web Environment
|
|
14
13
|
Classifier: Intended Audience :: Developers
|
|
15
|
-
Classifier: License :: OSI Approved
|
|
16
|
-
Classifier: License :: OSI Approved :: BSD License
|
|
17
14
|
Classifier: Operating System :: MacOS
|
|
18
15
|
Classifier: Operating System :: POSIX
|
|
19
16
|
Classifier: Operating System :: POSIX :: Linux
|
|
20
17
|
Classifier: Programming Language :: Python
|
|
21
18
|
Classifier: Programming Language :: Python :: 3
|
|
22
|
-
Classifier: Programming Language :: Python :: 3.6
|
|
23
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
24
19
|
Classifier: Programming Language :: Python :: 3.8
|
|
25
20
|
Classifier: Programming Language :: Python :: 3.9
|
|
26
21
|
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
27
25
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
28
26
|
Classifier: Programming Language :: Python :: Implementation
|
|
29
27
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
30
28
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
31
29
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
32
|
-
Requires-Python: >=3.
|
|
30
|
+
Requires-Python: >=3.8
|
|
33
31
|
Description-Content-Type: text/x-rst
|
|
32
|
+
License-File: LICENSE
|
|
34
33
|
Provides-Extra: rsa
|
|
34
|
+
Requires-Dist: cryptography>=3.0.0; extra == "rsa"
|
|
35
35
|
Provides-Extra: signedtoken
|
|
36
|
+
Requires-Dist: cryptography>=3.0.0; extra == "signedtoken"
|
|
37
|
+
Requires-Dist: pyjwt<3,>=2.0.0; extra == "signedtoken"
|
|
36
38
|
Provides-Extra: signals
|
|
37
|
-
|
|
39
|
+
Requires-Dist: blinker>=1.4.0; extra == "signals"
|
|
40
|
+
Dynamic: author
|
|
41
|
+
Dynamic: classifier
|
|
42
|
+
Dynamic: description
|
|
43
|
+
Dynamic: description-content-type
|
|
44
|
+
Dynamic: home-page
|
|
45
|
+
Dynamic: license
|
|
46
|
+
Dynamic: license-file
|
|
47
|
+
Dynamic: maintainer
|
|
48
|
+
Dynamic: maintainer-email
|
|
49
|
+
Dynamic: platform
|
|
50
|
+
Dynamic: provides-extra
|
|
51
|
+
Dynamic: requires-python
|
|
52
|
+
Dynamic: summary
|
|
38
53
|
|
|
39
54
|
OAuthLib - Python Framework for OAuth1 & OAuth2
|
|
40
55
|
===============================================
|
|
41
56
|
|
|
42
57
|
*A generic, spec-compliant, thorough implementation of the OAuth request-signing
|
|
43
|
-
logic for Python 3.
|
|
58
|
+
logic for Python 3.8+*
|
|
44
59
|
|
|
45
|
-
.. image:: https://
|
|
46
|
-
:target: https://
|
|
47
|
-
:alt:
|
|
60
|
+
.. image:: https://github.com/oauthlib/oauthlib/actions/workflows/python-build.yml/badge.svg
|
|
61
|
+
:target: https://github.com/oauthlib/oauthlib/actions
|
|
62
|
+
:alt: GitHub Actions
|
|
48
63
|
.. image:: https://coveralls.io/repos/oauthlib/oauthlib/badge.svg?branch=master
|
|
49
64
|
:target: https://coveralls.io/r/oauthlib/oauthlib
|
|
50
65
|
:alt: Coveralls
|
|
@@ -113,7 +128,9 @@ Which web frameworks are supported?
|
|
|
113
128
|
|
|
114
129
|
The following packages provide OAuth support using OAuthLib.
|
|
115
130
|
|
|
116
|
-
- For Django there is
|
|
131
|
+
- For Django there is:
|
|
132
|
+
- `django-oauth-toolkit`_, which includes `Django REST framework`_ support.
|
|
133
|
+
- `django-allauth`_, which includes `Django REST framework`_ as well as `Django Ninja`_ support.
|
|
117
134
|
- For Flask there is `flask-oauthlib`_ and `Flask-Dance`_.
|
|
118
135
|
- For Pyramid there is `pyramid-oauthlib`_.
|
|
119
136
|
- For Bottle there is `bottle-oauthlib`_.
|
|
@@ -127,6 +144,8 @@ please open a Pull Request, updating the documentation.
|
|
|
127
144
|
.. _`Flask-Dance`: https://github.com/singingwolfboy/flask-dance
|
|
128
145
|
.. _`pyramid-oauthlib`: https://github.com/tilgovi/pyramid-oauthlib
|
|
129
146
|
.. _`bottle-oauthlib`: https://github.com/thomsonreuters/bottle-oauthlib
|
|
147
|
+
.. _`django-allauth`: https://allauth.org/
|
|
148
|
+
.. _`Django Ninja`: https://django-ninja.dev/
|
|
130
149
|
|
|
131
150
|
Using OAuthLib? Please get in touch!
|
|
132
151
|
------------------------------------
|
|
@@ -148,7 +167,7 @@ have the pleasure to run into each other, please send a docs pull request =)
|
|
|
148
167
|
License
|
|
149
168
|
-------
|
|
150
169
|
|
|
151
|
-
OAuthLib is yours to use and abuse according to the terms of the BSD license.
|
|
170
|
+
OAuthLib is yours to use and abuse according to the terms of the BSD-3-Clause license.
|
|
152
171
|
Check the LICENSE file for full details.
|
|
153
172
|
|
|
154
173
|
Credits
|
|
@@ -2,11 +2,11 @@ OAuthLib - Python Framework for OAuth1 & OAuth2
|
|
|
2
2
|
===============================================
|
|
3
3
|
|
|
4
4
|
*A generic, spec-compliant, thorough implementation of the OAuth request-signing
|
|
5
|
-
logic for Python 3.
|
|
5
|
+
logic for Python 3.8+*
|
|
6
6
|
|
|
7
|
-
.. image:: https://
|
|
8
|
-
:target: https://
|
|
9
|
-
:alt:
|
|
7
|
+
.. image:: https://github.com/oauthlib/oauthlib/actions/workflows/python-build.yml/badge.svg
|
|
8
|
+
:target: https://github.com/oauthlib/oauthlib/actions
|
|
9
|
+
:alt: GitHub Actions
|
|
10
10
|
.. image:: https://coveralls.io/repos/oauthlib/oauthlib/badge.svg?branch=master
|
|
11
11
|
:target: https://coveralls.io/r/oauthlib/oauthlib
|
|
12
12
|
:alt: Coveralls
|
|
@@ -75,7 +75,9 @@ Which web frameworks are supported?
|
|
|
75
75
|
|
|
76
76
|
The following packages provide OAuth support using OAuthLib.
|
|
77
77
|
|
|
78
|
-
- For Django there is
|
|
78
|
+
- For Django there is:
|
|
79
|
+
- `django-oauth-toolkit`_, which includes `Django REST framework`_ support.
|
|
80
|
+
- `django-allauth`_, which includes `Django REST framework`_ as well as `Django Ninja`_ support.
|
|
79
81
|
- For Flask there is `flask-oauthlib`_ and `Flask-Dance`_.
|
|
80
82
|
- For Pyramid there is `pyramid-oauthlib`_.
|
|
81
83
|
- For Bottle there is `bottle-oauthlib`_.
|
|
@@ -89,6 +91,8 @@ please open a Pull Request, updating the documentation.
|
|
|
89
91
|
.. _`Flask-Dance`: https://github.com/singingwolfboy/flask-dance
|
|
90
92
|
.. _`pyramid-oauthlib`: https://github.com/tilgovi/pyramid-oauthlib
|
|
91
93
|
.. _`bottle-oauthlib`: https://github.com/thomsonreuters/bottle-oauthlib
|
|
94
|
+
.. _`django-allauth`: https://allauth.org/
|
|
95
|
+
.. _`Django Ninja`: https://django-ninja.dev/
|
|
92
96
|
|
|
93
97
|
Using OAuthLib? Please get in touch!
|
|
94
98
|
------------------------------------
|
|
@@ -110,7 +114,7 @@ have the pleasure to run into each other, please send a docs pull request =)
|
|
|
110
114
|
License
|
|
111
115
|
-------
|
|
112
116
|
|
|
113
|
-
OAuthLib is yours to use and abuse according to the terms of the BSD license.
|
|
117
|
+
OAuthLib is yours to use and abuse according to the terms of the BSD-3-Clause license.
|
|
114
118
|
Check the LICENSE file for full details.
|
|
115
119
|
|
|
116
120
|
Credits
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import enum
|
|
2
|
+
import json
|
|
3
|
+
import datetime
|
|
4
|
+
from datetime import timedelta
|
|
5
|
+
|
|
6
|
+
from oauthlib.oauth2 import RequestValidator, Server, DeviceApplicationServer
|
|
7
|
+
from oauthlib.oauth2.rfc8628 import errors as device_flow_errors
|
|
8
|
+
from oauthlib.oauth2.rfc8628.errors import AccessDenied, AuthorizationPendingError, ExpiredTokenError, SlowDownError
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
A pseudocode implementation of the device flow code under an Oauth2 provider.
|
|
13
|
+
|
|
14
|
+
This example is not concerned with openid in any way.
|
|
15
|
+
|
|
16
|
+
This example is also not a 1:1 pseudocode implementation. Please refer to the rfc
|
|
17
|
+
for the full details.
|
|
18
|
+
https://datatracker.ietf.org/doc/html/rfc8628
|
|
19
|
+
|
|
20
|
+
This module is just acting as a way to demonstrate the main pieces
|
|
21
|
+
needed in oauthlib to implement the flow
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
We also assume you already have the /token & /login endpoint in your provider.
|
|
25
|
+
|
|
26
|
+
Your provider will also need the following endpoints(which will be discussed
|
|
27
|
+
in the example below):
|
|
28
|
+
- /device_authorization (part of rfc)
|
|
29
|
+
- /device (part of rfc)
|
|
30
|
+
- /approve-deny (up to your implementation, this is an example)
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
"""
|
|
35
|
+
Device flow pseudocode implementation step by step:
|
|
36
|
+
0. Providing some way to represent the device flow session
|
|
37
|
+
|
|
38
|
+
Some python object to represent the current state of the device during
|
|
39
|
+
the device flow. This, for example, could be an object that persists
|
|
40
|
+
and represents the device in a database
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Device:
|
|
45
|
+
class DeviceFlowStatus(enum.Enum):
|
|
46
|
+
AUTHORIZED = "Authorized"
|
|
47
|
+
AUTHORIZATION_PENDING = "Authorization_pending"
|
|
48
|
+
EXPIRED = "Expired"
|
|
49
|
+
DENIED = "Denied"
|
|
50
|
+
|
|
51
|
+
# https://datatracker.ietf.org/doc/html/rfc8628#section-3.2
|
|
52
|
+
# https://datatracker.ietf.org/doc/html/rfc8628#section-3.4
|
|
53
|
+
id = ... # if Device is representing a database object, this will be the id of that row
|
|
54
|
+
device_code = ...
|
|
55
|
+
user_code = ...
|
|
56
|
+
scope = ...
|
|
57
|
+
interval = ... # in seconds, default is 5
|
|
58
|
+
expires = ... # seconds
|
|
59
|
+
status = ... # DeviceFlowStatus with AUTHORIZATION_PENDING as the default
|
|
60
|
+
|
|
61
|
+
client_id = ...
|
|
62
|
+
last_checked = ... # datetime
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
"""
|
|
66
|
+
1. User goes on their device(client) and the client sends a request to /device_authorization
|
|
67
|
+
against the provider:
|
|
68
|
+
https://datatracker.ietf.org/doc/html/rfc8628#section-3.1
|
|
69
|
+
https://datatracker.ietf.org/doc/html/rfc8628#section-3.2
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
POST /device_authorization HTTP/1.1
|
|
73
|
+
Host: server.example.com
|
|
74
|
+
Content-Type: application/x-www-form-urlencoded
|
|
75
|
+
|
|
76
|
+
client_id=1406020730&scope=example_scope
|
|
77
|
+
|
|
78
|
+
Response:
|
|
79
|
+
HTTP/1.1 200 OK
|
|
80
|
+
Content-Type: application/json
|
|
81
|
+
Cache-Control: no-store
|
|
82
|
+
|
|
83
|
+
{
|
|
84
|
+
"device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS",
|
|
85
|
+
"user_code": "WDJB-MJHT",
|
|
86
|
+
"verification_uri": "https://example.com/device",
|
|
87
|
+
"verification_uri_complete":
|
|
88
|
+
"https://example.com/device?user_code=WDJB-MJHT",
|
|
89
|
+
"expires_in": 1800,
|
|
90
|
+
"interval": 5
|
|
91
|
+
}
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class DeviceAuthorizationEndpoint:
|
|
96
|
+
@staticmethod
|
|
97
|
+
def create_device_authorization_response(request):
|
|
98
|
+
server = DeviceApplicationServer(interval=5, verification_uri="https://example.com/device")
|
|
99
|
+
return server.create_device_authorization_response(request)
|
|
100
|
+
|
|
101
|
+
def post(self, request):
|
|
102
|
+
headers, data, status = self.create_device_authorization_response(request)
|
|
103
|
+
device_response = ...
|
|
104
|
+
|
|
105
|
+
# Create an instance of examples.device_flow.Device` using `request` and `data`that encapsulates
|
|
106
|
+
# https://datatracker.ietf.org/doc/html/rfc8628#section-3.1 &
|
|
107
|
+
# https://datatracker.ietf.org/doc/html/rfc8628#section-3.2
|
|
108
|
+
|
|
109
|
+
return device_response
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
"""
|
|
113
|
+
2. Client presents the information to the user
|
|
114
|
+
(There's a section on non visual capable devices as well
|
|
115
|
+
https://datatracker.ietf.org/doc/html/rfc8628#section-5.7)
|
|
116
|
+
+-------------------------------------------------+
|
|
117
|
+
| |
|
|
118
|
+
| Scan the QR code or, using +------------+ |
|
|
119
|
+
| a browser on another device, |[_].. . [_]| |
|
|
120
|
+
| visit: | . .. . .| |
|
|
121
|
+
| https://example.com/device | . . . ....| |
|
|
122
|
+
| |. . . . | |
|
|
123
|
+
| And enter the code: |[_]. ... . | |
|
|
124
|
+
| WDJB-MJHT +------------+ |
|
|
125
|
+
| |
|
|
126
|
+
+-------------------------------------------------+
|
|
127
|
+
"""
|
|
128
|
+
# The implementation for step 2 is up to the owner of device.
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
""""
|
|
132
|
+
3 (The browser flow). User goes to https://example.com/device where they're presented with a
|
|
133
|
+
form to fill in the user code.
|
|
134
|
+
|
|
135
|
+
Implement that endpoint on your provider and follow the logic in the rfc.
|
|
136
|
+
|
|
137
|
+
Making use of the errors in `oauthlib.oauth2.rfc8628.errors`
|
|
138
|
+
|
|
139
|
+
raise AccessDenied/AuthorizationPendingError/ExpiredTokenError where appropriate making use of
|
|
140
|
+
`examples.device_flow.Device` to get and update current state of the device during the session
|
|
141
|
+
|
|
142
|
+
If the user isn't logged in(after inputting the user-code), they should be redirected to the provider's /login
|
|
143
|
+
endpoint and redirected back to an /approve-deny endpoint(The name and implementation of /approve-deny is up
|
|
144
|
+
to the owner of the provider, this is just an example).
|
|
145
|
+
They should then see an "approve" or "deny" button to authorize the device.
|
|
146
|
+
|
|
147
|
+
Again, using `examples.device_flow.Device` to update the status appropriately during the session.
|
|
148
|
+
"""
|
|
149
|
+
# /device and /approve-deny is up to the owner of the provider to implement. Again, make sure to
|
|
150
|
+
# keep referring to the rfc when implementing.
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
"""
|
|
154
|
+
4 (The polling flow)
|
|
155
|
+
https://datatracker.ietf.org/doc/html/rfc8628#section-3.4
|
|
156
|
+
https://datatracker.ietf.org/doc/html/rfc8628#section-3.5
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
Right after step 2, the device polls the /token endpoint every "interval" amount of seconds
|
|
160
|
+
to check if user has approved or denied the request.
|
|
161
|
+
|
|
162
|
+
When grant type is `urn:ietf:params:oauth:grant-type:device_code`,
|
|
163
|
+
`oauthlib.oauth2.rfc8628.grant_types.device_code.DeviceCodeGrant` will be the handler
|
|
164
|
+
that handles token generation.
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
# This is purely for illustrative purposes
|
|
169
|
+
# to demonstrate rate limiting on the token endpoint for the device flow.
|
|
170
|
+
# It is up to as the provider to decide how you want
|
|
171
|
+
# to rate limit the device during polling.
|
|
172
|
+
def rate_limit(func, rate="1/5s"):
|
|
173
|
+
def wrapper():
|
|
174
|
+
# some logic to ensure client device is rate limited by a minimum
|
|
175
|
+
# of 1 request every 5 seconds during device polling
|
|
176
|
+
# https://datatracker.ietf.org/doc/html/rfc8628#section-3.2
|
|
177
|
+
|
|
178
|
+
# use device_code to retrieve device
|
|
179
|
+
device = Device
|
|
180
|
+
|
|
181
|
+
# get the time in seconds since the device polled the /token endpoint
|
|
182
|
+
now = datetime.datetime.now(tz=datetime.UTC)
|
|
183
|
+
diff = now - timedelta(device.last_checked)
|
|
184
|
+
total_seconds_since_last_device_poll = diff.total_seconds()
|
|
185
|
+
|
|
186
|
+
device.last_checked = now
|
|
187
|
+
|
|
188
|
+
# for illustrative purposes. 1/5s means 1 request every 5 seconds.
|
|
189
|
+
# so if `total_seconds_since_last_device_poll` is 4 seconds, this will
|
|
190
|
+
# raise an error
|
|
191
|
+
if total_seconds_since_last_device_poll < rate:
|
|
192
|
+
raise device_flow_errors.SlowDownError()
|
|
193
|
+
|
|
194
|
+
result = func()
|
|
195
|
+
return result
|
|
196
|
+
|
|
197
|
+
return wrapper
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class ExampleRequestValidator(RequestValidator):
|
|
201
|
+
# All the other methods that need to be implemented...
|
|
202
|
+
# see examples.skeleton_oauth2_web_application_server.SkeletonValidator
|
|
203
|
+
# for a more complete example.
|
|
204
|
+
|
|
205
|
+
# Here our main concern is this method:
|
|
206
|
+
def create_token_response(self): ...
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class ServerSetupForTokenEndpoint:
|
|
210
|
+
def __init__(self):
|
|
211
|
+
validator = ExampleRequestValidator
|
|
212
|
+
self.server = Server(validator)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
# You should already have the /token endpoint implemented in your provider.
|
|
216
|
+
class TokenEndpoint(ServerSetupForTokenEndpoint):
|
|
217
|
+
def default_flow_token_response(self, request):
|
|
218
|
+
url, headers, body, status = self.server.create_token_response(request)
|
|
219
|
+
access_token = json.loads(body).get("access_token")
|
|
220
|
+
|
|
221
|
+
# return access_token in a http response
|
|
222
|
+
return access_token
|
|
223
|
+
|
|
224
|
+
@rate_limit # this will raise the SlowDownError
|
|
225
|
+
def device_flow_token_response(self, request, device_code):
|
|
226
|
+
"""
|
|
227
|
+
Following the rfc, this will route the device request accordingly and raise
|
|
228
|
+
required errors.
|
|
229
|
+
|
|
230
|
+
Remember that unlike other auth flows, the device if polling this endpoint once
|
|
231
|
+
every "interval" amount of seconds.
|
|
232
|
+
"""
|
|
233
|
+
# using device_code arg to retrieve the correct device object instance
|
|
234
|
+
device = Device
|
|
235
|
+
|
|
236
|
+
if device.status == device.DeviceFlowStatus.AUTHORIZATION_PENDING:
|
|
237
|
+
raise AuthorizationPendingError()
|
|
238
|
+
|
|
239
|
+
# If user clicked "deny" in the /approve-deny page endpoint.
|
|
240
|
+
# the device gets set to 'authorized' in /approve-deny and /device checks
|
|
241
|
+
# if someone tries to input a code for a user code that's already been authorized
|
|
242
|
+
if device.status == device.DeviceFlowStatus.DENIED:
|
|
243
|
+
raise AccessDenied()
|
|
244
|
+
|
|
245
|
+
url, headers, body, status = self.server.create_token_response(request)
|
|
246
|
+
|
|
247
|
+
access_token = json.loads(body).get("access_token")
|
|
248
|
+
|
|
249
|
+
device.status = device.EXPIRED
|
|
250
|
+
|
|
251
|
+
# return access_token in a http response
|
|
252
|
+
return access_token
|
|
253
|
+
|
|
254
|
+
# Example of how token endpoint could handle the token creation depending on
|
|
255
|
+
# the grant type during a POST to /token.
|
|
256
|
+
def post(self, request):
|
|
257
|
+
params = request.POST
|
|
258
|
+
if params.get("grant_type") == "urn:ietf:params:oauth:grant-type:device_code":
|
|
259
|
+
return self.device_flow_token_response(request, params["device_code"])
|
|
260
|
+
return self.default_flow_token_response(request)
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Skeleton for an OAuth 2 Web Application Server which is an OAuth
|
|
2
|
+
# provider configured for Authorization Code, Refresh Token grants and
|
|
3
|
+
# for dispensing Bearer Tokens.
|
|
4
|
+
|
|
5
|
+
# This example is meant to act as a supplement to the documentation,
|
|
6
|
+
# see https://oauthlib.readthedocs.io/en/latest/.
|
|
7
|
+
|
|
8
|
+
from oauthlib.oauth2 import RequestValidator, WebApplicationServer
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SkeletonValidator(RequestValidator):
|
|
12
|
+
|
|
13
|
+
# Ordered roughly in order of appearance in the authorization grant flow
|
|
14
|
+
|
|
15
|
+
# Pre- and post-authorization.
|
|
16
|
+
|
|
17
|
+
def validate_client_id(self, client_id, request, *args, **kwargs):
|
|
18
|
+
# Simple validity check, does client exist? Not banned?
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
def validate_redirect_uri(self, client_id, redirect_uri, request, *args, **kwargs):
|
|
22
|
+
# Is the client allowed to use the supplied redirect_uri? i.e. has
|
|
23
|
+
# the client previously registered this EXACT redirect uri.
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
def get_default_redirect_uri(self, client_id, request, *args, **kwargs):
|
|
27
|
+
# The redirect used if none has been supplied.
|
|
28
|
+
# Prefer your clients to pre register a redirect uri rather than
|
|
29
|
+
# supplying one on each authorization request.
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
def validate_scopes(self, client_id, scopes, client, request, *args, **kwargs):
|
|
33
|
+
# Is the client allowed to access the requested scopes?
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
def get_default_scopes(self, client_id, request, *args, **kwargs):
|
|
37
|
+
# Scopes a client will authorize for if none are supplied in the
|
|
38
|
+
# authorization request.
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
def validate_response_type(self, client_id, response_type, client, request, *args, **kwargs):
|
|
42
|
+
# Clients should only be allowed to use one type of response type, the
|
|
43
|
+
# one associated with their one allowed grant type.
|
|
44
|
+
# In this case it must be "code".
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
# Post-authorization
|
|
48
|
+
|
|
49
|
+
def save_authorization_code(self, client_id, code, request, *args, **kwargs):
|
|
50
|
+
# Remember to associate it with request.scopes, request.redirect_uri
|
|
51
|
+
# request.client and request.user (the last is passed in
|
|
52
|
+
# post_authorization credentials, i.e. { 'user': request.user}.
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
# Token request
|
|
56
|
+
|
|
57
|
+
def client_authentication_required(self, request, *args, **kwargs):
|
|
58
|
+
# Check if the client provided authentication information that needs to
|
|
59
|
+
# be validated, e.g. HTTP Basic auth
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
def authenticate_client(self, request, *args, **kwargs):
|
|
63
|
+
# Whichever authentication method suits you, HTTP Basic might work
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
def authenticate_client_id(self, client_id, request, *args, **kwargs):
|
|
67
|
+
# The client_id must match an existing public (non-confidential) client
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
def validate_code(self, client_id, code, client, request, *args, **kwargs):
|
|
71
|
+
# Validate the code belongs to the client. Add associated scopes
|
|
72
|
+
# and user to request.scopes and request.user.
|
|
73
|
+
pass
|
|
74
|
+
|
|
75
|
+
def confirm_redirect_uri(self, client_id, code, redirect_uri, client, request, *args, **kwargs):
|
|
76
|
+
# You did save the redirect uri with the authorization code right?
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
def validate_grant_type(self, client_id, grant_type, client, request, *args, **kwargs):
|
|
80
|
+
# Clients should only be allowed to use one type of grant.
|
|
81
|
+
# In this case, it must be "authorization_code" or "refresh_token"
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
def save_bearer_token(self, token, request, *args, **kwargs):
|
|
85
|
+
# Remember to associate it with request.scopes, request.user and
|
|
86
|
+
# request.client. The two former will be set when you validate
|
|
87
|
+
# the authorization code. Don't forget to save both the
|
|
88
|
+
# access_token and the refresh_token and set expiration for the
|
|
89
|
+
# access_token to now + expires_in seconds.
|
|
90
|
+
pass
|
|
91
|
+
|
|
92
|
+
def invalidate_authorization_code(self, client_id, code, request, *args, **kwargs):
|
|
93
|
+
# Authorization codes are use once, invalidate it when a Bearer token
|
|
94
|
+
# has been acquired.
|
|
95
|
+
pass
|
|
96
|
+
|
|
97
|
+
# Protected resource request
|
|
98
|
+
|
|
99
|
+
def validate_bearer_token(self, token, scopes, request):
|
|
100
|
+
# Remember to check expiration and scope membership
|
|
101
|
+
pass
|
|
102
|
+
|
|
103
|
+
# Token refresh request
|
|
104
|
+
|
|
105
|
+
def get_original_scopes(self, refresh_token, request, *args, **kwargs):
|
|
106
|
+
# Obtain the token associated with the given refresh_token and
|
|
107
|
+
# return its scopes, these will be passed on to the refreshed
|
|
108
|
+
# access token if the client did not specify a scope during the
|
|
109
|
+
# request.
|
|
110
|
+
pass
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
validator = SkeletonValidator()
|
|
114
|
+
server = WebApplicationServer(validator)
|
|
@@ -5,30 +5,30 @@
|
|
|
5
5
|
A generic, spec-compliant, thorough implementation of the OAuth
|
|
6
6
|
request-signing logic.
|
|
7
7
|
|
|
8
|
-
:copyright: (c)
|
|
9
|
-
:license: BSD, see LICENSE for details.
|
|
8
|
+
:copyright: (c) The OAuthlib Community
|
|
9
|
+
:license: BSD-3-Clause, see LICENSE for details.
|
|
10
10
|
"""
|
|
11
11
|
import logging
|
|
12
12
|
from logging import NullHandler
|
|
13
13
|
|
|
14
14
|
__author__ = 'The OAuthlib Community'
|
|
15
|
-
__version__ = '3.
|
|
15
|
+
__version__ = '3.3.0'
|
|
16
16
|
|
|
17
17
|
logging.getLogger('oauthlib').addHandler(NullHandler())
|
|
18
18
|
|
|
19
19
|
_DEBUG = False
|
|
20
20
|
|
|
21
21
|
def set_debug(debug_val):
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
"""Set value of debug flag
|
|
23
|
+
|
|
24
24
|
:param debug_val: Value to set. Must be a bool value.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
"""
|
|
26
|
+
global _DEBUG # noqa: PLW0603
|
|
27
|
+
_DEBUG = debug_val
|
|
28
28
|
|
|
29
29
|
def get_debug():
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
"""Get debug mode value.
|
|
31
|
+
|
|
32
|
+
:return: `True` if debug mode is on, `False` otherwise
|
|
33
|
+
"""
|
|
34
|
+
return _DEBUG
|
|
@@ -34,7 +34,7 @@ INVALID_HEX_PATTERN = re.compile(r'%[^0-9A-Fa-f]|%[0-9A-Fa-f][^0-9A-Fa-f]')
|
|
|
34
34
|
|
|
35
35
|
always_safe = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
|
36
36
|
'abcdefghijklmnopqrstuvwxyz'
|
|
37
|
-
'
|
|
37
|
+
'0123456789_.-')
|
|
38
38
|
|
|
39
39
|
log = logging.getLogger('oauthlib')
|
|
40
40
|
|
|
@@ -198,7 +198,7 @@ def generate_token(length=30, chars=UNICODE_ASCII_CHARACTER_SET):
|
|
|
198
198
|
|
|
199
199
|
|
|
200
200
|
def generate_signed_token(private_pem, request):
|
|
201
|
-
import jwt
|
|
201
|
+
import jwt # noqa: PLC0415
|
|
202
202
|
|
|
203
203
|
now = datetime.datetime.utcnow()
|
|
204
204
|
|
|
@@ -216,7 +216,7 @@ def generate_signed_token(private_pem, request):
|
|
|
216
216
|
|
|
217
217
|
|
|
218
218
|
def verify_signed_token(public_pem, token):
|
|
219
|
-
import jwt
|
|
219
|
+
import jwt # noqa: PLC0415
|
|
220
220
|
|
|
221
221
|
return jwt.decode(token, public_pem, algorithms=['RS256'])
|
|
222
222
|
|
|
@@ -316,7 +316,7 @@ class CaseInsensitiveDict(dict):
|
|
|
316
316
|
return super().__getitem__(key)
|
|
317
317
|
|
|
318
318
|
def get(self, k, default=None):
|
|
319
|
-
return self[k] if k in self else default
|
|
319
|
+
return self[k] if k in self else default # noqa: SIM401
|
|
320
320
|
|
|
321
321
|
def __setitem__(self, k, v):
|
|
322
322
|
super().__setitem__(k, v)
|
|
@@ -346,7 +346,8 @@ class Request:
|
|
|
346
346
|
def __init__(self, uri, http_method='GET', body=None, headers=None,
|
|
347
347
|
encoding='utf-8'):
|
|
348
348
|
# Convert to unicode using encoding if given, else assume unicode
|
|
349
|
-
encode
|
|
349
|
+
def encode(x):
|
|
350
|
+
return to_unicode(x, encoding) if encoding else x
|
|
350
351
|
|
|
351
352
|
self.uri = encode(uri)
|
|
352
353
|
self.http_method = encode(http_method)
|