Repository: TACC/agavepy

Issue:
Implement an oauth client delete method
Write an document a method to delete oauth clients.

Discussion:

Patch:
diff --git a/agavepy/agave.py b/agavepy/agave.py
index a6cfa1e..6245c49 100644
--- a/agavepy/agave.py
+++ b/agavepy/agave.py
@@ -20,7 +20,7 @@ import dateutil.parser
 import requests
 
 from agavepy.tenants import tenant_list
-from agavepy.clients import clients_create, clients_list
+from agavepy.clients import clients_create, clients_delete, clients_list
 from agavepy.tokens import token_create, refresh_token
 from agavepy.utils import load_config, save_config
 from agavepy.files import (files_copy, files_delete, files_download, 
@@ -636,7 +636,7 @@ class Agave(object):
 
 
     def clients_create(self, client_name, description):
-        """ Create an Agave Oauth client
+        """ Create an Oauth client
 
         Save the api key and secret upon a successfull reuest to Agave.
 
@@ -660,8 +660,31 @@ class Agave(object):
         self.client_name = client_name
 
 
+    def clients_delete(self, client_name=None):
+        """ Delete an Oauth client
+
+        If no client_name is passed then we will try to delete the oauth client
+        stored in the current session.
+        """
+        # Set username.
+        if self.username == "" or self.username is None:
+            self.username = input("API username: ")
+
+        # If client_name is not set, then delete the current client, if it 
+        # exists.
+        if client_name is None:
+            client_name = self.client_name
+
+        # Delete client.
+        clients_delete(self.api_server, self.username, client_name)
+
+        # If we deleted the current client, then zero out its secret and key.
+        if self.client_name == client_name:
+            self.api_key, self.api_secret = "", ""
+
+
     def clients_list(self):
-        """ List all Agave oauth clients
+        """ List all oauth clients
         """
         # Set username.
         if self.username == "" or self.username is None:
diff --git a/agavepy/clients/__init__.py b/agavepy/clients/__init__.py
index aa93013..cfa3ed8 100644
--- a/agavepy/clients/__init__.py
+++ b/agavepy/clients/__init__.py
@@ -1,2 +1,3 @@
 from .create import clients_create
+from .delete import clients_delete
 from .list import clients_list
diff --git a/agavepy/clients/create.py b/agavepy/clients/create.py
index d52e59b..a83f668 100644
--- a/agavepy/clients/create.py
+++ b/agavepy/clients/create.py
@@ -5,17 +5,14 @@ Functions to create Agave oauth clients.
 """
 from __future__ import print_function
 import getpass
-import json
 import requests
-import sys
-from os import path
 from .exceptions import AgaveClientError
 from ..utils import handle_bad_response_status_code
 
 
 
 def clients_create(username, client_name, description, tenant_url):
-    """ Create an Agave client
+    """ Create an oauth client
 
     Make a request to Agave to create an oauth client. Returns the client's api
     key and secret as a tuple.
diff --git a/agavepy/clients/delete.py b/agavepy/clients/delete.py
new file mode 100644
index 0000000..b5b4ff7
--- /dev/null
+++ b/agavepy/clients/delete.py
@@ -0,0 +1,29 @@
+"""
+    clients.py
+"""
+import getpass
+import requests
+from .exceptions import AgaveClientError
+from ..utils import handle_bad_response_status_code
+
+
+
+def clients_delete(tenant_url, username, client_name):
+    """ Create an Oauth client
+    """
+    # Set the endpoint.
+    endpoint = "{}/clients/v2/{}".format(tenant_url, client_name)
+
+    # Get user's password.                                                      
+    passwd = getpass.getpass(prompt="API password: ")
+
+    # Make request.
+    try:
+        resp = requests.delete(endpoint, auth=(username, passwd))
+        del passwd
+    except Exception as err:
+        del passwd
+        raise AgaveClientError(err)
+
+    # Handle bad status code.
+    handle_bad_response_status_code(resp)
diff --git a/agavepy/clients/list.py b/agavepy/clients/list.py
index 140e9d4..eebd29e 100644
--- a/agavepy/clients/list.py
+++ b/agavepy/clients/list.py
@@ -5,10 +5,7 @@ Functions to list agave oauth clients.
 """
 from __future__ import print_function
 import getpass
-import json
 import requests
-import sys
-from os import path
 from .exceptions import AgaveClientError
 from ..utils import handle_bad_response_status_code
 
diff --git a/docs/docsite/authentication/clients.rst b/docs/docsite/authentication/clients.rst
index a7242cc..b3b4402 100644
--- a/docs/docsite/authentication/clients.rst
+++ b/docs/docsite/authentication/clients.rst
@@ -9,8 +9,8 @@ Creating a client
 #################
 
 
-Once you have soecified the tenant you wish to interact with :ref:`tenants`
-we can go ahead and create an oauth client, which in turn we will use to biant
+Once you have specified the tenant you wish to interact with :ref:`tenants`
+we can go ahead and create an oauth client, which in turn we will use to obtain
 and refresh tokens.
 
 To create a client use the method ``clients_create``.
@@ -47,3 +47,17 @@ To list all agave oauth clients registered for a given user, one can use the
     NAME                           DESCRIPTION
     client-name                    some description
     >>>
+
+
+Deleting a client
+#################
+
+If you want to delete an oauth client, you can do as such:
+
+.. code-block:: pycon
+
+    >>> ag.clients_delete("some-client-name")
+    API password:
+
+If you don't pass a client name to ``clients_delete``, then the ``Agave``
+object will try to delete the oauth client in its current session.


Test patch:
diff --git a/tests/clients_test.py b/tests/clients_test.py
index da3563d..729f9ce 100644
--- a/tests/clients_test.py
+++ b/tests/clients_test.py
@@ -35,6 +35,13 @@ class MockServerClientEndpoints(BaseHTTPRequestHandler):
     def do_GET(self):
         """ Mock oauth client listing.
         """
+        # Check that basic auth is used.
+        authorization = self.headers.get("Authorization")
+        if authorization == "" or authorization is None:
+            self.send_response(400)
+            self.end_headers()
+            return
+
         self.send_response(200)
         self.end_headers()
         self.wfile.write(json.dumps(sample_client_list_response).encode())
@@ -43,6 +50,13 @@ class MockServerClientEndpoints(BaseHTTPRequestHandler):
     def do_POST(self):
         """ Mock agave client creation
         """
+        # Check that basic auth is used.
+        authorization = self.headers.get("Authorization")
+        if authorization == "" or authorization is None:
+            self.send_response(400)
+            self.end_headers()
+            return
+
         # Get request data.
         form = cgi.FieldStorage(
             fp = self.rfile,
@@ -80,6 +94,20 @@ class MockServerClientEndpoints(BaseHTTPRequestHandler):
         self.wfile.write(json.dumps(sample_client_create_response).encode())
 
 
+    def do_DELETE(self):
+        """ test clients_delete
+        """
+        # Check that basic auth is used.
+        authorization = self.headers.get("Authorization")
+        if authorization == "" or authorization is None:
+            self.send_response(400)
+            self.end_headers()
+            return
+
+        self.send_response(200)
+        self.end_headers()
+
+
 
 class TestMockServer(MockServer):
     """ Test client-related agave api endpoints 
@@ -120,6 +148,32 @@ class TestMockServer(MockServer):
         assert ag.api_secret == "some secret"
 
 
+    @patch("agavepy.agave.input")                                               
+    @patch("agavepy.clients.delete.getpass.getpass")
+    def test_client_delete(self, mock_input, mock_pass):
+        """ Test clients_delete op
+
+        Patch username and password from user to send a client create request
+        to mock server.
+        """
+        # Patch username and password.
+        mock_input.return_value = "user"
+        mock_pass.return_value = "pass"
+
+        # Instantiate Agave object making reference to local mock server.
+        local_uri = "http://localhost:{port}/".format(port=self.mock_server_port)
+        ag = Agave(api_server=local_uri)
+        ag.client_name = "client-name"
+        ag.api_key = "some api key"
+        ag.api_secret = "some secret"
+
+        # Create client.
+        ag.clients_delete()
+
+        assert ag.api_key == ""
+        assert ag.api_secret == ""
+
+
     @patch("agavepy.agave.input")
     @patch("agavepy.clients.list.getpass.getpass")
     def test_clients_list(self, mock_input, mock_pass, capfd):