Repository: auth0/auth0-python
Pull request #459 (2023-01-17 15:20:58)

[SDK-3866] Add support for managing client credentials
Add get all, get by id, create and delete for client credentials.

### Testing

- [x] This change adds unit test coverage
- [x] This change has been tested on the latest version of the platform/language or why not

### Checklist

- [x] I have read the [Auth0 general contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md)
- [x] I have read the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md)
- [x] All existing and new tests complete without errors



Patch:
diff --git a/README.md b/README.md
index 894b7a3..9d7cc3d 100644
--- a/README.md
+++ b/README.md
@@ -113,6 +113,7 @@ For more code samples on how to integrate the auth0-python SDK in your Python ap
 - AttackProtection() (`Auth0().attack_protection`)
 - Blacklists() ( `Auth0().blacklists` )
 - Branding() ( `Auth0().branding` )
+- ClientCredentials() ( `Auth0().client_credentials` )
 - ClientGrants() ( `Auth0().client_grants` )
 - Clients() ( `Auth0().clients` )
 - Connections() ( `Auth0().connections` )
diff --git a/auth0/v3/management/__init__.py b/auth0/v3/management/__init__.py
index 5ebfe0c..ab87b33 100644
--- a/auth0/v3/management/__init__.py
+++ b/auth0/v3/management/__init__.py
@@ -3,6 +3,7 @@ from .actions import Actions
 from .attack_protection import AttackProtection
 from .blacklists import Blacklists
 from .branding import Branding
+from .client_credentials import ClientCredentials
 from .client_grants import ClientGrants
 from .clients import Clients
 from .connections import Connections
