diff --git a/keystoneclient/tests/unit/v3/test_access_rules.py b/keystoneclient/tests/unit/v3/test_access_rules.py new file mode 100644 index 000000000..d3e22f8dd --- /dev/null +++ b/keystoneclient/tests/unit/v3/test_access_rules.py @@ -0,0 +1,41 @@ +# 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 keystoneclient import exceptions +from keystoneclient.tests.unit.v3 import utils +from keystoneclient.v3 import access_rules + + +class AccessRuleTests(utils.ClientTestCase, utils.CrudTests): + def setUp(self): + super(AccessRuleTests, self).setUp() + self.key = 'access_rule' + self.collection_key = 'access_rules' + self.model = access_rules.AccessRule + self.manager = self.client.access_rules + self.path_prefix = 'users/%s' % self.TEST_USER_ID + + def new_ref(self, **kwargs): + kwargs = super(AccessRuleTests, self).new_ref(**kwargs) + kwargs.setdefault('path', uuid.uuid4().hex) + kwargs.setdefault('method', uuid.uuid4().hex) + kwargs.setdefault('service', uuid.uuid4().hex) + return kwargs + + def test_update(self): + self.assertRaises(exceptions.MethodNotImplemented, self.manager.update) + + def test_create(self): + self.assertRaises(exceptions.MethodNotImplemented, self.manager.create) diff --git a/keystoneclient/tests/unit/v3/test_application_credentials.py b/keystoneclient/tests/unit/v3/test_application_credentials.py index be3c62ac0..6e4bba3e6 100644 --- a/keystoneclient/tests/unit/v3/test_application_credentials.py +++ b/keystoneclient/tests/unit/v3/test_application_credentials.py @@ -98,6 +98,27 @@ class ApplicationCredentialTests(utils.ClientTestCase, utils.CrudTests): super(ApplicationCredentialTests, self).test_create(ref=ref, req_ref=req_ref) + def test_create_with_access_rules(self): + ref = self.new_ref(user=uuid.uuid4().hex) + access_rules = [ + { + 'method': 'GET', + 'path': '/v3/projects', + 'service': 'identity' + } + ] + ref['access_rules'] = access_rules + 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) diff --git a/keystoneclient/v3/access_rules.py b/keystoneclient/v3/access_rules.py new file mode 100644 index 000000000..78fd0159e --- /dev/null +++ b/keystoneclient/v3/access_rules.py @@ -0,0 +1,118 @@ +# Copyright 2019 SUSE LLC +# +# 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 keystoneclient import base +from keystoneclient import exceptions +from keystoneclient.i18n import _ + + +class AccessRule(base.Resource): + """Represents an Identity access rule for application credentials. + + Attributes: + * id: a uuid that identifies the access rule + * method: The request method that the application credential is + permitted to use for a given API endpoint + * path: The API path that the application credential is permitted to + access + * service: The service type identifier for the service that the + application credential is permitted to access + + """ + + pass + + +class AccessRuleManager(base.CrudManager): + """Manager class for manipulating Identity access rules.""" + + resource_class = AccessRule + collection_key = 'access_rules' + key = 'access_rule' + + def get(self, access_rule, user=None): + """Retrieve an access rule. + + :param access_rule: the access rule to be retrieved from the + server + :type access_rule: str or + :class:`keystoneclient.v3.access_rules.AccessRule` + :param string user: User ID + + :returns: the specified access rule + :rtype: + :class:`keystoneclient.v3.access_rules.AccessRule` + + """ + user = user or self.client.user_id + self.base_url = '/users/%(user)s' % {'user': user} + + return super(AccessRuleManager, self).get( + access_rule_id=base.getid(access_rule)) + + def list(self, user=None, **kwargs): + """List access rules. + + :param string user: User ID + + :returns: a list of access rules + :rtype: list of + :class:`keystoneclient.v3.access_rules.AccessRule` + """ + user = user or self.client.user_id + self.base_url = '/users/%(user)s' % {'user': user} + + return super(AccessRuleManager, self).list(**kwargs) + + def find(self, user=None, **kwargs): + """Find an access rule with attributes matching ``**kwargs``. + + :param string user: User ID + + :returns: a list of matching access rules + :rtype: list of + :class:`keystoneclient.v3.access_rules.AccessRule` + """ + user = user or self.client.user_id + self.base_url = '/users/%(user)s' % {'user': user} + + return super(AccessRuleManager, self).find(**kwargs) + + def delete(self, access_rule, user=None): + """Delete an access rule. + + :param access_rule: the access rule to be deleted + :type access_rule: str or + :class:`keystoneclient.v3.access_rules.AccessRule` + :param string user: User ID + + :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(AccessRuleManager, self).delete( + access_rule_id=base.getid(access_rule)) + + def update(self): + raise exceptions.MethodNotImplemented( + _('Access rules are immutable, updating is not' + ' supported.')) + + def create(self): + raise exceptions.MethodNotImplemented( + _('Access rules can only be created as attributes of application ' + 'credentials.')) diff --git a/keystoneclient/v3/application_credentials.py b/keystoneclient/v3/application_credentials.py index 0fc94aff7..694ee8cd1 100644 --- a/keystoneclient/v3/application_credentials.py +++ b/keystoneclient/v3/application_credentials.py @@ -33,6 +33,8 @@ class ApplicationCredential(base.Resource): * roles: role assignments on the project * unrestricted: whether the application credential has restrictions applied + * access_rules: a list of access rules defining what API requests the + application credential may be used for """ @@ -48,7 +50,7 @@ class ApplicationCredentialManager(base.CrudManager): def create(self, name, user=None, secret=None, description=None, expires_at=None, roles=None, - unrestricted=False, **kwargs): + unrestricted=False, access_rules=None, **kwargs): """Create a credential. :param string name: application credential name @@ -60,6 +62,7 @@ class ApplicationCredentialManager(base.CrudManager): or a list of dicts specifying role name and domain :param bool unrestricted: whether the application credential has restrictions applied + :param List access_rules: a list of dicts representing access rules :returns: the created application credential :rtype: @@ -99,6 +102,7 @@ class ApplicationCredentialManager(base.CrudManager): expires_at=expires_str, roles=role_list, unrestricted=unrestricted, + access_rules=access_rules, **kwargs) def get(self, application_credential, user=None): diff --git a/keystoneclient/v3/client.py b/keystoneclient/v3/client.py index 89ba5ac1b..e99b06549 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 access_rules from keystoneclient.v3 import application_credentials from keystoneclient.v3 import auth from keystoneclient.v3.contrib import endpoint_filter @@ -223,6 +224,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.access_rules = ( + access_rules.AccessRuleManager(self._adapter) + ) self.application_credentials = ( application_credentials.ApplicationCredentialManager(self._adapter) ) diff --git a/releasenotes/notes/bp-whitelist-extension-for-app-creds-d03526e52e3edcce.yaml b/releasenotes/notes/bp-whitelist-extension-for-app-creds-d03526e52e3edcce.yaml new file mode 100644 index 000000000..9c7dc2bf1 --- /dev/null +++ b/releasenotes/notes/bp-whitelist-extension-for-app-creds-d03526e52e3edcce.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Adds support for creating access rules as an attribute of application + credentials as well as for retrieving and deleting them.