Repository: auth0/auth0-python
Pull request #177 (2019-01-03 20:31:21)

Add grants, custom domains, rules_configs to API
Following up on https://github.com/auth0/auth0-python/pull/160, I wanted to help get the new APIs listed in the title available in the client.  I've addressed the comments I see on that PR and made a few other changes I saw.  

sagnew-dg: Thanks for the review; latest commit should address the comments. 😀 

Patch:
diff --git a/.gitignore b/.gitignore
index 4714cf4..40ae709 100644
--- a/.gitignore
+++ b/.gitignore
@@ -39,6 +39,7 @@ nosetests.xml
 coverage.xml
 *,cover
 .hypothesis/
+.pytest_cache
 
 # Translations
 *.mo
diff --git a/auth0/v3/management/__init__.py b/auth0/v3/management/__init__.py
index 750b728..3c8c032 100644
--- a/auth0/v3/management/__init__.py
+++ b/auth0/v3/management/__init__.py
@@ -3,14 +3,17 @@ from .blacklists import Blacklists
 from .clients import Clients
 from .client_grants import ClientGrants
 from .connections import Connections
+from .custom_domains import CustomDomains
 from .device_credentials import DeviceCredentials
 from .emails import Emails
 from .email_templates import EmailTemplates
+from .grants import Grants
 from .guardian import Guardian
 from .jobs import Jobs
 from .logs import Logs
 from .resource_servers import ResourceServers
 from .rules import Rules
+from .rules_configs import RulesConfigs
 from .stats import Stats
 from .tenants import Tenants
 from .tickets import Tickets
diff --git a/auth0/v3/management/auth0.py b/auth0/v3/management/auth0.py
index 10d534d..b014d39 100644
--- a/auth0/v3/management/auth0.py
+++ b/auth0/v3/management/auth0.py
@@ -1,3 +1,5 @@
+from .grants import Grants
+from .custom_domains import CustomDomains
 from .blacklists import Blacklists
 from .clients import Clients
 from .client_grants import ClientGrants
@@ -10,6 +12,7 @@ from .jobs import Jobs
 from .logs import Logs
 from .resource_servers import ResourceServers
 from .rules import Rules
+from .rules_configs import RulesConfigs
 from .stats import Stats
 from .tenants import Tenants
 from .tickets import Tickets
@@ -31,15 +34,18 @@ class Auth0(object):
         self.blacklists = Blacklists(domain, token)
         self.clients = Clients(domain, token)
         self.client_grants = ClientGrants(domain, token)
+        self.custom_domains = CustomDomains(domain, token)
         self.connections = Connections(domain, token)
         self.device_credentials = DeviceCredentials(domain, token)
         self.emails = Emails(domain, token)
         self.email_templates = EmailTemplates(domain, token)
+        self.grants = Grants(domain.token)
         self.guardian = Guardian(domain, token)
         self.jobs = Jobs(domain, token)
         self.logs = Logs(domain, token)
         self.resource_servers = ResourceServers(domain, token)
         self.rules = Rules(domain, token)
+        self.rules_configs = RulesConfigs(domain, token)
         self.stats = Stats(domain, token)
         self.tenants = Tenants(domain, token)
         self.tickets = Tickets(domain, token)
diff --git a/auth0/v3/management/clients.py b/auth0/v3/management/clients.py
index 60db7a9..a69dfa6 100644
--- a/auth0/v3/management/clients.py
+++ b/auth0/v3/management/clients.py
@@ -111,7 +111,7 @@ class Clients(object):
         attributes can only be updated with the update:client_keys scope.
 
         Args:
-           id (str): Client id of the application.
+           id (str): Client ID of the application.
 
            body (dict): Attributes to modify.
               See: https://auth0.com/docs/api/management/v2#!/Clients/patch_clients_by_id
@@ -119,3 +119,17 @@ class Clients(object):
 
         return self.client.patch(self._url(id), data=body)
 
