diff --git a/docs/channels.rst b/docs/channels.rst index 8902639f..bfd7b4ec 100644 --- a/docs/channels.rst +++ b/docs/channels.rst @@ -59,3 +59,19 @@ Example: channel = create_camunda_cloud_channel("client_id", "client_secret", "cluster_id") + + +Credentials +----------- + +.. autoclass:: pyzeebe.AuthMetadataPlugin + :members: + :undoc-members: + +.. autoclass:: pyzeebe.CredentialsABC + :members: + :undoc-members: + +.. autoclass:: pyzeebe.CamundaIdentityCredentials + :members: + :undoc-members: diff --git a/pyzeebe/__init__.py b/pyzeebe/__init__.py index b9273e2a..e8dffcc4 100644 --- a/pyzeebe/__init__.py +++ b/pyzeebe/__init__.py @@ -4,6 +4,7 @@ from pyzeebe.channel import * from pyzeebe.client.client import ZeebeClient from pyzeebe.client.sync_client import SyncZeebeClient # type: ignore +from pyzeebe.credentials.base import CredentialsABC from pyzeebe.credentials.camunda_identity import CamundaIdentityCredentials from pyzeebe.credentials.plugins import AuthMetadataPlugin from pyzeebe.job.job import Job diff --git a/pyzeebe/credentials/__init__.py b/pyzeebe/credentials/__init__.py index f3b8b7c4..aa79dedb 100644 --- a/pyzeebe/credentials/__init__.py +++ b/pyzeebe/credentials/__init__.py @@ -1,2 +1,3 @@ +from .base import CredentialsABC from .camunda_identity import CamundaIdentityCredentials from .plugins import AuthMetadataPlugin diff --git a/pyzeebe/credentials/base.py b/pyzeebe/credentials/base.py index d03ca72e..d75d4c1b 100644 --- a/pyzeebe/credentials/base.py +++ b/pyzeebe/credentials/base.py @@ -3,7 +3,16 @@ from pyzeebe.credentials.typing import AuthMetadata, CallContext -class Credentials(abc.ABC): +class CredentialsABC(abc.ABC): + """TODO.""" + @abc.abstractmethod def get_auth_metadata(self, context: CallContext) -> AuthMetadata: + """ + Args: + context (grpc.AuthMetadataContext): Provides information to call credentials metadata plugins. + + Returns: + Tuple[Tuple[str, Union[str, bytes]], ...]: The `metadata` used to construct the grpc.CallCredentials. + """ raise NotImplementedError diff --git a/pyzeebe/credentials/camunda_identity.py b/pyzeebe/credentials/camunda_identity.py index 45c74e94..3e3614bd 100644 --- a/pyzeebe/credentials/camunda_identity.py +++ b/pyzeebe/credentials/camunda_identity.py @@ -4,12 +4,22 @@ import requests -from pyzeebe.credentials.base import Credentials +from pyzeebe.credentials.base import CredentialsABC from pyzeebe.credentials.typing import AuthMetadata, CallContext from pyzeebe.errors import InvalidOAuthCredentialsError -class CamundaIdentityCredentials(Credentials): +class CamundaIdentityCredentials(CredentialsABC): + """Credentials client for Camunda Platform. + + Args: + oauth_url (str): The Keycloak auth endpoint url. + client_id (str): The client id provided by Camunda Platform + client_secret (str): The client secret provided by Camunda Platform + audience (str): + refresh_threshold_seconds (int): + """ + def __init__( self, *, @@ -30,14 +40,14 @@ def __init__( self._token: Optional[Dict[str, Any]] = None self._expires_in: Optional[datetime.datetime] = None - def expired(self) -> bool: + def _expired(self) -> bool: return ( self._token is None or self._expires_in is None or (self._expires_in - self._refresh_threshold) < datetime.datetime.now(datetime.timezone.utc) ) - def refresh(self) -> None: + def _refresh(self) -> None: try: response = requests.post( self.oauth_url, @@ -60,7 +70,17 @@ def refresh(self) -> None: ) from http_error def get_auth_metadata(self, context: CallContext) -> AuthMetadata: + """ + Args: + context (grpc.AuthMetadataContext): Provides information to call credentials metadata plugins. + + Returns: + Tuple[Tuple[str, Union[str, bytes]], ...]: The `metadata` used to construct the grpc.CallCredentials. + + Raises: + InvalidOAuthCredentialsError: One of the provided camunda credentials is not correct + """ with self._lock: - if self.expired() is True: - self.refresh() + if self._expired() is True: + self._refresh() return (("authorization", "Bearer {}".format(self._token)),) diff --git a/pyzeebe/credentials/plugins.py b/pyzeebe/credentials/plugins.py index a4688e9c..9aa65641 100644 --- a/pyzeebe/credentials/plugins.py +++ b/pyzeebe/credentials/plugins.py @@ -2,16 +2,31 @@ import grpc -from pyzeebe.credentials.base import Credentials +from pyzeebe.credentials.base import CredentialsABC from pyzeebe.credentials.typing import AuthMetadata class AuthMetadataPlugin(grpc.AuthMetadataPlugin): - def __init__(self, *, credentials: Credentials) -> None: - super().__init__() + """TODO. + + Args: + credentials (CredentialsABC): TODO + """ + + def __init__(self, *, credentials: CredentialsABC) -> None: self._credentials = credentials def __call__(self, context: grpc.AuthMetadataContext, callback: grpc.AuthMetadataPluginCallback) -> None: + """Implements authentication by passing metadata to a callback. + + This method will be invoked asynchronously in a separate thread. + + Args: + context: An AuthMetadataContext providing information on the RPC that + the plugin is being called to authenticate. + callback: An AuthMetadataPluginCallback to be invoked either + synchronously or asynchronously. + """ try: metadata = self._credentials.get_auth_metadata(context) except Exception as e: diff --git a/pyzeebe/credentials/typing.py b/pyzeebe/credentials/typing.py index a9eb8ecf..9e4dd652 100644 --- a/pyzeebe/credentials/typing.py +++ b/pyzeebe/credentials/typing.py @@ -1,4 +1,4 @@ -from typing import Protocol, Tuple, Union, final +from typing import Protocol, Tuple, Union AuthMetadata = Tuple[Tuple[str, Union[str, bytes]], ...] diff --git a/tests/unit/credentials/auth_metadata_plugin_test.py b/tests/unit/credentials/auth_metadata_plugin_test.py index 4a68cce8..8bcb18f7 100644 --- a/tests/unit/credentials/auth_metadata_plugin_test.py +++ b/tests/unit/credentials/auth_metadata_plugin_test.py @@ -4,7 +4,7 @@ import pytest from pyzeebe import AuthMetadataPlugin -from pyzeebe.credentials.base import Credentials +from pyzeebe.credentials.base import CredentialsABC from pyzeebe.credentials.typing import CallContext from pyzeebe.errors.credentials_errors import InvalidOAuthCredentialsError @@ -12,7 +12,7 @@ class TestAuthMetadataPlugin: @pytest.fixture() def credentials_mock(self) -> Mock: - return Mock(spec_set=Credentials) + return Mock(spec_set=CredentialsABC) @pytest.fixture() def callback_mock(self) -> Mock: