From e3892e4f92d2ef31989dbb0f3e6b4fe2e69fac1b Mon Sep 17 00:00:00 2001 From: Carl Alexander Adams Date: Sat, 22 Mar 2025 17:27:01 -0700 Subject: [PATCH 1/4] WIP --- .../oidc/oauth_request_auth_enrichers.py | 19 +++++++++++++++++++ src/planet_auth/oidc/request_authenticator.py | 5 +++++ 2 files changed, 24 insertions(+) create mode 100644 src/planet_auth/oidc/oauth_request_auth_enrichers.py diff --git a/src/planet_auth/oidc/oauth_request_auth_enrichers.py b/src/planet_auth/oidc/oauth_request_auth_enrichers.py new file mode 100644 index 0000000..1659b9c --- /dev/null +++ b/src/planet_auth/oidc/oauth_request_auth_enrichers.py @@ -0,0 +1,19 @@ +# Copyright 2025 Planet Labs PBC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# While the whole exercise of talking to OAuth servers is to get access tokens +# that can be used for making requests to HTTP resource servers, those +# requests themselves need to be authenticated in various ways. +# This module provided implementations for various ways that is done. + diff --git a/src/planet_auth/oidc/request_authenticator.py b/src/planet_auth/oidc/request_authenticator.py index 3f85c53..bbc2994 100644 --- a/src/planet_auth/oidc/request_authenticator.py +++ b/src/planet_auth/oidc/request_authenticator.py @@ -64,6 +64,11 @@ def _load(self): # for clients who will be presenting tokens to such a server. We # are inspecting ourselves, not verifying for trust purposes. # We are not expected to be the audience. + # TODO: we should use `expires_in` from the response from the + # OAuth server, which would work for non-JWT opaque OAuth + # tokens. Since that is a relative time, we would also need + # to augment our FileBackedOidcCredential with an issued + # at time. unverified_decoded_atoken = jwt.decode(access_token_str, options={"verify_signature": False}) # nosemgrep iat = unverified_decoded_atoken.get("iat") or 0 exp = unverified_decoded_atoken.get("exp") or 0 From c0addd9705599970b650d24e2ba17f85ee86446e Mon Sep 17 00:00:00 2001 From: Carl Alexander Adams Date: Sun, 30 Mar 2025 08:50:50 -0700 Subject: [PATCH 2/4] Adding more typing --- .../oidc/api_clients/api_client.py | 40 +++++++++++---- .../api_clients/authorization_api_client.py | 14 ++--- .../device_authorization_api_client.py | 34 ++++++++++--- .../oidc/api_clients/discovery_api_client.py | 32 +++++++----- .../oidc/api_clients/introspect_api_client.py | 28 ++++++---- .../oidc/api_clients/jwks_api_client.py | 7 +-- .../oidc/api_clients/oidc_request_auth.py | 13 +++-- .../oidc/api_clients/revocation_api_client.py | 18 ++++--- .../oidc/api_clients/token_api_client.py | 51 ++++++++++++------- .../oidc/api_clients/userinfo_api_client.py | 7 +-- src/planet_auth/oidc/auth_client.py | 31 ++++++----- .../oidc/auth_client_with_client_pubkey.py | 5 +- .../oidc/auth_client_with_client_secret.py | 8 ++- .../auth_clients/client_credentials_flow.py | 7 ++- .../oidc/auth_clients/client_validator.py | 6 +-- .../oidc/oauth_request_auth_enrichers.py | 19 ------- src/planet_auth/util.py | 2 +- .../oidc/test_oidc_auth_client_baseclass.py | 6 +-- tests/test_planet_auth/unit/auth/util.py | 7 ++- 19 files changed, 200 insertions(+), 135 deletions(-) delete mode 100644 src/planet_auth/oidc/oauth_request_auth_enrichers.py diff --git a/src/planet_auth/oidc/api_clients/api_client.py b/src/planet_auth/oidc/api_clients/api_client.py index 1df7f4f..b2b4ee5 100644 --- a/src/planet_auth/oidc/api_clients/api_client.py +++ b/src/planet_auth/oidc/api_clients/api_client.py @@ -13,15 +13,25 @@ # limitations under the License. from abc import ABC -from requests import Session +from requests import Session, Response from requests.adapters import HTTPAdapter from requests.auth import AuthBase +from typing import Callable, Dict, Optional, Tuple from urllib3.util.retry import Retry from planet_auth.auth_client import AuthClientException from planet_auth.constants import X_PLANET_APP_HEADER, X_PLANET_APP from planet_auth.util import parse_content_type +EnricherPayloadType = Dict +# EnricherAudType = str +EnricherReturnType = Tuple[Dict, Optional[AuthBase]] +EnricherFuncType = Callable[[EnricherPayloadType, str], EnricherReturnType] + +_RequestAuthType = AuthBase +_RequestParamsType = Dict # Requests allows a lot more, but constrain our use. +_RequestResponseType = Response + class OidcApiClient(ABC): """ @@ -34,6 +44,8 @@ class OidcApiClient(ABC): # Generally, this will be some combination of the client ID and secret # and may be a header or payload adjustment. But sometimes, we just # need to use an Authorization header. + # TODO: dog-food - use our own RequestAuthenticator like we do for the + # static API key auth client class TokenBearerAuth(AuthBase): def __init__(self, token): self._token = token @@ -42,7 +54,7 @@ def __call__(self, r): r.headers["Authorization"] = "Bearer " + self._token return r - def __init__(self, endpoint_uri): + def __init__(self, endpoint_uri: str): self._endpoint_uri = endpoint_uri retry_strategy = Retry(total=3, backoff_factor=1, status_forcelist=[429], allowed_methods=["POST", "GET"]) @@ -51,7 +63,7 @@ def __init__(self, endpoint_uri): self._session.mount("https://", adapter) # self._session.mount("http://", adapter) - def __check_http_error(self, response): + def __check_http_error(self, response: _RequestResponseType) -> None: if not response.ok: raise OidcApiClientException( message="HTTP error from OIDC endpoint at {}: {}: {}".format( @@ -60,7 +72,7 @@ def __check_http_error(self, response): raw_response=response, ) - def __check_oidc_payload_json_error(self, response): + def __check_oidc_payload_json_error(self, response: _RequestResponseType) -> None: if response.content: ct = parse_content_type(response.headers.get("content-type")) if not ct["content-type"] == "application/json": @@ -89,7 +101,7 @@ def __check_oidc_payload_json_error(self, response): ) @staticmethod - def __checked_json_response(response): + def __checked_json_response(response: _RequestResponseType) -> Dict: json_response = None if response.content: ct = parse_content_type(response.headers.get("content-type")) @@ -106,13 +118,15 @@ def __checked_json_response(response): ) return json_response - def __check_response(self, response): + def __check_response(self, response: _RequestResponseType) -> None: # Check for the json error first so we throw a more specific parsed # error if we understand it, regardless of HTTP status code. self.__check_oidc_payload_json_error(response) self.__check_http_error(response) - def _checked_get(self, params, request_auth): + def _checked_get( + self, params: Optional[_RequestParamsType], request_auth: Optional[_RequestAuthType] + ) -> _RequestResponseType: response = self._session.get( self._endpoint_uri, params=params, @@ -122,7 +136,9 @@ def _checked_get(self, params, request_auth): self.__check_response(response) return response - def _checked_post(self, params, request_auth): + def _checked_post( + self, params: Optional[_RequestParamsType], request_auth: Optional[_RequestAuthType] + ) -> _RequestResponseType: response = self._session.post( self._endpoint_uri, # Note: is the data/params crossing confusing? This was born out @@ -139,10 +155,14 @@ def _checked_post(self, params, request_auth): self.__check_response(response) return response - def _checked_post_json_response(self, params, request_auth): + def _checked_post_json_response( + self, params: Optional[_RequestParamsType], request_auth: Optional[_RequestAuthType] + ) -> Dict: return self.__checked_json_response(self._checked_post(params, request_auth)) - def _checked_get_json_response(self, params, request_auth): + def _checked_get_json_response( + self, params: Optional[_RequestParamsType], request_auth: Optional[_RequestAuthType] + ) -> Dict: return self.__checked_json_response(self._checked_get(params, request_auth)) diff --git a/src/planet_auth/oidc/api_clients/authorization_api_client.py b/src/planet_auth/oidc/api_clients/authorization_api_client.py index 54143d9..d407099 100644 --- a/src/planet_auth/oidc/api_clients/authorization_api_client.py +++ b/src/planet_auth/oidc/api_clients/authorization_api_client.py @@ -19,7 +19,7 @@ from http import HTTPStatus from urllib.parse import urlparse, parse_qs, urlencode -from typing import List +from typing import Dict, List, Optional from webbrowser import open_new import planet_auth.logging.auth_logger @@ -125,7 +125,9 @@ class AuthorizationApiClient: interactive authentication can be performed. """ - def __init__(self, authorization_uri=None, authorization_callback_acknowledgement_response_body=None): + def __init__( + self, authorization_uri: str, authorization_callback_acknowledgement_response_body: Optional[str] = None + ): """ Create a new Authorization API Client. """ @@ -146,8 +148,8 @@ def prep_pkce_auth_payload( requested_scopes: List[str], requested_audiences: List[str], pkce_code_challenge: str, - extra: dict, - ) -> dict: + extra: Dict, + ) -> Dict: """ Prepare the payload needed to make an authorization request to an OAuth authorization endpoint. This will usually be used to construct @@ -218,7 +220,7 @@ def authcode_from_pkce_auth_request_with_browser_and_callback_listener( requested_scopes: List[str], requested_audiences: List[str], pkce_code_challenge: str, - extra: dict, + extra: Dict, ) -> str: """ Request an authorization code by launching a web browser directed to the @@ -309,7 +311,7 @@ def authcode_from_pkce_auth_request_with_tty_input( requested_scopes: List[str], requested_audiences: List[str], pkce_code_challenge: str, - extra: dict, + extra: Dict, ) -> str: """ Request an authorization code by prompting the user to visit a diff --git a/src/planet_auth/oidc/api_clients/device_authorization_api_client.py b/src/planet_auth/oidc/api_clients/device_authorization_api_client.py index 489e1d7..d2a69b0 100644 --- a/src/planet_auth/oidc/api_clients/device_authorization_api_client.py +++ b/src/planet_auth/oidc/api_clients/device_authorization_api_client.py @@ -12,7 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -from planet_auth.oidc.api_clients.api_client import OidcApiClient, OidcApiClientException +from typing import Dict, List, Optional + +from planet_auth.oidc.api_clients.api_client import ( + OidcApiClient, + OidcApiClientException, + EnricherFuncType, + _RequestParamsType, + _RequestAuthType, +) class DeviceAuthorizationApiException(OidcApiClientException): @@ -28,11 +36,16 @@ class DeviceAuthorizationApiClient(OidcApiClient): All invalid responses or error responses will result in an exception. """ - def __init__(self, device_authorization_uri=None): + def __init__(self, device_authorization_uri: str): super().__init__(endpoint_uri=device_authorization_uri) @staticmethod - def _prep_device_code_request_payload(client_id, requested_scopes, requested_audiences, extra): + def _prep_device_code_request_payload( + client_id, + requested_scopes: Optional[List[str]], + requested_audiences: Optional[List[str]], + extra: Optional[Dict], + ) -> Dict: if extra is None: extra = {} # "None" is pythonic, and does not mean anything to OAuth APIs. @@ -50,7 +63,7 @@ def _prep_device_code_request_payload(client_id, requested_scopes, requested_aud return data @staticmethod - def _check_device_auth_response(json_response): + def _check_device_auth_response(json_response: Dict) -> Dict: # Protocol endpoint specific response checks if not json_response.get("device_code"): raise DeviceAuthorizationApiException( @@ -71,11 +84,20 @@ def _check_device_auth_response(json_response): # verification_uri_complete and interval are optional under the spec, so we don't force them to be present. return json_response - def _checked_request_device_code_call(self, request_params, request_auth): + def _checked_request_device_code_call( + self, request_params: _RequestParamsType, request_auth: Optional[_RequestAuthType] + ) -> Dict: json_response = self._checked_post_json_response(request_params, request_auth) return self._check_device_auth_response(json_response) - def request_device_code(self, client_id: str, requested_scopes, requested_audiences, auth_enricher, extra): + def request_device_code( + self, + client_id: str, + requested_scopes: Optional[List[str]], + requested_audiences: Optional[List[str]], + auth_enricher: Optional[EnricherFuncType], + extra, + ) -> Dict: request_params = self._prep_device_code_request_payload( client_id=client_id, requested_scopes=requested_scopes, diff --git a/src/planet_auth/oidc/api_clients/discovery_api_client.py b/src/planet_auth/oidc/api_clients/discovery_api_client.py index ce600b9..5846763 100644 --- a/src/planet_auth/oidc/api_clients/discovery_api_client.py +++ b/src/planet_auth/oidc/api_clients/discovery_api_client.py @@ -12,12 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -from planet_auth.oidc.api_clients.api_client import OidcApiClient +from typing import Dict, Optional + +from planet_auth.oidc.api_clients.api_client import OidcApiClient, OidcApiClientException -# class DiscoveryApiException(OidcApiClientException): -# -# def __init__(self, message=None, raw_response=None): -# super().__init__(message, raw_response) + +class DiscoveryApiException(OidcApiClientException): + def __init__(self, message=None, raw_response=None): + super().__init__(message, raw_response) class DiscoveryApiClient(OidcApiClient): @@ -28,28 +30,32 @@ class DiscoveryApiClient(OidcApiClient): # TODO: Revisit if this is where I should cache. I did work # on the JWKS client after this, and I think it is more mature. - def __init__(self, discovery_uri=None, auth_server=None): + def __init__(self, discovery_uri: Optional[str] = None, auth_server: Optional[str] = None): """ Create a new OIDC discovery API client. """ if discovery_uri: d_uri = discovery_uri else: - if auth_server.endswith("/"): - d_uri = auth_server + ".well-known/openid-configuration" + if auth_server: + if auth_server.endswith("/"): + d_uri = auth_server + ".well-known/openid-configuration" + else: + d_uri = auth_server + "/.well-known/openid-configuration" else: - d_uri = auth_server + "/.well-known/openid-configuration" + raise DiscoveryApiException("One of discovery_uri or auth_server must be provided") + super().__init__(d_uri) - self._oidc_discovery = None + self._oidc_discovery: Dict = None # type: ignore - def do_discovery(self): + def do_discovery(self) -> None: """ Contact the discovery endpoint, download discovery information, and cache the results inside the client. """ self._oidc_discovery = self._checked_get_json_response(None, None) - def do_discovery_jit(self): + def do_discovery_jit(self) -> None: """ Contact the discovery endpoint and download and cache the results only if discovery had not previously been performed and cached. @@ -57,7 +63,7 @@ def do_discovery_jit(self): if not self._oidc_discovery: self.do_discovery() - def discovery(self): + def discovery(self) -> Dict: """ Return the discovery information. If the information was previously fetched, the cached information will be returned, and no network connection will be made. diff --git a/src/planet_auth/oidc/api_clients/introspect_api_client.py b/src/planet_auth/oidc/api_clients/introspect_api_client.py index 2ceba96..1c1c0f4 100644 --- a/src/planet_auth/oidc/api_clients/introspect_api_client.py +++ b/src/planet_auth/oidc/api_clients/introspect_api_client.py @@ -12,9 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Dict, Optional + from planet_auth.auth_exception import InvalidTokenException from planet_auth.logging.events import AuthEvent -from planet_auth.oidc.api_clients.api_client import OidcApiClient, OidcApiClientException +from planet_auth.oidc.api_clients.api_client import ( + OidcApiClient, + OidcApiClientException, + EnricherFuncType, + _RequestAuthType, + _RequestParamsType, +) class IntrospectionApiException(OidcApiClientException): @@ -36,14 +44,14 @@ class IntrospectionApiClient(OidcApiClient): is not active will result in an exception. """ - def __init__(self, introspect_uri=None): + def __init__(self, introspect_uri: str): """ Create a new token introspection API client """ super().__init__(endpoint_uri=introspect_uri) @staticmethod - def _check_introspection_response(json_response): + def _check_introspection_response(json_response: Dict) -> Dict: if not isinstance(json_response.get("active"), bool): raise IntrospectionApiException( message="Unrecognized response: 'active' field was not present or was not boolean in the response." @@ -55,17 +63,19 @@ def _check_introspection_response(json_response): return json_response @staticmethod - def check_introspection_response(json_response): + def check_introspection_response(json_response: Dict) -> Dict: # Like above, but for external utility consumption if not json_response: raise IntrospectionApiException(message="Invalid response: introspection result cannot be empty.") return IntrospectionApiClient._check_introspection_response(json_response) - def _checked_introspection_call(self, validate_params, auth): + def _checked_introspection_call( + self, validate_params: _RequestParamsType, auth: Optional[_RequestAuthType] + ) -> Dict: json_response = self._checked_post_json_response(validate_params, auth) return self._check_introspection_response(json_response) - def _validate_token(self, token, token_hint, auth_enricher): + def _validate_token(self, token: str, token_hint: str, auth_enricher: Optional[EnricherFuncType] = None) -> Dict: params = { "token": token, "token_type_hint": token_hint, @@ -75,21 +85,21 @@ def _validate_token(self, token, token_hint, auth_enricher): params, request_auth = auth_enricher(params, self._endpoint_uri) return self._checked_introspection_call(params, request_auth) - def validate_access_token(self, access_token, auth_enricher=None): + def validate_access_token(self, access_token: str, auth_enricher: Optional[EnricherFuncType] = None) -> Dict: """ Validate an access token against the OAuth introspection endpoint. Invalid tokens will result in an exception. """ return self._validate_token(access_token, "access_token", auth_enricher) - def validate_id_token(self, id_token, auth_enricher=None): + def validate_id_token(self, id_token: str, auth_enricher: Optional[EnricherFuncType] = None) -> Dict: """ Validate an ID token against the OAuth introspection endpoint. Invalid tokens will result in an exception. """ return self._validate_token(id_token, "id_token", auth_enricher) - def validate_refresh_token(self, refresh_token, auth_enricher): + def validate_refresh_token(self, refresh_token: str, auth_enricher: Optional[EnricherFuncType] = None) -> Dict: """ Validate a refresh token against the OAuth introspection endpoint. Invalid tokens will result in an exception. diff --git a/src/planet_auth/oidc/api_clients/jwks_api_client.py b/src/planet_auth/oidc/api_clients/jwks_api_client.py index 87d68ad..712b8c7 100644 --- a/src/planet_auth/oidc/api_clients/jwks_api_client.py +++ b/src/planet_auth/oidc/api_clients/jwks_api_client.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Dict, List from planet_auth.oidc.api_clients.api_client import OidcApiClient, OidcApiClientException @@ -28,7 +29,7 @@ class JwksApiClient(OidcApiClient): for more information. """ - def __init__(self, jwks_uri): + def __init__(self, jwks_uri: str): """ Create a JWKS endpoint client """ @@ -37,7 +38,7 @@ def __init__(self, jwks_uri): def _checked_fetch(self): return self._checked_get_json_response(None, None) - def jwks(self) -> dict: + def jwks(self) -> Dict: """ Fetch metadata from the JWKS endpoint. @@ -47,7 +48,7 @@ def jwks(self) -> dict: """ return self._checked_fetch() - def jwks_keys(self) -> list: + def jwks_keys(self) -> List: """ Fetch keys from the JWKS endpoint. diff --git a/src/planet_auth/oidc/api_clients/oidc_request_auth.py b/src/planet_auth/oidc/api_clients/oidc_request_auth.py index 5923b21..138f7e3 100644 --- a/src/planet_auth/oidc/api_clients/oidc_request_auth.py +++ b/src/planet_auth/oidc/api_clients/oidc_request_auth.py @@ -27,8 +27,11 @@ import time import uuid +from typing import Dict from requests.auth import HTTPBasicAuth +from planet_auth.oidc.api_clients.api_client import _RequestAuthType, _RequestParamsType + def _prepare_oidc_client_jwt_payload(audience: str, client_id: str, ttl: int): # See @@ -53,23 +56,25 @@ def prepare_oidc_client_private_key_jwt(audience: str, client_id: str, private_k return signed_jwt -def prepare_client_noauth_auth_payload(client_id: str): +def prepare_client_noauth_auth_payload(client_id: str) -> Dict: client_secret_auth_payload = { "client_id": client_id, } return client_secret_auth_payload -def prepare_client_secret_request_auth(client_id: str, client_secret: str): +def prepare_client_secret_request_auth(client_id: str, client_secret: str) -> _RequestAuthType: return HTTPBasicAuth(client_id, client_secret) -def prepare_client_secret_auth_payload(client_id: str, client_secret: str): +def prepare_client_secret_auth_payload(client_id: str, client_secret: str) -> _RequestParamsType: client_secret_auth_payload = {"client_id": client_id, "client_secret": client_secret} return client_secret_auth_payload -def prepare_private_key_assertion_auth_payload(audience: str, client_id: str, private_key, ttl: int): +def prepare_private_key_assertion_auth_payload( + audience: str, client_id: str, private_key, ttl: int +) -> _RequestParamsType: signed_jwt = prepare_oidc_client_private_key_jwt( audience=audience, client_id=client_id, private_key=private_key, ttl=ttl ) diff --git a/src/planet_auth/oidc/api_clients/revocation_api_client.py b/src/planet_auth/oidc/api_clients/revocation_api_client.py index 802787b..aeb086a 100644 --- a/src/planet_auth/oidc/api_clients/revocation_api_client.py +++ b/src/planet_auth/oidc/api_clients/revocation_api_client.py @@ -12,7 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -from planet_auth.oidc.api_clients.api_client import OidcApiClient +from typing import Optional +from planet_auth.oidc.api_clients.api_client import ( + OidcApiClient, + EnricherFuncType, + _RequestParamsType, + _RequestAuthType, +) # class RevocationAPIException(OidcApiClientException): # @@ -26,13 +32,13 @@ class RevocationApiClient(OidcApiClient): RFC 7009 - OAuth 2.0 Token Revocation for protocol details. """ - def __init__(self, revocation_uri): + def __init__(self, revocation_uri: str): """ Create a new Revocation API Client. """ super().__init__(endpoint_uri=revocation_uri) - def _checked_revocation_call(self, params, request_auth): + def _checked_revocation_call(self, params: _RequestParamsType, request_auth: Optional[_RequestAuthType]) -> None: self._checked_post(params, request_auth) # if response.content: # # No payload expected on success. All HTTP and known json error @@ -41,7 +47,7 @@ def _checked_revocation_call(self, params, request_auth): # message='Unexpected response from OIDC Revocation endpoint', # raw_response=response) - def _revoke_token(self, token: str, token_hint: str, auth_enricher=None): + def _revoke_token(self, token: str, token_hint: str, auth_enricher: Optional[EnricherFuncType] = None) -> None: params = { "token": token, "token_type_hint": token_hint, @@ -52,13 +58,13 @@ def _revoke_token(self, token: str, token_hint: str, auth_enricher=None): params, request_auth = auth_enricher(params, self._endpoint_uri) self._checked_revocation_call(params, request_auth) - def revoke_access_token(self, access_token: str, auth_enricher=None) -> None: + def revoke_access_token(self, access_token: str, auth_enricher: Optional[EnricherFuncType] = None) -> None: """ Revoke the specified access token. """ self._revoke_token(access_token, "access_token", auth_enricher) - def revoke_refresh_token(self, refresh_token: str, auth_enricher=None) -> None: + def revoke_refresh_token(self, refresh_token: str, auth_enricher: Optional[EnricherFuncType] = None) -> None: """ Revoke the specified refresh token. """ diff --git a/src/planet_auth/oidc/api_clients/token_api_client.py b/src/planet_auth/oidc/api_clients/token_api_client.py index 927b90c..06d3629 100644 --- a/src/planet_auth/oidc/api_clients/token_api_client.py +++ b/src/planet_auth/oidc/api_clients/token_api_client.py @@ -13,10 +13,15 @@ # limitations under the License. import time -from typing import Any, List +from typing import Dict, List, Optional import planet_auth.logging.auth_logger -from planet_auth.oidc.api_clients.api_client import OidcApiClient, OidcApiClientException +from planet_auth.oidc.api_clients.api_client import ( + OidcApiClient, + OidcApiClientException, + EnricherFuncType, + _RequestParamsType, +) auth_logger = planet_auth.logging.auth_logger.getAuthLogger() @@ -36,14 +41,14 @@ class TokenApiClient(OidcApiClient): RFC 6749 - The OAuth 2.0 Authorization Framework for protocol details. """ - def __init__(self, token_uri): + def __init__(self, token_uri: str): """ Create a new Token API Client. """ super().__init__(endpoint_uri=token_uri) @staticmethod - def _check_valid_token_response(json_response): + def _check_valid_token_response(json_response: Dict) -> None: if not json_response.get("expires_in"): # https://datatracker.ietf.org/doc/html/rfc6749#section-5.1 # Note: while OAuth requires the access_token field, and OIDC @@ -57,7 +62,9 @@ def _check_valid_token_response(json_response): raise TokenApiException(message="Invalid token received. Missing expires_in field.") # auth_logger.warning(msg='Token response was missing expires_in field.') - def _checked_call(self, token_params, auth_enricher=None): + def _checked_call( + self, token_params: _RequestParamsType, auth_enricher: Optional[EnricherFuncType] = None + ) -> Dict: request_auth = None if auth_enricher: token_params, request_auth = auth_enricher(token_params, self._endpoint_uri) @@ -66,7 +73,13 @@ def _checked_call(self, token_params, auth_enricher=None): self._check_valid_token_response(json_response) return json_response - def _polling_checked_call(self, token_params, timeout, poll_interval, auth_enricher=None): + def _polling_checked_call( + self, + token_params: _RequestParamsType, + timeout: float, + poll_interval: float, + auth_enricher: Optional[EnricherFuncType] = None, + ) -> Dict: start_time = time.time() while True: try: @@ -92,9 +105,9 @@ def get_token_from_refresh( client_id: str, refresh_token: str, requested_scopes: List[str] = None, - auth_enricher: Any = None, - extra: dict = None, - ) -> dict: + auth_enricher: Optional[EnricherFuncType] = None, + extra: Dict = None, + ) -> Dict: """ Obtain tokens using a refresh token. @@ -141,9 +154,9 @@ def get_token_from_client_credentials( client_id: str, requested_scopes: List[str] = None, requested_audiences: List[str] = None, - auth_enricher: Any = None, - extra: dict = None, - ) -> dict: + auth_enricher: Optional[EnricherFuncType] = None, + extra: Dict = None, + ) -> Dict: """ Obtain tokens using client credentials and the client credentials grant OAuth flow. @@ -194,9 +207,9 @@ def get_token_from_code( client_id: str, code: str, code_verifier: str, - auth_enricher: Any = None, + auth_enricher: Optional[EnricherFuncType] = None, # extra=None, # This should be in the auth request, not the code redemption. - ) -> dict: + ) -> Dict: """ Obtain tokens using an authorization code. @@ -227,9 +240,9 @@ def poll_for_token_from_device_code( device_code: str, timeout: int, poll_interval: int = 5, # Default poll interval specified in RFC 8628 - auth_enricher: Any = None, + auth_enricher: Optional[EnricherFuncType] = None, # extra=None, # This should be in the auth request, not the code redemption. - ) -> dict: + ) -> Dict: """ Poll for the completion of a device code login. @@ -260,9 +273,9 @@ def get_token_from_password( password: str, requested_scopes: List[str] = None, requested_audiences: List[str] = None, - auth_enricher: Any = None, - extra: dict = None, - ) -> dict: + auth_enricher: Optional[EnricherFuncType] = None, + extra: Dict = None, + ) -> Dict: """ Obtain tokens using a username and password and the resource owner grant OAuth flow. diff --git a/src/planet_auth/oidc/api_clients/userinfo_api_client.py b/src/planet_auth/oidc/api_clients/userinfo_api_client.py index c1b696f..d92b103 100644 --- a/src/planet_auth/oidc/api_clients/userinfo_api_client.py +++ b/src/planet_auth/oidc/api_clients/userinfo_api_client.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Dict from planet_auth.oidc.api_clients.api_client import OidcApiClient @@ -26,16 +27,16 @@ class UserinfoApiClient(OidcApiClient): network endpoint. """ - def __init__(self, userinfo_uri=None): + def __init__(self, userinfo_uri: str): """ Create a new token Userinfo API client """ super().__init__(endpoint_uri=userinfo_uri) - def _checked_userinfo_call(self, access_token): + def _checked_userinfo_call(self, access_token: str) -> Dict: return self._checked_get_json_response(params=None, request_auth=self.TokenBearerAuth(access_token)) - def userinfo_from_access_token(self, access_token: str) -> dict: + def userinfo_from_access_token(self, access_token: str) -> Dict: """ Obtain user information from the authorization server for the user who owns the presented access token. diff --git a/src/planet_auth/oidc/auth_client.py b/src/planet_auth/oidc/auth_client.py index 42314a2..d87f514 100644 --- a/src/planet_auth/oidc/auth_client.py +++ b/src/planet_auth/oidc/auth_client.py @@ -13,10 +13,10 @@ # limitations under the License. from abc import abstractmethod, ABC -from requests.auth import AuthBase -from typing import List, Optional, Tuple +from typing import Dict, List, Optional from planet_auth.auth_client import AuthClientConfig, AuthClient, AuthClientConfigException, AuthClientException +from planet_auth.oidc.api_clients.api_client import EnricherPayloadType, EnricherReturnType from planet_auth.oidc.api_clients.authorization_api_client import AuthorizationApiClient from planet_auth.oidc.api_clients.device_authorization_api_client import DeviceAuthorizationApiClient from planet_auth.oidc.api_clients.discovery_api_client import DiscoveryApiClient @@ -347,7 +347,7 @@ def token_client(self): # level concept of "OIDC client type" imposes itself on some of # the low level protocol interactions. @abstractmethod - def _client_auth_enricher(self, raw_payload: dict, audience: str) -> Tuple[dict, Optional[AuthBase]]: + def _client_auth_enricher(self, raw_payload: EnricherPayloadType, audience: str) -> EnricherReturnType: """ Some OIDC endpoints require client auth, and how auth is done can vary depending on how the OIDC provider is configured to handle the @@ -361,6 +361,9 @@ def _client_auth_enricher(self, raw_payload: dict, audience: str) -> Tuple[dict, If no enrichment is needed or appropriate, implementations should return the raw payload unmodified, and None for the AuthBase. + The signature of this abstract method is expected to adhere to + the api_client.EnricherFuncType function signature. + See https://developer.okta.com/docs/reference/api/oidc/#client-authentication-methods """ @@ -389,7 +392,7 @@ def login( allow_tty_prompt: Optional[bool] = False, requested_scopes: Optional[List[str]] = None, requested_audiences: Optional[List[str]] = None, - extra: Optional[dict] = None, + extra: Optional[Dict] = None, **kwargs, ): """ @@ -430,7 +433,7 @@ def _oidc_flow_login( allow_tty_prompt: Optional[bool], requested_scopes: Optional[List[str]], requested_audiences: Optional[List[str]], - extra: Optional[dict], + extra: Optional[Dict], **kwargs, ) -> FileBackedOidcCredential: """ @@ -439,7 +442,7 @@ def _oidc_flow_login( """ def refresh( - self, refresh_token: str, requested_scopes: List[str] = None, extra: Optional[dict] = None + self, refresh_token: str, requested_scopes: List[str] = None, extra: Optional[Dict] = None ) -> FileBackedOidcCredential: """ Refresh auth tokens using the provided refresh token @@ -478,7 +481,7 @@ def refresh( ) ) - def validate_access_token_remote(self, access_token: str) -> dict: + def validate_access_token_remote(self, access_token: str) -> Dict: """ Validate the access token against the OIDC token introspection endpoint Parameters: @@ -490,7 +493,7 @@ def validate_access_token_remote(self, access_token: str) -> dict: def validate_access_token_local( self, access_token: str, required_audience: str = None, scopes_anyof: list = None - ) -> dict: + ) -> Dict: # Note: # Tokens may have multiple audiences. When someone requests a # token with multiple audiences, the expectation during login is @@ -530,7 +533,7 @@ def validate_access_token_local( scopes_anyof=scopes_anyof, ) - def validate_id_token_remote(self, id_token: str) -> dict: + def validate_id_token_remote(self, id_token: str) -> Dict: """ Validate the ID token against the OIDC token introspection endpoint Parameters: @@ -540,7 +543,7 @@ def validate_id_token_remote(self, id_token: str) -> dict: """ return self.introspection_client().validate_id_token(id_token, self._client_auth_enricher) - def validate_id_token_local(self, id_token: str) -> dict: + def validate_id_token_local(self, id_token: str) -> Dict: """ Validate the ID token locally. A remote connection may still be made to obtain signing keys. @@ -553,7 +556,7 @@ def validate_id_token_local(self, id_token: str) -> dict: token_str=id_token, issuer=self._issuer(), client_id=self._oidc_client_config.client_id() ) - def validate_refresh_token_remote(self, refresh_token: str) -> dict: + def validate_refresh_token_remote(self, refresh_token: str) -> Dict: """ Validate the refresh token against the OIDC token introspection endpoint @@ -580,7 +583,7 @@ def revoke_refresh_token(self, refresh_token: str) -> None: """ self.revocation_client().revoke_refresh_token(refresh_token, self._client_auth_enricher) - def userinfo_from_access_token(self, access_token: str) -> dict: + def userinfo_from_access_token(self, access_token: str) -> Dict: """ Look up user information from the auth server using the access token. Parameters: @@ -588,7 +591,7 @@ def userinfo_from_access_token(self, access_token: str) -> dict: """ return self.userinfo_client().userinfo_from_access_token(access_token=access_token) - def oidc_discovery(self) -> dict: + def oidc_discovery(self) -> Dict: """ Query the authorization server's OIDC discovery endpoint for server information. Returns: @@ -611,7 +614,7 @@ class OidcAuthClientWithNoneClientAuth(OidcAuthClient, ABC): Mix-in base class for "public" (non-confidential) OAuth/OIDC auth clients. """ - def _client_auth_enricher(self, raw_payload: dict, audience: str) -> Tuple[dict, Optional[AuthBase]]: + def _client_auth_enricher(self, raw_payload: EnricherPayloadType, audience: str) -> EnricherReturnType: auth_payload = prepare_client_noauth_auth_payload(client_id=self._oidc_client_config.client_id()) enriched_payload = {**raw_payload, **auth_payload} return enriched_payload, None diff --git a/src/planet_auth/oidc/auth_client_with_client_pubkey.py b/src/planet_auth/oidc/auth_client_with_client_pubkey.py index 53bab82..4814fcb 100644 --- a/src/planet_auth/oidc/auth_client_with_client_pubkey.py +++ b/src/planet_auth/oidc/auth_client_with_client_pubkey.py @@ -15,10 +15,9 @@ from abc import ABC from cryptography.hazmat.primitives import serialization as crypto_serialization -from requests.auth import AuthBase -from typing import Optional, Tuple from planet_auth.auth_client import AuthClientConfigException +from planet_auth.oidc.api_clients.api_client import EnricherPayloadType, EnricherReturnType from planet_auth.oidc.api_clients.oidc_request_auth import prepare_private_key_assertion_auth_payload from planet_auth.oidc.auth_client import OidcAuthClient, OidcAuthClientConfig @@ -149,7 +148,7 @@ def __init__(self, client_config: OidcAuthClientWithPubKeyClientConfig): super().__init__(client_config) self._oidc_pubkey_client_config = client_config - def _client_auth_enricher(self, raw_payload: dict, audience: str) -> Tuple[dict, Optional[AuthBase]]: + def _client_auth_enricher(self, raw_payload: EnricherPayloadType, audience: str) -> EnricherReturnType: auth_assertion_payload = prepare_private_key_assertion_auth_payload( audience=audience, client_id=self._oidc_pubkey_client_config.client_id(), diff --git a/src/planet_auth/oidc/auth_client_with_client_secret.py b/src/planet_auth/oidc/auth_client_with_client_secret.py index 210fdf6..b55b7bc 100644 --- a/src/planet_auth/oidc/auth_client_with_client_secret.py +++ b/src/planet_auth/oidc/auth_client_with_client_secret.py @@ -14,9 +14,7 @@ from abc import ABC -from requests.auth import AuthBase -from typing import Optional, Tuple - +from planet_auth.oidc.api_clients.api_client import EnricherPayloadType, EnricherReturnType from planet_auth.auth_client import AuthClientConfigException from planet_auth.oidc.api_clients.oidc_request_auth import ( prepare_client_secret_request_auth, @@ -70,7 +68,7 @@ def __init__(self, client_config: OidcAuthClientWithClientSecretClientConfig): super().__init__(client_config) self._oidc_client_secret_client_config = client_config - def _client_auth_enricher(self, raw_payload: dict, audience: str) -> Tuple[dict, Optional[AuthBase]]: + def _client_auth_enricher(self, raw_payload: EnricherPayloadType, audience: str) -> EnricherReturnType: return raw_payload, prepare_client_secret_request_auth( self._oidc_client_secret_client_config.client_id(), self._oidc_client_secret_client_config.client_secret() ) @@ -86,7 +84,7 @@ def __init__(self, client_config: OidcAuthClientWithClientSecretClientConfig): super().__init__(client_config) self._oidc_client_secret_client_config = client_config - def _client_auth_enricher(self, raw_payload: dict, audience: str) -> Tuple[dict, Optional[AuthBase]]: + def _client_auth_enricher(self, raw_payload: EnricherPayloadType, audience: str) -> EnricherReturnType: auth_payload = prepare_client_secret_auth_payload( client_id=self._oidc_client_secret_client_config.client_id(), client_secret=self._oidc_client_secret_client_config.client_secret(), diff --git a/src/planet_auth/oidc/auth_clients/client_credentials_flow.py b/src/planet_auth/oidc/auth_clients/client_credentials_flow.py index 9fe4c3c..f004e81 100644 --- a/src/planet_auth/oidc/auth_clients/client_credentials_flow.py +++ b/src/planet_auth/oidc/auth_clients/client_credentials_flow.py @@ -13,10 +13,9 @@ # limitations under the License. from abc import ABC +from typing import List, Optional -from requests.auth import AuthBase -from typing import List, Optional, Tuple - +from planet_auth.oidc.api_clients.api_client import EnricherPayloadType, EnricherReturnType from planet_auth.oidc.api_clients.oidc_request_auth import ( prepare_client_secret_auth_payload, ) @@ -81,7 +80,7 @@ def __init__(self, client_config: ClientCredentialsClientSecretClientConfig): # us to send it an an auth header. We prefer this as the default since # an auth header should be less likely to land in a log than URL # parameters or request payloads. - def _client_auth_enricher_login(self, raw_payload: dict, audience: str) -> Tuple[dict, Optional[AuthBase]]: + def _client_auth_enricher_login(self, raw_payload: EnricherPayloadType, audience: str) -> EnricherReturnType: auth_payload = prepare_client_secret_auth_payload( client_id=self._oidc_client_secret_client_config.client_id(), client_secret=self._oidc_client_secret_client_config.client_secret(), diff --git a/src/planet_auth/oidc/auth_clients/client_validator.py b/src/planet_auth/oidc/auth_clients/client_validator.py index 4790466..f046071 100644 --- a/src/planet_auth/oidc/auth_clients/client_validator.py +++ b/src/planet_auth/oidc/auth_clients/client_validator.py @@ -13,13 +13,13 @@ # limitations under the License. import pathlib -from typing import List, Optional, Tuple, Union -from requests.auth import AuthBase +from typing import List, Optional, Union from planet_auth import CredentialRequestAuthenticator from planet_auth.auth_client import AuthClientException from planet_auth.credential import Credential from planet_auth.oidc.auth_client import OidcAuthClientConfig, OidcAuthClient, FileBackedOidcCredential +from planet_auth.oidc.api_clients.api_client import EnricherPayloadType, EnricherReturnType from planet_auth.request_authenticator import ForbiddenRequestAuthenticator @@ -81,7 +81,7 @@ def __init__(self, client_config: OidcClientValidatorAuthClientConfig): # Not used at this time # self._client_validator_client_config = client_config - def _client_auth_enricher(self, raw_payload: dict, audience: str) -> Tuple[dict, Optional[AuthBase]]: + def _client_auth_enricher(self, raw_payload: EnricherPayloadType, audience: str) -> EnricherReturnType: return raw_payload, None def _oidc_flow_login( diff --git a/src/planet_auth/oidc/oauth_request_auth_enrichers.py b/src/planet_auth/oidc/oauth_request_auth_enrichers.py deleted file mode 100644 index 1659b9c..0000000 --- a/src/planet_auth/oidc/oauth_request_auth_enrichers.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright 2025 Planet Labs PBC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# While the whole exercise of talking to OAuth servers is to get access tokens -# that can be used for making requests to HTTP resource servers, those -# requests themselves need to be authenticated in various ways. -# This module provided implementations for various ways that is done. - diff --git a/src/planet_auth/util.py b/src/planet_auth/util.py index c43790f..2ceb178 100644 --- a/src/planet_auth/util.py +++ b/src/planet_auth/util.py @@ -19,7 +19,7 @@ auth_logger = planet_auth.logging.auth_logger.getAuthLogger() -def parse_content_type(content_type: str) -> Dict[str, Optional[str]]: +def parse_content_type(content_type: Optional[str]) -> Dict[str, Optional[str]]: result: Dict[str, Optional[str]] = { "content-type": None, } diff --git a/tests/test_planet_auth/unit/auth/auth_clients/oidc/test_oidc_auth_client_baseclass.py b/tests/test_planet_auth/unit/auth/auth_clients/oidc/test_oidc_auth_client_baseclass.py index a9d7c4e..39d731d 100644 --- a/tests/test_planet_auth/unit/auth/auth_clients/oidc/test_oidc_auth_client_baseclass.py +++ b/tests/test_planet_auth/unit/auth/auth_clients/oidc/test_oidc_auth_client_baseclass.py @@ -14,15 +14,15 @@ import pathlib import unittest -from typing import Optional, Tuple, Union +from typing import Union from unittest import mock from unittest.mock import MagicMock import pytest as pytest -from requests.auth import AuthBase from planet_auth.auth_client import AuthClientConfigException, AuthClientException from planet_auth.credential import Credential +from planet_auth.oidc.api_clients.api_client import EnricherPayloadType, EnricherReturnType from planet_auth.oidc.auth_client import ( OidcAuthClient, OidcAuthClientConfig, @@ -246,7 +246,7 @@ def __init__(self, client_config: OidcBaseTestHarnessClientConfig): super().__init__(client_config) self._test_client_config = client_config - def _client_auth_enricher(self, raw_payload: dict, audience: str) -> Tuple[dict, Optional[AuthBase]]: + def _client_auth_enricher(self, raw_payload: EnricherPayloadType, audience: str) -> EnricherReturnType: return raw_payload, None def _oidc_flow_login( diff --git a/tests/test_planet_auth/unit/auth/util.py b/tests/test_planet_auth/unit/auth/util.py index 15185f1..0d1df1a 100644 --- a/tests/test_planet_auth/unit/auth/util.py +++ b/tests/test_planet_auth/unit/auth/util.py @@ -19,14 +19,12 @@ import jwt.utils import pathlib import secrets -from typing import List, Optional, Tuple, Union +from typing import List, Optional, Union import jwt import time import uuid -from requests.auth import AuthBase - from planet_auth.storage_utils import ( FileBackedJsonObjectException, ObjectStorageProvider_KeyType, @@ -34,6 +32,7 @@ ) from planet_auth.credential import Credential from planet_auth.request_authenticator import CredentialRequestAuthenticator, ForbiddenRequestAuthenticator +from planet_auth.oidc.api_clients.api_client import EnricherPayloadType, EnricherReturnType from planet_auth.oidc.oidc_credential import FileBackedOidcCredential from planet_auth.oidc.api_clients.jwks_api_client import JwksApiClient from planet_auth.oidc.api_clients.token_api_client import TokenApiException @@ -344,7 +343,7 @@ def _construct_oidc_credential( credential = FileBackedOidcCredential(data=credential_data) return credential - def _client_auth_enricher(self, raw_payload: dict, audience: str) -> Tuple[dict, Optional[AuthBase]]: + def _client_auth_enricher(self, raw_payload: EnricherPayloadType, audience: str) -> EnricherReturnType: # return raw_payload, None # Abstract in the base class. Not under test here. assert 0 From 44fc42bf5c91a376cd97978937d320c43aed20e2 Mon Sep 17 00:00:00 2001 From: Carl Alexander Adams Date: Sun, 30 Mar 2025 09:05:49 -0700 Subject: [PATCH 3/4] pin pyflakes --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6dc0988..af602e5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,7 +53,7 @@ test = [ "freezegun", "mypy", "nox", - "pyflakes", + "pyflakes == 3.2.0", # 3.3.0 causes some grief with a new warning. "pylint", # "pylint[spelling]", ## TODO "pytest", From e6a4b0d8471154e6aad46a9c4e8bcc0aa04e0a35 Mon Sep 17 00:00:00 2001 From: Carl Alexander Adams Date: Sun, 30 Mar 2025 09:28:11 -0700 Subject: [PATCH 4/4] typing --- src/planet_auth/auth_client.py | 24 +++++++++---------- src/planet_auth/oidc/request_authenticator.py | 3 ++- .../planet_legacy/request_authenticator.py | 2 +- src/planet_auth/request_authenticator.py | 3 ++- 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/planet_auth/auth_client.py b/src/planet_auth/auth_client.py index dd6898f..15ce46b 100644 --- a/src/planet_auth/auth_client.py +++ b/src/planet_auth/auth_client.py @@ -122,7 +122,7 @@ def _get_typename_map(cls): return cls._typename_map @classmethod - def from_dict(cls, config_data: dict) -> AuthClientConfig: + def from_dict(cls, config_data: Dict) -> AuthClientConfig: """ Create a AuthClientConfig from a configuration dictionary. Returns: @@ -158,7 +158,7 @@ def from_file(file_path, storage_provider: Optional[ObjectStorageProvider] = Non @classmethod @abstractmethod - def meta(cls) -> dict: + def meta(cls) -> Dict: """ Return a dictionary of metadata. The meta dictionary provides a place to store information that is @@ -269,7 +269,7 @@ def login( implementations should raise an exception for all login errors. """ - def device_login_initiate(self, **kwargs) -> dict: + def device_login_initiate(self, **kwargs) -> Dict: """ Initiate the process to login a device with limited UI capabilities. The returned dictionary should contain information for the application @@ -286,7 +286,7 @@ def device_login_initiate(self, **kwargs) -> dict: """ raise AuthClientException(message="Device login is not supported for the current authentication mechanism") - def device_login_complete(self, initiated_login_data: dict) -> Credential: + def device_login_complete(self, initiated_login_data: Dict) -> Credential: """ Complete a login process that was initiated by a call to `device_login_initiate()`. @@ -319,7 +319,7 @@ def refresh(self, refresh_token: str, requested_scopes: List[str]) -> Credential """ raise AuthClientException(message="Refresh not implemented for the current authentication mechanism") - def validate_access_token_remote(self, access_token: str) -> dict: + def validate_access_token_remote(self, access_token: str) -> Dict: """ Validate an access token with the authorization server. Parameters: @@ -337,7 +337,7 @@ def validate_access_token_remote(self, access_token: str) -> dict: def validate_access_token_local( self, access_token: str, required_audience: str = None, scopes_anyof: list = None - ) -> dict: + ) -> Dict: """ Validate an access token locally. While the validation is local, the authorization server may still may contacted to obtain signing @@ -382,7 +382,7 @@ def validate_access_token_local( message="Access token validation is not implemented for the current authentication mechanism" ) - def validate_id_token_remote(self, id_token: str) -> dict: + def validate_id_token_remote(self, id_token: str) -> Dict: """ Validate an ID token with the authorization server. Parameters: @@ -394,7 +394,7 @@ def validate_id_token_remote(self, id_token: str) -> dict: message="ID token validation is not implemented for the current authentication mechanism" ) - def validate_id_token_local(self, id_token: str) -> dict: + def validate_id_token_local(self, id_token: str) -> Dict: """ Validate an ID token locally. The authorization server may still be called to obtain signing keys for validation. Signing keys will be @@ -408,7 +408,7 @@ def validate_id_token_local(self, id_token: str) -> dict: message="ID token validation is not implemented for the current authentication mechanism" ) - def validate_refresh_token_remote(self, refresh_token: str) -> dict: + def validate_refresh_token_remote(self, refresh_token: str) -> Dict: """ Validate a refresh token with the authorization server. Parameters: @@ -440,7 +440,7 @@ def revoke_refresh_token(self, refresh_token: str): message="Refresh token revocation is not implemented for the current authentication mechanism" ) - def userinfo_from_access_token(self, access_token: str) -> dict: + def userinfo_from_access_token(self, access_token: str) -> Dict: """ Look up user information from the auth server using the access token. Parameters: @@ -450,7 +450,7 @@ def userinfo_from_access_token(self, access_token: str) -> dict: message="User information lookup is not implemented for the current authentication mechanism" ) - def oidc_discovery(self) -> dict: + def oidc_discovery(self) -> Dict: """ Query the authorization server's OIDC discovery endpoint for server information. Returns: @@ -460,7 +460,7 @@ def oidc_discovery(self) -> dict: message="OIDC discovery is not implemented for the current authentication mechanism." ) - # def oauth_discovery(self) -> dict: + # def oauth_discovery(self) -> Dict: # """ # Query the authorization server's OAuth2 discovery endpoint for server information. # Returns: diff --git a/src/planet_auth/oidc/request_authenticator.py b/src/planet_auth/oidc/request_authenticator.py index bbc2994..cae4aec 100644 --- a/src/planet_auth/oidc/request_authenticator.py +++ b/src/planet_auth/oidc/request_authenticator.py @@ -14,6 +14,7 @@ import jwt import time +from typing import Dict import planet_auth.logging.auth_logger from planet_auth.credential import Credential @@ -125,7 +126,7 @@ def update_credential(self, new_credential: Credential): self._refresh_at = 0 # self._load() # Mimic __init__. Don't load, let that happen JIT. - def update_credential_data(self, new_credential_data: dict): + def update_credential_data(self, new_credential_data: Dict): # This is more different than update_credential() than it may # appear. Inherent in being passed a Credential in update_credential() # is that it may not yet be loaded from disk, and so deferring diff --git a/src/planet_auth/planet_legacy/request_authenticator.py b/src/planet_auth/planet_legacy/request_authenticator.py index 79c0dc2..ef95e2c 100644 --- a/src/planet_auth/planet_legacy/request_authenticator.py +++ b/src/planet_auth/planet_legacy/request_authenticator.py @@ -40,7 +40,7 @@ def update_credential(self, new_credential): super().update_credential(new_credential) self._api_key_file = new_credential - # def update_credential_data(self, new_credential_data: dict): + # def update_credential_data(self, new_credential_data: Dict): # super().update_credential_data(new_credential_data=new_credential_data) # # The super class is not changing the instance, so we don't need to update our reference # # self._api_key_file = self._credential diff --git a/src/planet_auth/request_authenticator.py b/src/planet_auth/request_authenticator.py index 8349865..0f10b80 100644 --- a/src/planet_auth/request_authenticator.py +++ b/src/planet_auth/request_authenticator.py @@ -13,6 +13,7 @@ # limitations under the License. from abc import abstractmethod, ABC +from typing import Dict import httpx import requests.auth @@ -107,7 +108,7 @@ def update_credential(self, new_credential: Credential): # requests. self._token_body = None - def update_credential_data(self, new_credential_data: dict): + def update_credential_data(self, new_credential_data: Dict): """ Provide raw data that should be used to update the Credential object used to authenticate requests. This information will