+    def rotate_secret(self, id):
+        """Rotate a client secret. The generated secret is NOT base64 encoded.
+
+        Args:
+           id (str): Client ID of the application.
+
+           body (dict): Attributes to modify.
+              See: https://auth0.com/docs/api/management/v2#!/Clients/post_rotate_secret
+        """
+
+        params = {'id': id }
+
+        url = self._url('%s/rotate-secret' % id)
+        return self.client.get(url, params=params)
\ No newline at end of file
diff --git a/auth0/v3/management/custom_domains.py b/auth0/v3/management/custom_domains.py
new file mode 100644
index 0000000..98e3d45
--- /dev/null
+++ b/auth0/v3/management/custom_domains.py
@@ -0,0 +1,75 @@
+from .rest import RestClient
+
+
+class CustomDomains(object):
+
+    """Auth0 custom domains endpoints
+
+    Args:
+        domain (str): Your Auth0 domain, e.g: 'username.auth0.com'
+
+        token (str): Management API v2 Token
+
+        telemetry (bool, optional): Enable or disable Telemetry
+            (defaults to True)
+    """
+
+    def __init__(self, domain, token, telemetry=True):
+        self.domain = domain
+        self.client = RestClient(jwt=token, telemetry=telemetry)
+
+    def _url(self, id=None):
+        url = 'https://%s/api/v2/custom-domains' % self.domain
+        if id is not None:
+            return url + '/' + id
+        return url
+
+    def all(self):
+        """Retrieves all custom domains.
+
+        See: https://auth0.com/docs/api/management/v2#!/Custom_Domains/get_custom_domains
+        """
+        return self.client.get(self._url())
+
+    def get(self, id):
+        """Retrieves custom domain.
+
+        See: https://auth0.com/docs/api/management/v2#!/Custom_Domains/get_custom_domains_by_id
+        """
+        url = self._url('%s' % (id))
+        return self.client.get(url)
+
+    def delete(self, id):
+        """Deletes a grant.
+
+        Args:
+           id (str): The id of the custom domain to delete
+
+
+        See: https://auth0.com/docs/api/management/v2#!/Custom_Domains/delete_custom_domains_by_id
+        """
+        url = self._url('%s' % (id))
+        return self.client.delete(url)
+
+    def create_new(self, body):
+        """Configure a new custom domain
+
+        Args:
+           body (str): The domain, tye and verification method in json
+
+
+        See: https://auth0.com/docs/api/management/v2#!/Custom_Domains/post_custom_domains
+        """
+        return self.client.post(self._url(), data=body)
+
+    def verify(self, id):
+        """Verify a custom domain
+
+        Args:
+           id (str): The id of the custom domain to delete
+
+
+        See: https://auth0.com/docs/api/management/v2#!/Custom_Domains/post_verify
+        """
+        url = self._url('%s/verify' % (id))
+        return self.client.post(url)
\ No newline at end of file
diff --git a/auth0/v3/management/grants.py b/auth0/v3/management/grants.py
new file mode 100644
index 0000000..da3b332
--- /dev/null
+++ b/auth0/v3/management/grants.py
@@ -0,0 +1,63 @@
+from .rest import RestClient
+
+
+class Grants(object):
+
+    """Auth0 grants endpoints
+
+    Args:
+        domain (str): Your Auth0 domain, e.g: 'username.auth0.com'
+
+        token (str): Management API v2 Token
+
+        telemetry (bool, optional): Enable or disable Telemetry
+            (defaults to True)
+    """
+
+    def __init__(self, domain, token, telemetry=True):
+        self.domain = domain
+        self.client = RestClient(jwt=token, telemetry=telemetry)
+
+    def _url(self, id=None):
+        url = 'https://%s/api/v2/grants' % self.domain
+        if id is not None:
+            return url + '/' + id
+        return url
+
+    def all(self, page=None, per_page=None, include_totals=False, extra_params=None):
+        """Retrieves all grants.
+
+        Args:
+            page (int, optional): The result's page number (zero based).
+
+            per_page (int, optional): The amount of entries per page.
+
+            include_totals (bool, optional): True if the query summary is
+                to be included in the result, False otherwise.
+
+           extra_params (dictionary, optional): The extra parameters to add to
+             the request. The page, per_page, and include_totals values
+             specified as parameters take precedence over the ones defined here.
+            
+        See: https://auth0.com/docs/api/management/v2#!/Grants/get_grants
+        """
+        params = extra_params or {}
+        params.update({
+            'page': page,
+            'per_page': per_page,
+            'include_totals': str(include_totals).lower()
+        })
+
+        return self.client.get(self._url(), params=params)
+
+    def delete(self, id):
+        """Deletes a grant.
+
+        Args:
+           id (str): The id of the grant to delete
+
+
+        See: https://auth0.com/docs/api/management/v2#!/Grants/delete_grants_by_id
+        """
+        url = self._url('%s' % (id))
+        return self.client.delete(url)
\ No newline at end of file
diff --git a/auth0/v3/management/jobs.py b/auth0/v3/management/jobs.py
index c06f3db..9d8864b 100644
--- a/auth0/v3/management/jobs.py
+++ b/auth0/v3/management/jobs.py
@@ -45,6 +45,17 @@ class Jobs(object):
         url = self._url('{}/errors'.format(id))
         return self.client.get(url)
 
