From df55840009c9bcac52a0e1a99faabc885e8a9052 Mon Sep 17 00:00:00 2001
From: Kirill Izotov <enykeev@stackstorm.com>
Date: Fri, 28 Feb 2014 15:45:40 +0700
Subject: [PATCH] Made keystone authentication optional

You need to supply auth_url to authenticate using Keystone. Otherwise,
auth_token, user_id, project_id and mistral_url provided will be
directly transmitted to HTTPClient.

Also:
 * renamed input_auth_token as auth_token
 * optimized few checks
 * added checks to prevent header update when not needed
 * simplified API test setUp

Change-Id: I1b34282956d21a1809ec37b0368fbb68aa113bea
Implements: blueprint mistral-optional-keystone-authentication
---
 mistralclient/api/client.py     | 85 ++++++++++++++++++---------------
 mistralclient/api/httpclient.py | 13 +++--
 mistralclient/shell.py          |  4 +-
 mistralclient/tests/base.py     |  5 +-
 4 files changed, 59 insertions(+), 48 deletions(-)

diff --git a/mistralclient/api/client.py b/mistralclient/api/client.py
index 386b2816..e6195ebe 100644
--- a/mistralclient/api/client.py
+++ b/mistralclient/api/client.py
@@ -29,19 +29,23 @@ class Client(object):
     def __init__(self, mistral_url=None, username=None, api_key=None,
                  project_name=None, auth_url=None, project_id=None,
                  endpoint_type='publicURL', service_type='workflow',
-                 input_auth_token=None):
+                 auth_token=None, user_id=None):
 
-        (mistral_url,
-         token,
-         project_id,
-         user_id) = self.authenticate(mistral_url, username,
-                                      api_key, project_name,
-                                      auth_url, project_id,
-                                      endpoint_type, service_type,
-                                      input_auth_token)
+        if mistral_url and not isinstance(mistral_url, six.string_types):
+            raise RuntimeError('Mistral url should be string')
+
+        if auth_url:
+            (mistral_url, auth_token, project_id, user_id) = \
+                self.authenticate(mistral_url, username, api_key,
+                                  project_name, auth_url, project_id,
+                                  endpoint_type, service_type, auth_token,
+                                  user_id)
+
+        if not mistral_url:
+            mistral_url = "http://localhost:8989/v1"
 
         self.http_client = httpclient.HTTPClient(mistral_url,
-                                                 token,
+                                                 auth_token,
                                                  project_id,
                                                  user_id)
         # Create all resource managers.
@@ -53,34 +57,42 @@ class Client(object):
     def authenticate(self, mistral_url=None, username=None, api_key=None,
                      project_name=None, auth_url=None, project_id=None,
                      endpoint_type='publicURL', service_type='workflow',
-                     input_auth_token=None):
-        if mistral_url and not isinstance(mistral_url, six.string_types):
-            raise RuntimeError('Mistral url should be string')
-        if ((isinstance(project_name, six.string_types) and project_name) or
-                (isinstance(project_id, six.string_types) and project_id)):
-            if project_name and project_id:
-                raise RuntimeError('Only project name or '
-                                   'project id should be set')
+                     auth_token=None, user_id=None):
 
-            if "v2.0" in auth_url:
-                raise RuntimeError('Mistral supports only v3  '
-                                   'keystone API.')
+        if (not (project_name or project_id) or
+            not (isinstance(project_name, six.string_types) or
+                 isinstance(project_id, six.string_types))):
+            raise RuntimeError('Either project name or project id should'
+                               ' be non-empty string')
+        if project_name and project_id:
+            raise RuntimeError('Only project name or '
+                               'project id should be set')
 
-            keystone = keystone_client.Client(username=username,
-                                              password=api_key,
-                                              token=input_auth_token,
-                                              tenant_id=project_id,
-                                              tenant_name=project_name,
-                                              auth_url=auth_url)
+        if (not (username or user_id) or
+            not (isinstance(username, six.string_types) or
+                 isinstance(user_id, six.string_types))):
+            raise RuntimeError('Either user name or user id should'
+                               ' be non-empty string')
+        if username and user_id:
+            raise RuntimeError('Only user name or user id'
+                               ' should be set')
 
