Add tests for application credentials

Application credentials were implemented in keystone in Queens. This
patch adds test for create, retrieval, and deleting application
credentials and ensures that application credentials that are created
can be used for authentication. Updating application credentials is not
supported.

bp application-credentials

Change-Id: I3272fee2881fb918fe83961774f4bd27e30cee02
This commit is contained in:
Colleen Murphy 2018-02-17 21:29:40 +01:00 committed by Colleen Murphy
parent 9c48584e01
commit 0e52d4e706
10 changed files with 438 additions and 10 deletions

View File

@ -0,0 +1,9 @@
---
features:
- |
[`blueprint application-credentials <https://blueprints.launchpad.net/keystone/+spec/application-credentials>`_]
Tempest can test keystone's application credentials interface. A new client
library is added for application credentials, and a new config option,
``[identity-feature-enabled]/application_credentials``, can control whether
the application credentials feature is tested (defaults to False,
indicating the feature is not enabled in the cloud under test).

View File

@ -0,0 +1,48 @@
# Copyright 2018 SUSE Linux GmbH
#
# 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 import config
from tempest.lib import decorators
CONF = config.CONF
class ApplicationCredentialsV3AdminTest(base.BaseApplicationCredentialsV3Test,
base.BaseIdentityV3AdminTest):
@decorators.idempotent_id('3b3dd48f-3388-406a-a9e6-4d078a552d0e')
def test_create_application_credential_with_roles(self):
role = self.setup_test_role()
self.os_admin.roles_v3_client.create_user_role_on_project(
self.project_id,
self.user_id,
role['id']
)
app_cred = self.create_application_credential(
roles=[{'id': role['id']}])
secret = app_cred['secret']
# Check that the application credential is functional
token_id, resp = self.non_admin_token.get_token(
app_cred_id=app_cred['id'],
app_cred_secret=secret,
auth_data=True
)
self.assertEqual(resp['project']['id'], self.project_id)
self.assertEqual(resp['roles'][0]['id'], role['id'])

View File

@ -190,6 +190,8 @@ class BaseIdentityV3Test(BaseIdentityTest):
cls.non_admin_catalog_client = cls.os_primary.catalog_client cls.non_admin_catalog_client = cls.os_primary.catalog_client
cls.non_admin_versions_client =\ cls.non_admin_versions_client =\
cls.os_primary.identity_versions_v3_client cls.os_primary.identity_versions_v3_client
cls.non_admin_app_creds_client = \
cls.os_primary.application_credentials_client
class BaseIdentityV3AdminTest(BaseIdentityV3Test): class BaseIdentityV3AdminTest(BaseIdentityV3Test):
@ -289,3 +291,30 @@ class BaseIdentityV3AdminTest(BaseIdentityV3Test):
test_utils.call_and_ignore_notfound_exc, test_utils.call_and_ignore_notfound_exc,
self.delete_domain, domain['id']) self.delete_domain, domain['id'])
return domain return domain
class BaseApplicationCredentialsV3Test(BaseIdentityV3Test):
@classmethod
def skip_checks(cls):
super(BaseApplicationCredentialsV3Test, cls).skip_checks()
if not CONF.identity_feature_enabled.application_credentials:
raise cls.skipException("Application credentials are not available"
" in this environment")
@classmethod
def resource_setup(cls):
super(BaseApplicationCredentialsV3Test, cls).resource_setup()
cls.user_id = cls.os_primary.credentials.user_id
cls.project_id = cls.os_primary.credentials.project_id
def create_application_credential(self, name=None, **kwargs):
name = name or data_utils.rand_name('application_credential')
application_credential = (
self.non_admin_app_creds_client.create_application_credential(
self.user_id, name=name, **kwargs))['application_credential']
self.addCleanup(
self.non_admin_app_creds_client.delete_application_credential,
self.user_id,
application_credential['id'])
return application_credential

View File

