Test Tokens - V3

Adds test script "test_tokens.py" so as to validate v3 password
authentication for a given user. Support functions are added in
client files in both the interfaces.

Implements: blueprint keystone-tokens-test
Change-Id: I9a810844b1d991f829f411db308e37a02bd6c09e
diff --git a/tempest/api/identity/admin/v3/test_tokens.py b/tempest/api/identity/admin/v3/test_tokens.py
new file mode 100644
index 0000000..2a20493
--- /dev/null
+++ b/tempest/api/identity/admin/v3/test_tokens.py
@@ -0,0 +1,57 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack, LLC
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from tempest.api.identity import base
+from tempest.common.utils.data_utils import rand_name
+from tempest import exceptions
+from tempest.test import attr
+
+
+class UsersTestJSON(base.BaseIdentityAdminTest):
+    _interface = 'json'
+
+    @attr(type='smoke')
+    def test_tokens(self):
+        # Valid user's token is authenticated
+        # Create a User
+        u_name = rand_name('user-')
+        u_desc = '%s-description' % u_name
+        u_email = '%s@testmail.tm' % u_name
+        u_password = rand_name('pass-')
+        resp, user = self.v3_client.create_user(
+            u_name, description=u_desc, password=u_password,
+            email=u_email)
+        self.assertTrue(resp['status'].startswith('2'))
+        self.addCleanup(self.v3_client.delete_user, user['id'])
+        # Perform Authentication
+        resp, body = self.v3_token.auth(user['id'], u_password)
+        self.assertEqual(resp['status'], '201')
+        subject_token = resp['x-subject-token']
+        # Perform GET Token
+        resp, token_details = self.v3_client.get_token(subject_token)
+        self.assertEqual(resp['status'], '200')
+        self.assertEqual(resp['x-subject-token'], subject_token)
+        self.assertEqual(token_details['user']['id'], user['id'])
+        self.assertEqual(token_details['user']['name'], u_name)
+        # Perform Delete Token
+        resp, _ = self.v3_client.delete_token(subject_token)
+        self.assertRaises(exceptions.Unauthorized, self.v3_client.get_token,
+                          subject_token)
+
+
+class UsersTestXML(UsersTestJSON):
+    _interface = 'xml'
diff --git a/tempest/api/identity/base.py b/tempest/api/identity/base.py
index db55509..1237ce4 100644
--- a/tempest/api/identity/base.py
+++ b/tempest/api/identity/base.py
@@ -32,6 +32,7 @@
         cls.v3_client = os.identity_v3_client
         cls.service_client = os.service_client
         cls.policy_client = os.policy_client
+        cls.v3_token = os.token_v3_client
 
         if not cls.client.has_admin_extensions():
             raise cls.skipException("Admin extensions disabled")
diff --git a/tempest/clients.py b/tempest/clients.py
index a5c7b4d..99e1c14 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -75,12 +75,14 @@
     EndPointClientJSON
 from tempest.services.identity.v3.json.identity_client import \
     IdentityV3ClientJSON
+from tempest.services.identity.v3.json.identity_client import V3TokenClientJSON
 from tempest.services.identity.v3.json.policy_client import PolicyClientJSON
 from tempest.services.identity.v3.json.service_client import \
     ServiceClientJSON
 from tempest.services.identity.v3.xml.endpoints_client import EndPointClientXML
 from tempest.services.identity.v3.xml.identity_client import \
     IdentityV3ClientXML
+from tempest.services.identity.v3.xml.identity_client import V3TokenClientXML
 from tempest.services.identity.v3.xml.policy_client import PolicyClientXML
 from tempest.services.identity.v3.xml.service_client import \
     ServiceClientXML
@@ -239,6 +241,11 @@
     "xml": HypervisorClientXML,
 }
 
+V3_TOKEN_CLIENT = {
+    "json": V3TokenClientJSON,
+    "xml": V3TokenClientXML,
+}
+
 
 class Manager(object):
 
@@ -319,6 +326,7 @@
                 TENANT_USAGES_CLIENT[interface](*client_args)
             self.policy_client = POLICY_CLIENT[interface](*client_args)
             self.hypervisor_client = HYPERVISOR_CLIENT[interface](*client_args)