+    def get_results(self, job_id):
+        """Get results of a job
+
+        Args:
+            job_id (str): The ID of the job.
+
+        See: https://auth0.com/docs/api/management/v2#!/Jobs/get_results
+        """
+        url = self._url('%s/results' % job_id)
+        return self.client.get(url)
+
     def export_users(self, body):
         """Export all users to a file using a long running job.
 
diff --git a/auth0/v3/management/rules_configs.py b/auth0/v3/management/rules_configs.py
new file mode 100644
index 0000000..cfde7b0
--- /dev/null
+++ b/auth0/v3/management/rules_configs.py
@@ -0,0 +1,60 @@
+from .rest import RestClient
+
+
+class RulesConfigs(object):
+
+    """RulesConfig endpoint implementation.
+
+    Args:
+        domain (str): Your Auth0 domain, e.g: 'username.auth0.com'
+
+        token (str): Management API v2 Token
+
+        telemetry (bool, optional): Enable or disable Telemetry
+            (defaults to True)
+    """
+
+    def __init__(self, domain, token, telemetry=True):
+        self.domain = domain
+        self.client = RestClient(jwt=token, telemetry=telemetry)
+
+    def _url(self, id=None):
+        url = 'https://%s/api/v2/rules-configs' % self.domain
+        if id is not None:
+            return url + '/' + id
+        return url
+
+    def all(self):
+        """Lists the config variable keys for rules.
+
+        See: https://auth0.com/docs/api/management/v2#!/Rules_Configs/get_rules_configs
+        """
+        return self.client.get(self._url())
+
+    def unset(self, key):
+        """Removes the rules config for a given key.
+
+        Args:
+            key (str): rules config key to remove
+
+        See: https://auth0.com/docs/api/management/v2#!/Rules_Configs/delete_rules_configs_by_key
+        """
+        params = {
+            'key': key
+        }
+        return self.client.delete(self._url(), params=params)
+
+    def set(self, key, value):
+        """Sets the rules config for a given key.
+
+        Args:
+            key (str): rules config key to set
+
+            value (str): value to set for the rules config key
+
+        See: https://auth0.com/docs/api/management/v2#!/Rules_Configs/put_rules_configs_by_key
+        """
+        url = self._url('{}'.format(key))
+        body = {'value': value}
+        return self.client.put(url, data=body)
+


Test patch:
diff --git a/auth0/v3/test/management/test_clients.py b/auth0/v3/test/management/test_clients.py
index 1c952b0..ae9d5e5 100644
--- a/auth0/v3/test/management/test_clients.py
+++ b/auth0/v3/test/management/test_clients.py
@@ -111,3 +111,17 @@ class TestClients(unittest.TestCase):
 
         self.assertEqual('https://domain/api/v2/clients/this-id', args[0])
         self.assertEqual(kwargs['data'], {'a': 'b', 'c': 'd'})
+
+    @mock.patch('auth0.v3.management.clients.RestClient')
+    def test_rotate_secret(self, mock_rc):
+        mock_instance = mock_rc.return_value
+
+        c = Clients(domain='domain', token='jwttoken')
+        c.rotate_secret('this-id')
+
+        mock_instance.get.assert_called_with(
+            'https://domain/api/v2/clients/this-id/rotate-secret', params={'id': 'this-id'}
+        )
+
+
+
diff --git a/auth0/v3/test/management/test_custom_domains.py b/auth0/v3/test/management/test_custom_domains.py
new file mode 100644
index 0000000..d80dc82
--- /dev/null
+++ b/auth0/v3/test/management/test_custom_domains.py
@@ -0,0 +1,52 @@
+import unittest
+import mock
+from ...management.custom_domains import CustomDomains
+
+
+class TestCustomDomains(unittest.TestCase):
+
+    @mock.patch('auth0.v3.management.custom_domains.RestClient')
+    def test_get_all(self, mock_rc):
+        mock_instance = mock_rc.return_value
+
+        g = CustomDomains(domain='domain', token='jwttoken')
+        g.all()
+
+        mock_instance.get.assert_called_with(
+            'https://domain/api/v2/custom-domains'
+        )
+
+    @mock.patch('auth0.v3.management.custom_domains.RestClient')
+    def test_create_new(self, mock_rc):
+        mock_instance = mock_rc.return_value
+
+        g = CustomDomains(domain='domain', token='jwttoken')
+        g.create_new(body={'a': 'b', 'c': 'd','e': 'f'})
+
+        args, kwargs = mock_instance.post.call_args
+
+        self.assertEqual('https://domain/api/v2/custom-domains',args[0])
+        self.assertEqual(kwargs['data'], {'a': 'b', 'c': 'd','e': 'f'})
+
+    @mock.patch('auth0.v3.management.custom_domains.RestClient')
+    def test_get_domain_by_id(self, mock_rc):
+        mock_instance = mock_rc.return_value
+
+        g = CustomDomains(domain='domain', token='jwttoken')
+        g.get('an-id')
+
+        mock_instance.get.assert_called_with('https://domain/api/v2/custom-domains/an-id')
+
+
+    @mock.patch('auth0.v3.management.custom_domains.RestClient')
+    def test_verify(self, mock_rc):
+        mock_instance = mock_rc.return_value
+
+        g = CustomDomains(domain='domain', token='jwttoken')
+        g.verify('an-id')
+
+        args, kwargs = mock_instance.post.call_args
+
+        self.assertEqual('https://domain/api/v2/custom-domains/an-id/verify', args[0])
+
+
diff --git a/auth0/v3/test/management/test_grants.py b/auth0/v3/test/management/test_grants.py
new file mode 100644
index 0000000..35d0636
--- /dev/null
+++ b/auth0/v3/test/management/test_grants.py
@@ -0,0 +1,31 @@
+import unittest
+import mock
+from ...management.grants import Grants
+
+
+class TestGrants(unittest.TestCase):
+
+    @mock.patch('auth0.v3.management.grants.RestClient')
+    def test_get_all(self, mock_rc):
+        mock_instance = mock_rc.return_value
+
+        g = Grants(domain='domain', token='jwttoken')
+        g.all(extra_params={'user_id':'an-id', 'client_id': 'an-id', 'audience':'test'})
+
+        args, kwargs = mock_instance.get.call_args
+
+        mock_instance.get.assert_called_with(
+            'https://domain/api/v2/grants', params={'user_id': 'an-id', 'client_id': 'an-id', 'audience': 'test', 'page': None, 'per_page': None, 'include_totals': 'false'}
+        )
+
+
+    @mock.patch('auth0.v3.management.grants.RestClient')
+    def test_delete(self, mock_rc):
+        mock_instance = mock_rc.return_value
+
+        c = Grants(domain='domain', token='jwttoken')
+        c.delete('an-id')
+
+        mock_instance.delete.assert_called_with(
+            'https://domain/api/v2/grants/an-id'
+        )
diff --git a/auth0/v3/test/management/test_jobs.py b/auth0/v3/test/management/test_jobs.py
index 7287011..e2685c4 100644
--- a/auth0/v3/test/management/test_jobs.py
+++ b/auth0/v3/test/management/test_jobs.py
@@ -27,6 +27,17 @@ class TestJobs(unittest.TestCase):
             'https://domain/api/v2/jobs/an-id/errors',
         )
 
+    @mock.patch('auth0.v3.management.jobs.RestClient')
+    def get_job_results(self, mock_rc):
+        mock_instance = mock_rc.return_value
+
+        j = Jobs(domain='domain', token='jwttoken')
+        j.get('an-id')
+
+        mock_instance.get.assert_called_with(
+            'https://domain/api/v2/jobs/an-id/results',
+        )
+
     @mock.patch('auth0.v3.management.jobs.RestClient')
     def test_export_users(self, mock_rc):
         mock_instance = mock_rc.return_value
diff --git a/auth0/v3/test/management/test_rules_configs.py b/auth0/v3/test/management/test_rules_configs.py
new file mode 100644
index 0000000..68ee018
--- /dev/null
+++ b/auth0/v3/test/management/test_rules_configs.py
@@ -0,0 +1,43 @@
+import unittest
+import mock
+from ...management.rules_configs import RulesConfigs
+
+
+class TestRules(unittest.TestCase):
+
+    @mock.patch('auth0.v3.management.rules_configs.RestClient')
+    def test_all(self, mock_rc):
+        mock_instance = mock_rc.return_value
+
+        c = RulesConfigs(domain='domain', token='jwttoken')
+
+        c.all()
+
+        args, kwargs = mock_instance.get.call_args
+
+        self.assertEqual('https://domain/api/v2/rules-configs', args[0])
+
+    @mock.patch('auth0.v3.management.rules_configs.RestClient')
+    def test_unset(self, mock_rc):
+        mock_instance = mock_rc.return_value
+
+        c = RulesConfigs(domain='domain', token='jwttoken')
+        c.unset('an-id')
+
+        mock_instance.delete.assert_called_with(
+            'https://domain/api/v2/rules-configs', params={'key': 'an-id'}
+        )
+
+    @mock.patch('auth0.v3.management.rules_configs.RestClient')
+    def test_set(self, mock_rc):
+        mock_instance = mock_rc.return_value
+
+        g = RulesConfigs(domain='domain', token='jwttoken')
+        g.set('key', 'MY_RULES_CONFIG_VALUES')
+
+        args, kwargs = mock_instance.put.call_args
+        self.assertEqual('https://domain/api/v2/rules-configs/key', args[0])
+
+
+
+