@@ -39,6 +40,7 @@ __all__ = (
     "AttackProtection",
     "Blacklists",
     "Branding",
+    "ClientCredentials",
     "ClientGrants",
     "Clients",
     "Connections",
diff --git a/auth0/v3/management/auth0.py b/auth0/v3/management/auth0.py
index 84c352a..8521100 100644
--- a/auth0/v3/management/auth0.py
+++ b/auth0/v3/management/auth0.py
@@ -3,6 +3,7 @@ from .actions import Actions
 from .attack_protection import AttackProtection
 from .blacklists import Blacklists
 from .branding import Branding
+from .client_credentials import ClientCredentials
 from .client_grants import ClientGrants
 from .clients import Clients
 from .connections import Connections
@@ -34,6 +35,7 @@ modules = {
     "attack_protection": AttackProtection,
     "blacklists": Blacklists,
     "branding": Branding,
+    "client_credentials": ClientCredentials,
     "client_grants": ClientGrants,
     "clients": Clients,
     "connections": Connections,
diff --git a/auth0/v3/management/client_credentials.py b/auth0/v3/management/client_credentials.py
new file mode 100644
index 0000000..fa40a64
--- /dev/null
+++ b/auth0/v3/management/client_credentials.py
@@ -0,0 +1,90 @@
+from ..rest import RestClient
+
+
+class ClientCredentials(object):
+    """Auth0 client credentials endpoints.
+
+    Args:
+        domain (str): Your Auth0 domain, for example: 'my-domain.us.auth0.com'
+
+        token (str): Management API v2 Token
+
+        telemetry (bool, optional): Enable or disable telemetry
+            (defaults to True)
+
+        timeout (float or tuple, optional): Change the requests
+            connect and read timeout. Pass a tuple to specify
+            both values separately or a float to set both to it.
+            (defaults to 5.0 for both)
+
+        rest_options (RestClientOptions): Pass an instance of
+            RestClientOptions to configure additional RestClient
+            options, such as rate-limit retries.
+            (defaults to None)
+    """
+
+    def __init__(
+        self,
+        domain,
+        token,
+        telemetry=True,
+        timeout=5.0,
+        protocol="https",
+        rest_options=None,
+    ):
+        self.domain = domain
+        self.protocol = protocol
+        self.client = RestClient(
+            jwt=token, telemetry=telemetry, timeout=timeout, options=rest_options
+        )
+
+    def _url(self, client_id, id=None):
+        url = "{}://{}/api/v2/clients/{}/credentials".format(
+            self.protocol, self.domain, client_id
+        )
+        if id is not None:
+            return "{}/{}".format(url, id)
+        return url
+
+    def all(self, client_id):
+        """Get a list of credentials associated with a client.
+
+        Args:
+            client_id (string): The id of a client that owns the credentials.
+
+        See: https://auth0.com/docs/api/management/v2#!/Client_Credentials/get_client_credentials
+        """
+        return self.client.get(self._url(client_id))
+
+    def get(self, client_id, id):
+        """Retrieve a specified client credential.
+
+        Args:
+            client_id (string): The id of a client that owns the credential.
+
+            id (string): The id of the credential.
+
+        See: https://auth0.com/docs/api/management/v2#!/Client_Credentials/get_client_credentials_by_id
+        """
+        return self.client.get(self._url(client_id, id))
+
+    def create(self, client_id, body):
+        """Create a credential on a client.
+
+        Args:
+            client_id (string): The id of a client to create the credential for.
+
+        See: https://auth0.com/docs/api/management/v2#!/Client_Credentials/post_client_credentials
+        """
+        return self.client.post(self._url(client_id), data=body)
+
+    def delete(self, client_id, id):
+        """Delete a client's credential.
+
+        Args:
+           id (str): The id of credential to delete.
+
+        See: https://auth0.com/docs/api/management/v2#!/Client_Credentials/delete_client_credentials_by_id
+        """
+
+        return self.client.delete(self._url(client_id, id))


Test patch:
diff --git a/auth0/v3/test/management/test_auth0.py b/auth0/v3/test/management/test_auth0.py
index 15ce786..b9251ec 100644
--- a/auth0/v3/test/management/test_auth0.py
+++ b/auth0/v3/test/management/test_auth0.py
@@ -4,6 +4,7 @@ from ...management.actions import Actions
 from ...management.attack_protection import AttackProtection
 from ...management.auth0 import Auth0
 from ...management.blacklists import Blacklists
+from ...management.client_credentials import ClientCredentials
 from ...management.client_grants import ClientGrants
 from ...management.clients import Clients
 from ...management.connections import Connections
@@ -47,6 +48,9 @@ class TestAuth0(unittest.TestCase):
     def test_blacklists(self):
         self.assertIsInstance(self.a0.blacklists, Blacklists)
 
+    def test_client_credentials(self):
+        self.assertIsInstance(self.a0.client_credentials, ClientCredentials)
+
     def test_client_grants(self):
         self.assertIsInstance(self.a0.client_grants, ClientGrants)
 
diff --git a/auth0/v3/test/management/test_client_credentials.py b/auth0/v3/test/management/test_client_credentials.py
new file mode 100644
index 0000000..dbeb09f
--- /dev/null
+++ b/auth0/v3/test/management/test_client_credentials.py
@@ -0,0 +1,51 @@
+import unittest
+
+import mock
+
+from ...management.client_credentials import ClientCredentials
+
+
+class TestClientCredentials(unittest.TestCase):
+    def test_init_with_optionals(self):
+        t = ClientCredentials(
+            domain="domain", token="jwttoken", telemetry=False, timeout=(10, 2)
+        )
+        self.assertEqual(t.client.options.timeout, (10, 2))
+        telemetry_header = t.client.base_headers.get("Auth0-Client", None)
+        self.assertEqual(telemetry_header, None)
+
+    @mock.patch("auth0.v3.management.client_credentials.RestClient")
+    def test_all(self, mock_rc):
+        mock_instance = mock_rc.return_value
+        c = ClientCredentials(domain="domain", token="jwttoken")
+        c.all("cid")
+        mock_instance.get.assert_called_with(
+            "https://domain/api/v2/clients/cid/credentials"
+        )
+
+    @mock.patch("auth0.v3.management.client_credentials.RestClient")
+    def test_get(self, mock_rc):
+        mock_instance = mock_rc.return_value
+        c = ClientCredentials(domain="domain", token="jwttoken")
+        c.get("cid", "this-id")
+        mock_instance.get.assert_called_with(
+            "https://domain/api/v2/clients/cid/credentials/this-id"
+        )
+
+    @mock.patch("auth0.v3.management.client_credentials.RestClient")
+    def test_create(self, mock_rc):
+        mock_instance = mock_rc.return_value
+        c = ClientCredentials(domain="domain", token="jwttoken")
+        c.create("cid", {"a": "b", "c": "d"})
+        mock_instance.post.assert_called_with(
+            "https://domain/api/v2/clients/cid/credentials", data={"a": "b", "c": "d"}
+        )
+
+    @mock.patch("auth0.v3.management.client_credentials.RestClient")
+    def test_delete(self, mock_rc):
+        mock_instance = mock_rc.return_value
+        c = ClientCredentials(domain="domain", token="jwttoken")
+        c.delete("cid", "this-id")
+        mock_instance.delete.assert_called_with(
+            "https://domain/api/v2/clients/cid/credentials/this-id"
+        )