-            keystone.authenticate()
-            token = keystone.auth_token
-            user_id = keystone.user_id
-            if project_name and not project_id:
-                project_id = keystone.project_id
-        else:
-            raise RuntimeError('Project name or project id should'
-                               ' not be empty and should be non-empty string')
+        if "v2.0" in auth_url:
+            raise RuntimeError('Mistral supports only v3  '
+                               'keystone API.')
+
+        keystone = keystone_client.Client(username=username,
+                                          user_id=user_id,
+                                          password=api_key,
+                                          token=auth_token,
+                                          project_id=project_id,
+                                          project_name=project_name,
+                                          auth_url=auth_url)
+
+        keystone.authenticate()
+        token = keystone.auth_token
+        user_id = keystone.user_id
+        project_id = keystone.project_id
 
         if not mistral_url:
             catalog = keystone.service_catalog.get_endpoints(service_type)
@@ -90,7 +102,4 @@ class Client(object):
                         mistral_url = endpoint
                         break
 
-        if not mistral_url:
-            mistral_url = "http://localhost:8989/v1"
-
         return mistral_url, token, project_id, user_id
diff --git a/mistralclient/api/httpclient.py b/mistralclient/api/httpclient.py
index 3a9bd5ec..6611cf58 100644
--- a/mistralclient/api/httpclient.py
+++ b/mistralclient/api/httpclient.py
@@ -18,7 +18,7 @@ import requests
 
 
 class HTTPClient(object):
-    def __init__(self, base_url, token, project_id, user_id):
+    def __init__(self, base_url, token=None, project_id=None, user_id=None):
         self.base_url = base_url
         self.token = token
         self.project_id = project_id
@@ -51,12 +51,17 @@ class HTTPClient(object):
     def _update_headers(self, headers):
         if not headers:
             headers = {}
+
         token = headers.get('x-auth-token', self.token)
-        headers['x-auth-token'] = token
+        if token:
+            headers['x-auth-token'] = token
 
         project_id = headers.get('X-Project-Id', self.project_id)
-        headers['X-Project-Id'] = project_id
+        if project_id:
+            headers['X-Project-Id'] = project_id
 
         user_id = headers.get('X-User-Id', self.user_id)
-        headers['X-User-Id'] = user_id
+        if user_id:
+            headers['X-User-Id'] = user_id
+
         return headers
diff --git a/mistralclient/shell.py b/mistralclient/shell.py
index 30eccdfd..f5fdff01 100644
--- a/mistralclient/shell.py
+++ b/mistralclient/shell.py
@@ -176,7 +176,7 @@ class MistralShell(App):
             '--os-auth-url',
             action='store',
             dest='auth_url',
-            default=env('OS_AUTH_URL', default='http://localhost:5000/v3'),
+            default=env('OS_AUTH_URL'),
             help='Authentication URL (Env: OS_AUTH_URL)'
         )
         return parser
@@ -190,7 +190,7 @@ class MistralShell(App):
                              project_id=self.options.tenant_id,
                              endpoint_type='publicURL',
                              service_type='workflow',
-                             input_auth_token=self.options.token)
+                             auth_token=self.options.token)
 
 
 def main(argv=sys.argv[1:]):
diff --git a/mistralclient/tests/base.py b/mistralclient/tests/base.py
index 7688b6c3..3ffdff1a 100644
--- a/mistralclient/tests/base.py
+++ b/mistralclient/tests/base.py
@@ -33,11 +33,8 @@ class FakeResponse(object):
 
 
 class BaseClientTest(unittest2.TestCase):
-    @mock.patch('keystoneclient.v3.client.Client')
-    def setUp(self, keystone):
-        keystone.return_value = mock.Mock()
+    def setUp(self):
         self._client = client.Client(project_name="test",
-                                     auth_url="v3.0",
                                      mistral_url="test")
         self.workbooks = self._client.workbooks
         self.executions = self._client.executions