@ -0,0 +1,85 @@
# Copyright 2018 SUSE Linux GmbH
#
# 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.
import datetime
from oslo_utils import timeutils
from tempest.api.identity import base
from tempest import config
from tempest.lib import decorators
CONF = config.CONF
class ApplicationCredentialsV3Test(base.BaseApplicationCredentialsV3Test):
def _list_app_creds(self, name=None):
kwargs = dict(user_id=self.user_id)
if name:
kwargs.update(name=name)
return self.non_admin_app_creds_client.list_application_credentials(
**kwargs)['application_credentials']
@decorators.idempotent_id('8080c75c-eddc-4786-941a-c2da7039ae61')
def test_create_application_credential(self):
app_cred = self.create_application_credential()
# Check that the secret appears in the create response
secret = app_cred['secret']
# Check that the secret is not retrievable after initial create
app_cred = self.non_admin_app_creds_client.show_application_credential(
user_id=self.user_id,
application_credential_id=app_cred['id']
)['application_credential']
self.assertNotIn('secret', app_cred)
# Check that the application credential is functional
token_id, resp = self.non_admin_token.get_token(
app_cred_id=app_cred['id'],
app_cred_secret=secret,
auth_data=True
)
self.assertEqual(resp['project']['id'], self.project_id)
@decorators.idempotent_id('852daf0c-42b5-4239-8466-d193d0543ed3')
def test_create_application_credential_expires(self):
expires_at = timeutils.utcnow() + datetime.timedelta(hours=1)
app_cred = self.create_application_credential(expires_at=expires_at)
expires_str = expires_at.isoformat()
self.assertEqual(expires_str, app_cred['expires_at'])
@decorators.idempotent_id('ff0cd457-6224-46e7-b79e-0ada4964a8a6')
def test_list_application_credentials(self):
self.create_application_credential()
self.create_application_credential()
app_creds = self._list_app_creds()
self.assertEqual(2, len(app_creds))
@decorators.idempotent_id('9bb5e5cc-5250-493a-8869-8b665f6aa5f6')
def test_query_application_credentials(self):
self.create_application_credential()
app_cred_two = self.create_application_credential()
app_cred_two_name = app_cred_two['name']
app_creds = self._list_app_creds(name=app_cred_two_name)
self.assertEqual(1, len(app_creds))
self.assertEqual(app_cred_two_name, app_creds[0]['name'])

View File

@ -199,6 +199,8 @@ class Manager(clients.ServiceClients):
self.catalog_client = self.identity_v3.CatalogClient(**params_v3) self.catalog_client = self.identity_v3.CatalogClient(**params_v3)
self.project_tags_client = self.identity_v3.ProjectTagsClient( self.project_tags_client = self.identity_v3.ProjectTagsClient(
**params_v3) **params_v3)
self.application_credentials_client = \
self.identity_v3.ApplicationCredentialsClient(**params_v3)
# Token clients do not use the catalog. They only need default_params. # Token clients do not use the catalog. They only need default_params.
# They read auth_url, so they should only be set if the corresponding # They read auth_url, so they should only be set if the corresponding

View File

