summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZuul <zuul@review.openstack.org>2018-01-24 16:15:12 +0000
committerGerrit Code Review <review@openstack.org>2018-01-24 16:15:12 +0000
commit1e8c9302fc055f78964f3eaef32e09dae89eb2fa (patch)
tree10db247b58db869631e2c7152e0764adffef2936
parent62982077aa53f1924b72fcb827ebcc097f52b001 (diff)
parentd59aaaa25c5ccd505aafbb1857807f6b8816771d (diff)
downloadpython-keystoneclient-1e8c9302fc055f78964f3eaef32e09dae89eb2fa.tar.gz
Merge "Add CRUD support for application credentials"3.15.0
-rw-r--r--keystoneclient/tests/unit/v3/test_application_credentials.py116
-rw-r--r--keystoneclient/v3/application_credentials.py171
-rw-r--r--keystoneclient/v3/client.py4
-rw-r--r--releasenotes/notes/bp-application-credentials-27728ded876d7d5a.yaml8
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.