diff options
| author | Colleen Murphy <colleen@gazlene.net> | 2018-01-21 20:02:02 +0100 |
|---|---|---|
| committer | Colleen Murphy <colleen@gazlene.net> | 2018-01-30 21:50:01 +0100 |
| commit | 375964f270e125b8887e0ca4ee1cbe15d5eddf04 (patch) | |
| tree | 65a6bf1e522ddee00f3c30f1ebccdf80f030fd18 /openstackclient/tests | |
| parent | 1e30be92d8b30e834b161c2246a499775d6ec6bc (diff) | |
| download | python-openstackclient-375964f270e125b8887e0ca4ee1cbe15d5eddf04.tar.gz | |
Add CRUD support for application credentials
Add support for creating, retrieving, and deleting application
credentials. Application credentials do not support updates.
In order to provide a positive user experience for the `--role` option,
this patch also includes an improvement to the
`identity.common._get_token_resource()` function that allows it to
introspect the roles list within a token. This way there is no need to
make a request to keystone to retrieve a role object, which would fail
most of the time anyway due to keystone's default policy prohibiting
unprivileged users from retrieving roles.
bp application-credentials
Change-Id: I29e03b72acd931305cbdac5a9ff666854d05c6d7
Diffstat (limited to 'openstackclient/tests')
3 files changed, 484 insertions, 0 deletions
diff --git a/openstackclient/tests/functional/identity/v3/test_application_credential.py b/openstackclient/tests/functional/identity/v3/test_application_credential.py new file mode 100644 index 00000000..daf64607 --- /dev/null +++ b/openstackclient/tests/functional/identity/v3/test_application_credential.py @@ -0,0 +1,143 @@ +# 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. + +import datetime + +from tempest.lib.common.utils import data_utils + +from openstackclient.tests.functional.identity.v3 import common + + +class ApplicationCredentialTests(common.IdentityTests): + + APPLICATION_CREDENTIAL_FIELDS = ['id', 'name', 'project_id', + 'description', 'roles', 'expires_at', + 'unrestricted'] + APPLICATION_CREDENTIAL_LIST_HEADERS = ['ID', 'Name', 'Project ID', + 'Description', 'Expires At'] + + def test_application_credential_create(self): + name = data_utils.rand_name('name') + raw_output = self.openstack('application credential create %(name)s' + % {'name': name}) + self.addCleanup( + self.openstack, + 'application credential delete %(name)s' % {'name': name}) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.APPLICATION_CREDENTIAL_FIELDS) + + def _create_role_assignments(self): + try: + user = self.openstack('configuration show -f value' + ' -c auth.username') + except Exception: + user = self.openstack('configuration show -f value' + ' -c auth.user_id') + try: + user_domain = self.openstack('configuration show -f value' + ' -c auth.user_domain_name') + except Exception: + user_domain = self.openstack('configuration show -f value' + ' -c auth.user_domain_id') + try: + project = self.openstack('configuration show -f value' + ' -c auth.project_name') + except Exception: + project = self.openstack('configuration show -f value' + ' -c auth.project_id') + try: + project_domain = self.openstack('configuration show -f value' + ' -c auth.project_domain_name') + except Exception: + project_domain = self.openstack('configuration show -f value' + ' -c auth.project_domain_id') + role1 = self._create_dummy_role() + role2 = self._create_dummy_role() + for role in role1, role2: + self.openstack('role add' + ' --user %(user)s' + ' --user-domain %(user_domain)s' + ' --project %(project)s' + ' --project-domain %(project_domain)s' + ' %(role)s' + % {'user': user, + 'user_domain': user_domain, + 'project': project, + 'project_domain': project_domain, + 'role': role}) + self.addCleanup(self.openstack, + 'role remove' + ' --user %(user)s' + ' --user-domain %(user_domain)s' + ' --project %(project)s' + ' --project-domain %(project_domain)s' + ' %(role)s' + % {'user': user, + 'user_domain': user_domain, + 'project': project, + 'project_domain': project_domain, + 'role': role}) + return role1, role2 + + def test_application_credential_create_with_options(self): + name = data_utils.rand_name('name') + secret = data_utils.rand_name('secret') + description = data_utils.rand_name('description') + tomorrow = (datetime.datetime.utcnow() + + datetime.timedelta(days=1)).strftime('%Y-%m-%dT%H:%M:%S%z') + role1, role2 = self._create_role_assignments() + raw_output = self.openstack('application credential create %(name)s' + ' --secret %(secret)s' + ' --description %(description)s' + ' --expiration %(tomorrow)s' + ' --role %(role1)s' + ' --role %(role2)s' + ' --unrestricted' + % {'name': name, + 'secret': secret, + 'description': description, + 'tomorrow': tomorrow, + 'role1': role1, + 'role2': role2}) + self.addCleanup( + self.openstack, + 'application credential delete %(name)s' % {'name': name}) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.APPLICATION_CREDENTIAL_FIELDS) + + def test_application_credential_delete(self): + name = data_utils.rand_name('name') + self.openstack('application credential create %(name)s' + % {'name': name}) + raw_output = self.openstack('application credential delete ' + '%(name)s' % {'name': name}) + self.assertEqual(0, len(raw_output)) + + def test_application_credential_list(self): + raw_output = self.openstack('application credential list') + items = self.parse_listing(raw_output) + self.assert_table_structure( + items, self.APPLICATION_CREDENTIAL_LIST_HEADERS) + + def test_application_credential_show(self): + name = data_utils.rand_name('name') + raw_output = self.openstack('application credential create %(name)s' + % {'name': name}) + self.addCleanup( + self.openstack, + 'application credential delete %(name)s' % {'name': name}) + raw_output = self.openstack('application credential show ' + '%(name)s' % {'name': name}) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.APPLICATION_CREDENTIAL_FIELDS) diff --git a/openstackclient/tests/unit/identity/v3/fakes.py b/openstackclient/tests/unit/identity/v3/fakes.py index 3e2caf01..fc06f9ec 100644 --- a/openstackclient/tests/unit/identity/v3/fakes.py +++ b/openstackclient/tests/unit/identity/v3/fakes.py @@ -14,6 +14,7 @@ # import copy +import datetime import uuid from keystoneauth1 import access @@ -438,6 +439,34 @@ OAUTH_VERIFIER = { 'oauth_verifier': oauth_verifier_pin } +app_cred_id = 'app-cred-id' +app_cred_name = 'testing_app_cred' +app_cred_role = {"id": role_id, "name": role_name, "domain": None}, +app_cred_description = 'app credential for testing' +app_cred_expires = datetime.datetime(2022, 1, 1, 0, 0) +app_cred_expires_str = app_cred_expires.strftime('%Y-%m-%dT%H:%M:%S%z') +app_cred_secret = 'moresecuresecret' +APP_CRED_BASIC = { + 'id': app_cred_id, + 'name': app_cred_name, + 'project_id': project_id, + 'roles': app_cred_role, + 'description': None, + 'expires_at': None, + 'unrestricted': False, + 'secret': app_cred_secret +} +APP_CRED_OPTIONS = { + 'id': app_cred_id, + 'name': app_cred_name, + 'project_id': project_id, + 'roles': app_cred_role, + 'description': app_cred_description, + 'expires_at': app_cred_expires_str, + 'unrestricted': False, + 'secret': app_cred_secret +} + def fake_auth_ref(fake_token, fake_service=None): """Create an auth_ref using keystoneauth's fixtures""" @@ -523,6 +552,9 @@ class FakeIdentityv3Client(object): self.auth = FakeAuth() self.auth.client = mock.Mock() self.auth.client.resource_class = fakes.FakeResource(None, {}) + self.application_credentials = mock.Mock() + self.application_credentials.resource_class = fakes.FakeResource(None, + {}) class FakeFederationManager(object): diff --git a/openstackclient/tests/unit/identity/v3/test_application_credential.py b/openstackclient/tests/unit/identity/v3/test_application_credential.py new file mode 100644 index 00000000..e7c8ede8 --- /dev/null +++ b/openstackclient/tests/unit/identity/v3/test_application_credential.py @@ -0,0 +1,309 @@ +# 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. +# + +import copy + +import mock +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.identity.v3 import application_credential +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes + + +class TestApplicationCredential(identity_fakes.TestIdentityv3): + + def setUp(self): + super(TestApplicationCredential, self).setUp() + + identity_manager = self.app.client_manager.identity + self.app_creds_mock = identity_manager.application_credentials + self.app_creds_mock.reset_mock() + self.roles_mock = identity_manager.roles + self.roles_mock.reset_mock() + + +class TestApplicationCredentialCreate(TestApplicationCredential): + + def setUp(self): + super(TestApplicationCredentialCreate, self).setUp() + + self.roles_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.ROLE), + loaded=True, + ) + + # Get the command object to test + self.cmd = application_credential.CreateApplicationCredential( + self.app, None) + + def test_application_credential_create_basic(self): + self.app_creds_mock.create.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.APP_CRED_BASIC), + loaded=True, + ) + + name = identity_fakes.app_cred_name + arglist = [ + name + ] + verifylist = [ + ('name', identity_fakes.app_cred_name) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class ShowOne in cliff, abstract method take_action() + # returns a two-part tuple with a tuple of column names and a tuple of + # data to be shown. + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'secret': None, + 'roles': [], + 'expires_at': None, + 'description': None, + 'unrestricted': False, + } + self.app_creds_mock.create.assert_called_with( + name, + **kwargs + ) + + collist = ('description', 'expires_at', 'id', 'name', 'project_id', + 'roles', 'secret', 'unrestricted') + self.assertEqual(collist, columns) + datalist = ( + None, + None, + identity_fakes.app_cred_id, + identity_fakes.app_cred_name, + identity_fakes.project_id, + identity_fakes.role_name, + identity_fakes.app_cred_secret, + False, + ) + self.assertEqual(datalist, data) + + def test_application_credential_create_with_options(self): + name = identity_fakes.app_cred_name + self.app_creds_mock.create.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.APP_CRED_OPTIONS), + loaded=True, + ) + + arglist = [ + name, + '--secret', 'moresecuresecret', + '--role', identity_fakes.role_id, + '--expiration', identity_fakes.app_cred_expires_str, + '--description', 'credential for testing' + ] + verifylist = [ + ('name', identity_fakes.app_cred_name), + ('secret', 'moresecuresecret'), + ('role', [identity_fakes.role_id]), + ('expiration', identity_fakes.app_cred_expires_str), + ('description', 'credential for testing') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class ShowOne in cliff, abstract method take_action() + # returns a two-part tuple with a tuple of column names and a tuple of + # data to be shown. + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'secret': 'moresecuresecret', + 'roles': [identity_fakes.role_id], + 'expires_at': identity_fakes.app_cred_expires, + 'description': 'credential for testing', + 'unrestricted': False + } + self.app_creds_mock.create.assert_called_with( + name, + **kwargs + ) + + collist = ('description', 'expires_at', 'id', 'name', 'project_id', + 'roles', 'secret', 'unrestricted') + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.app_cred_description, + identity_fakes.app_cred_expires_str, + identity_fakes.app_cred_id, + identity_fakes.app_cred_name, + identity_fakes.project_id, + identity_fakes.role_name, + identity_fakes.app_cred_secret, + False, + ) + self.assertEqual(datalist, data) + + +class TestApplicationCredentialDelete(TestApplicationCredential): + + def setUp(self): + super(TestApplicationCredentialDelete, self).setUp() + + # This is the return value for utils.find_resource() + self.app_creds_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.APP_CRED_BASIC), + loaded=True, + ) + self.app_creds_mock.delete.return_value = None + + # Get the command object to test + self.cmd = application_credential.DeleteApplicationCredential( + self.app, None) + + def test_application_credential_delete(self): + arglist = [ + identity_fakes.app_cred_id, + ] + verifylist = [ + ('application_credential', [identity_fakes.app_cred_id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.app_creds_mock.delete.assert_called_with( + identity_fakes.app_cred_id, + ) + self.assertIsNone(result) + + @mock.patch.object(utils, 'find_resource') + def test_delete_multi_app_creds_with_exception(self, find_mock): + find_mock.side_effect = [self.app_creds_mock.get.return_value, + exceptions.CommandError] + arglist = [ + identity_fakes.app_cred_id, + 'nonexistent_app_cred', + ] + verifylist = [ + ('application_credential', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 application credentials failed to' + ' delete.', str(e)) + + find_mock.assert_any_call(self.app_creds_mock, + identity_fakes.app_cred_id) + find_mock.assert_any_call(self.app_creds_mock, + 'nonexistent_app_cred') + + self.assertEqual(2, find_mock.call_count) + self.app_creds_mock.delete.assert_called_once_with( + identity_fakes.app_cred_id) + + +class TestApplicationCredentialList(TestApplicationCredential): + + def setUp(self): + super(TestApplicationCredentialList, self).setUp() + + self.app_creds_mock.list.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.APP_CRED_BASIC), + loaded=True, + ), + ] + + # Get the command object to test + self.cmd = application_credential.ListApplicationCredential(self.app, + None) + + def test_application_credential_list(self): + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + self.app_creds_mock.list.assert_called_with(user=None) + + collist = ('ID', 'Name', 'Project ID', 'Description', 'Expires At') + self.assertEqual(collist, columns) + datalist = (( + identity_fakes.app_cred_id, + identity_fakes.app_cred_name, + identity_fakes.project_id, + None, + None + ), ) + self.assertEqual(datalist, tuple(data)) + + +class TestApplicationCredentialShow(TestApplicationCredential): + + def setUp(self): + super(TestApplicationCredentialShow, self).setUp() + + self.app_creds_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.APP_CRED_BASIC), + loaded=True, + ) + + # Get the command object to test + self.cmd = application_credential.ShowApplicationCredential(self.app, + None) + + def test_application_credential_show(self): + arglist = [ + identity_fakes.app_cred_id, + ] + verifylist = [ + ('application_credential', identity_fakes.app_cred_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class ShowOne in cliff, abstract method take_action() + # returns a two-part tuple with a tuple of column names and a tuple of + # data to be shown. + columns, data = self.cmd.take_action(parsed_args) + + self.app_creds_mock.get.assert_called_with(identity_fakes.app_cred_id) + + collist = ('description', 'expires_at', 'id', 'name', 'project_id', + 'roles', 'secret', 'unrestricted') + self.assertEqual(collist, columns) + datalist = ( + None, + None, + identity_fakes.app_cred_id, + identity_fakes.app_cred_name, + identity_fakes.project_id, + identity_fakes.role_name, + identity_fakes.app_cred_secret, + False, + ) + self.assertEqual(datalist, data) |