@ -240,7 +240,13 @@ IdentityFeatureGroup = [
'settings enabled?'), 'settings enabled?'),
cfg.BoolOpt('project_tags', cfg.BoolOpt('project_tags',
default=False, default=False,
help='Is the project tags identity v3 API available?') help='Is the project tags identity v3 API available?'),
# Application credentials is a default feature in Queens. This config
# option can removed once Pike is EOL.
cfg.BoolOpt('application_credentials',
default=False,
help='Does the environment have application credentials '
'enabled?')
] ]
compute_group = cfg.OptGroup(name='compute', compute_group = cfg.OptGroup(name='compute',

View File

@ -12,6 +12,8 @@
# License for the specific language governing permissions and limitations under # License for the specific language governing permissions and limitations under
# the License. # the License.
from tempest.lib.services.identity.v3.application_credentials_client import \
ApplicationCredentialsClient
from tempest.lib.services.identity.v3.catalog_client import \ from tempest.lib.services.identity.v3.catalog_client import \
CatalogClient CatalogClient
from tempest.lib.services.identity.v3.credentials_client import \ from tempest.lib.services.identity.v3.credentials_client import \
@ -46,11 +48,11 @@ from tempest.lib.services.identity.v3.trusts_client import TrustsClient
from tempest.lib.services.identity.v3.users_client import UsersClient from tempest.lib.services.identity.v3.users_client import UsersClient
from tempest.lib.services.identity.v3.versions_client import VersionsClient from tempest.lib.services.identity.v3.versions_client import VersionsClient
__all__ = ['CatalogClient', 'CredentialsClient', 'DomainsClient', __all__ = ['ApplicationCredentialsClient', 'CatalogClient',
'DomainConfigurationClient', 'EndPointGroupsClient', 'CredentialsClient', 'DomainsClient', 'DomainConfigurationClient',
'EndPointsClient', 'EndPointsFilterClient', 'GroupsClient', 'EndPointGroupsClient', 'EndPointsClient', 'EndPointsFilterClient',
'IdentityClient', 'InheritedRolesClient', 'OAUTHConsumerClient', 'GroupsClient', 'IdentityClient', 'InheritedRolesClient',
'OAUTHTokenClient', 'PoliciesClient', 'ProjectsClient', 'OAUTHConsumerClient', 'OAUTHTokenClient', 'PoliciesClient',
'ProjectTagsClient', 'RegionsClient', 'RoleAssignmentsClient', 'ProjectsClient', 'ProjectTagsClient', 'RegionsClient',
'RolesClient', 'ServicesClient', 'V3TokenClient', 'TrustsClient', 'RoleAssignmentsClient', 'RolesClient', 'ServicesClient',
'UsersClient', 'VersionsClient'] 'V3TokenClient', 'TrustsClient', 'UsersClient', 'VersionsClient']

View File

@ -0,0 +1,83 @@
# Copyright 2018 SUSE Linux GmbH
#
# 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.
"""
https://developer.openstack.org/api-ref/identity/v3/index.html#application-credentials
"""
from oslo_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
class ApplicationCredentialsClient(rest_client.RestClient):
api_version = "v3"
def create_application_credential(self, user_id, **kwargs):
"""Creates an application credential.
For a full list of available parameters, please refer to the official
API reference:
https://developer.openstack.org/api-ref/identity/v3/index.html#create-application-credential
"""
post_body = json.dumps({'application_credential': kwargs})
resp, body = self.post('users/%s/application_credentials' % user_id,
post_body)
self.expected_success(201, resp.status)
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
def show_application_credential(self, user_id, application_credential_id):
"""Gets details of an application credential.
For a full list of available parameters, please refer to the official
API reference:
https://developer.openstack.org/api-ref/identity/v3/index.html#show-application-credential-details
"""
resp, body = self.get('users/%s/application_credentials/%s' %
(user_id, application_credential_id))
self.expected_success(200, resp.status)
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
def list_application_credentials(self, user_id, **params):
"""Lists out all of a user's application credentials.
For a full list of available parameters, please refer to the official
API reference:
https://developer.openstack.org/api-ref/identity/v3/index.html#list-application-credentials
"""
url = 'users/%s/application_credentials' % user_id
if params:
url += '?%s' % urllib.urlencode(params)
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
def delete_application_credential(self, user_id,
application_credential_id):
"""Deletes an application credential.
For a full list of available parameters, please refer to the official
API reference:
https://developer.openstack.org/api-ref/identity/v3/index.html#delete-application-credential
"""
resp, body = self.delete('users/%s/application_credentials/%s' %
(user_id, application_credential_id))
self.expected_success(204, resp.status)
return rest_client.ResponseBody(resp, body)

View File

@ -51,7 +51,8 @@ class V3TokenClient(rest_client.RestClient):
def auth(self, user_id=None, username=None, password=None, project_id=None, def auth(self, user_id=None, username=None, password=None, project_id=None,
project_name=None, user_domain_id=None, user_domain_name=None, project_name=None, user_domain_id=None, user_domain_name=None,
project_domain_id=None, project_domain_name=None, domain_id=None, project_domain_id=None, project_domain_name=None, domain_id=None,
domain_name=None, token=None): domain_name=None, token=None, app_cred_id=None,
app_cred_secret=None):
"""Obtains a token from the authentication service """Obtains a token from the authentication service
:param user_id: user id :param user_id: user id
@ -109,6 +110,13 @@ class V3TokenClient(rest_client.RestClient):
if _domain: if _domain:
id_obj['password']['user']['domain'] = _domain id_obj['password']['user']['domain'] = _domain
if app_cred_id and app_cred_secret:
id_obj['methods'].append('application_credential')
id_obj['application_credential'] = {
'id': app_cred_id,
'secret': app_cred_secret,
}
if (project_id or project_name): if (project_id or project_name):
_project = dict() _project = dict()

View File

@ -0,0 +1,156 @@
# Copyright 2018 SUSE Linux GmbH
#
# 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.lib.services.identity.v3 import application_credentials_client
from tempest.tests.lib import fake_auth_provider
from tempest.tests.lib.services import base
class TestApplicationCredentialsClient(base.BaseServiceTest):
FAKE_CREATE_APP_CRED = {
"application_credential": {
"description": "fake application credential",
"roles": [
{
"id": "c60fdd45",
"domain_id": None,
"name": "Member"
}
],
"expires_at": "2019-02-27T18:30:59.999999Z",
"secret": "_BVq0xU5L",
"unrestricted": None,
"project_id": "ddef321",
"id": "5499a186",
"name": "one"
}
}
FAKE_LIST_APP_CREDS = {
"application_credentials": [
{
"description": "fake application credential",
"roles": [
{
"domain_id": None,
"name": "Member",
"id": "c60fdd45",
}
],
"expires_at": "2018-02-27T18:30:59.999999Z",
"unrestricted": None,
"project_id": "ddef321",
"id": "5499a186",
"name": "one"
},
{
"description": None,
"roles": [
{
"id": "0f1837c8",
"domain_id": None,
"name": "anotherrole"
},
{
"id": "c60fdd45",
"domain_id": None,
"name": "Member"
}
],
"expires_at": None,
"unrestricted": None,
"project_id": "c5403d938",
"id": "d441c904f",
"name": "two"
}
]
}
FAKE_APP_CRED_INFO = {
"application_credential": {
"description": None,
"roles": [
{
"domain_id": None,
"name": "Member",
"id": "c60fdd45",
}
],
"expires_at": None,
"unrestricted": None,
"project_id": "ddef321",
"id": "5499a186",
"name": "one"
}
}
def setUp(self):
super(TestApplicationCredentialsClient, self).setUp()
fake_auth = fake_auth_provider.FakeAuthProvider()
self.client = \
application_credentials_client.ApplicationCredentialsClient(
fake_auth, 'identity', 'regionOne')
def _test_create_app_cred(self, bytes_body=False):
self.check_service_client_function(
self.client.create_application_credential,
'tempest.lib.common.rest_client.RestClient.post',
self.FAKE_CREATE_APP_CRED,
bytes_body,
status=201,
user_id="123456")
def _test_show_app_cred(self, bytes_body=False):
self.check_service_client_function(
self.client.show_application_credential,
'tempest.lib.common.rest_client.RestClient.get',
self.FAKE_APP_CRED_INFO,
bytes_body,
user_id="123456",
application_credential_id="5499a186")
def _test_list_app_creds(self, bytes_body=False):
self.check_service_client_function(
self.client.list_application_credentials,
'tempest.lib.common.rest_client.RestClient.get',
self.FAKE_LIST_APP_CREDS,
bytes_body,
user_id="123456")
def test_create_application_credential_with_str_body(self):
self._test_create_app_cred()
def test_create_application_credential_with_bytes_body(self):
self._test_create_app_cred(bytes_body=True)
def test_show_application_credential_with_str_body(self):
self._test_show_app_cred()
def test_show_application_credential_with_bytes_body(self):
self._test_show_app_cred(bytes_body=True)
def test_list_application_credential_with_str_body(self):
self._test_list_app_creds()
def test_list_application_credential_with_bytes_body(self):
self._test_list_app_creds(bytes_body=True)
def test_delete_trust(self):
self.check_service_client_function(
self.client.delete_application_credential,
'tempest.lib.common.rest_client.RestClient.delete',
{},
user_id="123456",
application_credential_id="5499a186",
status=204)