Merge "Add CRUD support for application credentials"
This commit is contained in:
commit
1e8c9302fc
|
@ -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)
|
|
@ -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.'))
|
|
@ -22,6 +22,7 @@ from keystoneclient.auth.identity import v3 as v3_auth
|
||||||
from keystoneclient import exceptions
|
from keystoneclient import exceptions
|
||||||
from keystoneclient import httpclient
|
from keystoneclient import httpclient
|
||||||
from keystoneclient.i18n import _
|
from keystoneclient.i18n import _
|
||||||
|
from keystoneclient.v3 import application_credentials
|
||||||
from keystoneclient.v3 import auth
|
from keystoneclient.v3 import auth
|
||||||
from keystoneclient.v3.contrib import endpoint_filter
|
from keystoneclient.v3.contrib import endpoint_filter
|
||||||
from keystoneclient.v3.contrib import endpoint_policy
|
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 '
|
'deprecated as of the 1.7.0 release and may be removed in '
|
||||||
'the 2.0.0 release.', DeprecationWarning)
|
'the 2.0.0 release.', DeprecationWarning)
|
||||||
|
|
||||||
|
self.application_credentials = (
|
||||||
|
application_credentials.ApplicationCredentialManager(self._adapter)
|
||||||
|
)
|
||||||
self.auth = auth.AuthManager(self._adapter)
|
self.auth = auth.AuthManager(self._adapter)
|
||||||
self.credentials = credentials.CredentialManager(self._adapter)
|
self.credentials = credentials.CredentialManager(self._adapter)
|
||||||
self.ec2 = ec2.EC2Manager(self._adapter)
|
self.ec2 = ec2.EC2Manager(self._adapter)
|
||||||
|
|
|
@ -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.
|
Loading…
Reference in New Issue