+            self.token_v3_client = V3_TOKEN_CLIENT[interface](*client_args)
 
             if client_args_v3_auth:
                 self.servers_client_v3_auth = SERVERS_CLIENTS[interface](
diff --git a/tempest/services/identity/v3/json/identity_client.py b/tempest/services/identity/v3/json/identity_client.py
index adbdc83..56a1a72 100644
--- a/tempest/services/identity/v3/json/identity_client.py
+++ b/tempest/services/identity/v3/json/identity_client.py
@@ -208,3 +208,61 @@
         resp, body = self.get('domains/%s' % domain_id)
         body = json.loads(body)
         return resp, body['domain']
+
+    def get_token(self, resp_token):
+        """Get token details."""
+        headers = {'X-Subject-Token': resp_token}
+        resp, body = self.get("auth/tokens", headers=headers)
+        body = json.loads(body)
+        return resp, body['token']
+
+    def delete_token(self, resp_token):
+        """Deletes token."""
+        headers = {'X-Subject-Token': resp_token}
+        resp, body = self.delete("auth/tokens", headers=headers)
+        return resp, body
+
+
+class V3TokenClientJSON(RestClient):
+
+    def __init__(self, config, username, password, auth_url, tenant_name=None):
+        super(V3TokenClientJSON, self).__init__(config, username, password,
+                                                auth_url, tenant_name)
+        self.service = self.config.identity.catalog_type
+        self.endpoint_url = 'adminURL'
+
+        auth_url = config.identity.uri
+
+        if 'tokens' not in auth_url:
+            auth_url = auth_url.rstrip('/') + '/tokens'
+
+        self.auth_url = auth_url
+        self.config = config
+
+    def auth(self, user_id, password):
+        creds = {
+            'auth': {
+                'identity': {
+                    'methods': ['password'],
+                    'password': {
+                        'user': {
+                            'id': user_id,
+                            'password': password
+                        }
+                    }
+                }
+            }
+        }
+        headers = {'Content-Type': 'application/json'}
+        body = json.dumps(creds)
+        resp, body = self.post("auth/tokens", headers=headers, body=body)
+        return resp, body
+
+    def request(self, method, url, headers=None, body=None, wait=None):
+        """Overriding the existing HTTP request in super class rest_client."""
+        self._set_auth()
+        self.base_url = self.base_url.replace(urlparse(self.base_url).path,
+                                              "/v3")
+        return super(V3TokenClientJSON, self).request(method, url,
+                                                      headers=headers,
+                                                      body=body)
diff --git a/tempest/services/identity/v3/xml/identity_client.py b/tempest/services/identity/v3/xml/identity_client.py
index 708ee28..571b491 100644
--- a/tempest/services/identity/v3/xml/identity_client.py
+++ b/tempest/services/identity/v3/xml/identity_client.py
@@ -22,9 +22,9 @@
 from tempest.common.rest_client import RestClientXML
 from tempest.services.compute.xml.common import Document
 from tempest.services.compute.xml.common import Element
+from tempest.services.compute.xml.common import Text
 from tempest.services.compute.xml.common import xml_to_json
 
-
 XMLNS = "http://docs.openstack.org/identity/api/v3"
 
 
@@ -241,3 +241,65 @@
         resp, body = self.get('domains/%s' % domain_id, self.headers)
         body = self._parse_body(etree.fromstring(body))
         return resp, body
+
+    def get_token(self, resp_token):
+        """GET a Token Details."""
+        headers = {'Content-Type': 'application/xml',
+                   'Accept': 'application/xml',
+                   'X-Subject-Token': resp_token}
+        resp, body = self.get("auth/tokens", headers=headers)
+        body = self._parse_body(etree.fromstring(body))
+        return resp, body
+
+    def delete_token(self, resp_token):
+        """Delete a Given Token."""
+        headers = {'X-Subject-Token': resp_token}
+        resp, body = self.delete("auth/tokens", headers=headers)
+        return resp, body
+
+
+class V3TokenClientXML(RestClientXML):
+
+    def __init__(self, config, username, password, auth_url, tenant_name=None):
+        super(V3TokenClientXML, self).__init__(config, username, password,
+                                               auth_url, tenant_name)
+        self.service = self.config.identity.catalog_type
+        self.endpoint_url = 'adminURL'
+
+        auth_url = config.identity.uri
+
+        if 'tokens' not in auth_url:
+            auth_url = auth_url.rstrip('/') + '/tokens'
+
+        self.auth_url = auth_url
+        self.config = config
+
+    def auth(self, user_id, password):
+        user = Element('user',
+                       id=user_id,
+                       password=password)
+        password = Element('password')
+        password.append(user)
+
+        method = Element('method')
+        method.append(Text('password'))
+        methods = Element('methods')
+        methods.append(method)
+        identity = Element('identity')
+        identity.append(methods)
+        identity.append(password)
+        auth = Element('auth')
+        auth.append(identity)
+        headers = {'Content-Type': 'application/xml'}
+        resp, body = self.post("auth/tokens", headers=headers,
+                               body=str(Document(auth)))
+        return resp, body
+
+    def request(self, method, url, headers=None, body=None, wait=None):
+        """Overriding the existing HTTP request in super class rest_client."""
+        self._set_auth()
+        self.base_url = self.base_url.replace(urlparse(self.base_url).path,
+                                              "/v3")
+        return super(V3TokenClientXML, self).request(method, url,
+                                                     headers=headers,
+                                                     body=body)