diff options
author | Zuul <zuul@review.openstack.org> | 2018-01-24 16:15:12 +0000 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2018-01-24 16:15:12 +0000 |
commit | 1e8c9302fc055f78964f3eaef32e09dae89eb2fa (patch) | |
tree | 10db247b58db869631e2c7152e0764adffef2936 | |
parent | 62982077aa53f1924b72fcb827ebcc097f52b001 (diff) | |
parent | d59aaaa25c5ccd505aafbb1857807f6b8816771d (diff) | |
download | python-keystoneclient-1e8c9302fc055f78964f3eaef32e09dae89eb2fa.tar.gz |
Merge "Add CRUD support for application credentials"3.15.0
-rw-r--r-- | keystoneclient/tests/unit/v3/test_application_credentials.py | 116 | ||||
-rw-r--r-- | keystoneclient/v3/application_credentials.py | 171 | ||||
-rw-r--r-- | keystoneclient/v3/client.py | 4 | ||||
-rw-r--r-- | releasenotes/notes/bp-application-credentials-27728ded876d7d5a.yaml | 8 |
4 files changed, 299 insertions, 0 deletions
diff --git a/keystoneclient/tests/unit/v3/test_application_credentials.py b/keystoneclient/tests/unit/v3/test_application_credentials.py new file mode 100644 index 0000000..be3c62a --- /dev/null +++ b/keystoneclient/tests/unit/v3/test_application_credentials.py @@ -0,0 +1,116 @@ +# 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 uuid + +from oslo_utils import timeutils + +from keystoneclient import exceptions +from keystoneclient.tests.unit.v3 import utils +from keystoneclient.v3 import application_credentials + + +class ApplicationCredentialTests(utils.ClientTestCase, utils.CrudTests): + def setUp(self): + super(ApplicationCredentialTests, self).setUp() + self.key = 'application_credential' + self.collection_key = 'application_credentials' + self.model = application_credentials.ApplicationCredential + self.manager = self.client.application_credentials + self.path_prefix = 'users/%s' % self.TEST_USER_ID + + def new_ref(self, **kwargs): + kwargs = super(ApplicationCredentialTests, self).new_ref(**kwargs) + kwargs.setdefault('name', uuid.uuid4().hex) + kwargs.setdefault('description', uuid.uuid4().hex) + kwargs.setdefault('unrestricted', False) + return kwargs + + def test_create_with_roles(self): + ref = self.new_ref(user=uuid.uuid4().hex) + ref['roles'] = [{'name': 'atestrole'}] + req_ref = ref.copy() + req_ref.pop('id') + user = req_ref.pop('user') + + self.stub_entity('POST', + ['users', user, self.collection_key], + status_code=201, entity=req_ref) + + super(ApplicationCredentialTests, self).test_create(ref=ref, + req_ref=req_ref) + + def test_create_with_role_id_and_names(self): + ref = self.new_ref(user=uuid.uuid4().hex) + ref['roles'] = [{'name': 'atestrole', 'domain': 'nondefault'}, + uuid.uuid4().hex] + req_ref = ref.copy() + req_ref.pop('id') + user = req_ref.pop('user') + + req_ref['roles'] = [{'name': 'atestrole', 'domain': 'nondefault'}, + {'id': ref['roles'][1]}] + self.stub_entity('POST', + ['users', user, self.collection_key], + status_code=201, entity=req_ref) + + super(ApplicationCredentialTests, self).test_create(ref=ref, + req_ref=req_ref) + + def test_create_expires(self): + ref = self.new_ref(user=uuid.uuid4().hex) + ref['expires_at'] = timeutils.parse_isotime( + '2013-03-04T12:00:01.000000Z') + req_ref = ref.copy() + req_ref.pop('id') + user = req_ref.pop('user') + + req_ref['expires_at'] = '2013-03-04T12:00:01.000000Z' + + self.stub_entity('POST', + ['users', user, self.collection_key], + status_code=201, entity=req_ref) + + super(ApplicationCredentialTests, self).test_create(ref=ref, + req_ref=req_ref) + + def test_create_unrestricted(self): + ref = self.new_ref(user=uuid.uuid4().hex) + ref['unrestricted'] = True + req_ref = ref.copy() + req_ref.pop('id') + user = req_ref.pop('user') + + self.stub_entity('POST', + ['users', user, self.collection_key], + status_code=201, entity=req_ref) + + super(ApplicationCredentialTests, self).test_create(ref=ref, + req_ref=req_ref) + + def test_get(self): + ref = self.new_ref(user=uuid.uuid4().hex) + + self.stub_entity( + 'GET', ['users', ref['user'], self.collection_key, ref['id']], + entity=ref) + returned = self.manager.get(ref['id'], ref['user']) + self.assertIsInstance(returned, self.model) + for attr in ref: + self.assertEqual( + getattr(returned, attr), + ref[attr], + 'Expected different %s' % attr) + + def test_update(self): + self.assertRaises(exceptions.MethodNotImplemented, self.manager.update) diff --git a/keystoneclient/v3/application_credentials.py b/keystoneclient/v3/application_credentials.py new file mode 100644 index 0000000..0fc94af --- /dev/null +++ b/keystoneclient/v3/application_credentials.py @@ -0,0 +1,171 @@ +# 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 six + +from keystoneclient import base +from keystoneclient import exceptions +from keystoneclient.i18n import _ +from keystoneclient import utils + + +class ApplicationCredential(base.Resource): + """Represents an Identity application credential. + + Attributes: + * id: a uuid that identifies the application credential + * user: the user who owns the application credential + * name: application credential name + * secret: application credential secret + * description: application credential description + * expires_at: expiry time + * roles: role assignments on the project + * unrestricted: whether the application credential has restrictions + applied + + """ + + pass + + +class ApplicationCredentialManager(base.CrudManager): + """Manager class for manipulating Identity application credentials.""" + + resource_class = ApplicationCredential + collection_key = 'application_credentials' + key = 'application_credential' + + def create(self, name, user=None, secret=None, description=None, + expires_at=None, roles=None, + unrestricted=False, **kwargs): + """Create a credential. + + :param string name: application credential name + :param string user: User ID + :param secret: application credential secret + :param description: application credential description + :param datetime.datetime expires_at: expiry time + :param List roles: list of roles on the project. Maybe a list of IDs + or a list of dicts specifying role name and domain + :param bool unrestricted: whether the application credential has + restrictions applied + + :returns: the created application credential + :rtype: + :class:`keystoneclient.v3.application_credentials.ApplicationCredential` + + """ + user = user or self.client.user_id + self.base_url = '/users/%(user)s' % {'user': user} + + # Convert roles list into list-of-dict API format + role_list = [] + if roles: + if not isinstance(roles, list): + roles = [roles] + for role in roles: + if isinstance(role, six.string_types): + role_list.extend([{'id': role}]) + elif isinstance(role, dict): + role_list.extend([role]) + else: + msg = (_("Roles must be a list of IDs or role dicts.")) + raise exceptions.CommandError(msg) + + if not role_list: + role_list = None + + # Convert datetime.datetime expires_at to iso format string + if expires_at: + expires_str = utils.isotime(at=expires_at, subsecond=True) + else: + expires_str = None + + return super(ApplicationCredentialManager, self).create( + name=name, + secret=secret, + description=description, + expires_at=expires_str, + roles=role_list, + unrestricted=unrestricted, + **kwargs) + + def get(self, application_credential, user=None): + """Retrieve an application credential. + + :param application_credential: the credential to be retrieved from the + server + :type applicationcredential: str or + :class:`keystoneclient.v3.application_credentials.ApplicationCredential` + + :returns: the specified application credential + :rtype: + :class:`keystoneclient.v3.application_credentials.ApplicationCredential` + + """ + user = user or self.client.user_id + self.base_url = '/users/%(user)s' % {'user': user} + + return super(ApplicationCredentialManager, self).get( + application_credential_id=base.getid(application_credential)) + + def list(self, user=None, **kwargs): + """List application credentials. + + :param string user: User ID + + :returns: a list of application credentials + :rtype: list of + :class:`keystoneclient.v3.application_credentials.ApplicationCredential` + """ + user = user or self.client.user_id + self.base_url = '/users/%(user)s' % {'user': user} + + return super(ApplicationCredentialManager, self).list(**kwargs) + + def find(self, user=None, **kwargs): + """Find an application credential with attributes matching ``**kwargs``. + + :param string user: User ID + + :returns: a list of matching application credentials + :rtype: list of + :class:`keystoneclient.v3.application_credentials.ApplicationCredential` + """ + user = user or self.client.user_id + self.base_url = '/users/%(user)s' % {'user': user} + + return super(ApplicationCredentialManager, self).find(**kwargs) + + def delete(self, application_credential, user=None): + """Delete an application credential. + + :param application_credential: the application credential to be deleted + :type credential: str or + :class:`keystoneclient.v3.application_credentials.ApplicationCredential` + + :returns: response object with 204 status + :rtype: :class:`requests.models.Response` + + """ + user = user or self.client.user_id + self.base_url = '/users/%(user)s' % {'user': user} + + return super(ApplicationCredentialManager, self).delete( + application_credential_id=base.getid(application_credential)) + + def update(self): + raise exceptions.MethodNotImplemented( + _('Application credentials are immutable, updating is not' + ' supported.')) diff --git a/keystoneclient/v3/client.py b/keystoneclient/v3/client.py index 2ca180a..e57e6bf 100644 --- a/keystoneclient/v3/client.py +++ b/keystoneclient/v3/client.py @@ -22,6 +22,7 @@ from keystoneclient.auth.identity import v3 as v3_auth from keystoneclient import exceptions from keystoneclient import httpclient from keystoneclient.i18n import _ +from keystoneclient.v3 import application_credentials from keystoneclient.v3 import auth from keystoneclient.v3.contrib import endpoint_filter from keystoneclient.v3.contrib import endpoint_policy @@ -212,6 +213,9 @@ class Client(httpclient.HTTPClient): 'deprecated as of the 1.7.0 release and may be removed in ' 'the 2.0.0 release.', DeprecationWarning) + self.application_credentials = ( + application_credentials.ApplicationCredentialManager(self._adapter) + ) self.auth = auth.AuthManager(self._adapter) self.credentials = credentials.CredentialManager(self._adapter) self.ec2 = ec2.EC2Manager(self._adapter) diff --git a/releasenotes/notes/bp-application-credentials-27728ded876d7d5a.yaml b/releasenotes/notes/bp-application-credentials-27728ded876d7d5a.yaml new file mode 100644 index 0000000..c67357c --- /dev/null +++ b/releasenotes/notes/bp-application-credentials-27728ded876d7d5a.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Adds support for creating, reading, and deleting application credentials. + With application credentials, a user can grant their applications limited + access to their cloud resources. Applications can use keystoneauth with + the `v3applicationcredential` auth plugin to authenticate with keystone + without needing the user's password. |