diff --git a/tempest/api/identity/base.py b/tempest/api/identity/base.py index 282343c380..5722f0e73e 100644 --- a/tempest/api/identity/base.py +++ b/tempest/api/identity/base.py @@ -192,6 +192,7 @@ class BaseIdentityV3Test(BaseIdentityTest): cls.os_primary.identity_versions_v3_client cls.non_admin_app_creds_client = \ cls.os_primary.application_credentials_client + cls.non_admin_access_rules_client = cls.os_primary.access_rules_client class BaseIdentityV3AdminTest(BaseIdentityV3Test): diff --git a/tempest/api/identity/v3/test_access_rules.py b/tempest/api/identity/v3/test_access_rules.py new file mode 100644 index 0000000000..608eb5966e --- /dev/null +++ b/tempest/api/identity/v3/test_access_rules.py @@ -0,0 +1,84 @@ +# Copyright 2019 SUSE LLC +# +# All Rights Reserved. +# +# 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 tempest.api.identity import base +from tempest import config +from tempest.lib.common.utils import data_utils +from tempest.lib import decorators +from tempest.lib import exceptions as lib_exc + +CONF = config.CONF + + +class AccessRulesV3Test(base.BaseIdentityV3Test): + + @classmethod + def skip_checks(cls): + super(AccessRulesV3Test, cls).skip_checks() + if not CONF.identity_feature_enabled.access_rules: + raise cls.skipException("Application credential access rules are " + "not available in this environment") + + @classmethod + def resource_setup(cls): + super(AccessRulesV3Test, cls).resource_setup() + cls.user_id = cls.os_primary.credentials.user_id + cls.project_id = cls.os_primary.credentials.project_id + + def setUp(self): + super(AccessRulesV3Test, self).setUp() + ac = self.non_admin_app_creds_client + access_rules = [ + { + "path": "/v2.1/servers/*/ips", + "method": "GET", + "service": "compute" + } + ] + self.app_cred = ac.create_application_credential( + self.user_id, + name=data_utils.rand_name('application_credential'), + access_rules=access_rules + )['application_credential'] + + @decorators.idempotent_id('2354c498-5119-4ba5-9f0d-44f16f78fb0e') + def test_list_access_rules(self): + ar = self.non_admin_access_rules_client.list_access_rules(self.user_id) + self.assertEqual(1, len(ar['access_rules'])) + + @decorators.idempotent_id('795dd507-ca1e-40e9-ba90-ff0a08689ba4') + def test_show_access_rule(self): + access_rule_id = self.app_cred['access_rules'][0]['id'] + self.non_admin_access_rules_client.show_access_rule( + self.user_id, access_rule_id) + + @decorators.idempotent_id('278757e9-e193-4bf8-adf2-0b0a229a17d0') + def test_delete_access_rule(self): + access_rule_id = self.app_cred['access_rules'][0]['id'] + app_cred_id = self.app_cred['id'] + self.assertRaises( + lib_exc.Forbidden, + self.non_admin_access_rules_client.delete_access_rule, + self.user_id, + access_rule_id) + self.non_admin_app_creds_client.delete_application_credential( + self.user_id, app_cred_id) + ar = self.non_admin_access_rules_client.list_access_rules(self.user_id) + self.assertEqual(1, len(ar['access_rules'])) + self.non_admin_access_rules_client.delete_access_rule( + self.user_id, access_rule_id) + ar = self.non_admin_access_rules_client.list_access_rules(self.user_id) + self.assertEqual(0, len(ar['access_rules'])) diff --git a/tempest/api/identity/v3/test_application_credentials.py b/tempest/api/identity/v3/test_application_credentials.py index 1cee902bd0..152e3a6dcb 100644 --- a/tempest/api/identity/v3/test_application_credentials.py +++ b/tempest/api/identity/v3/test_application_credentials.py @@ -19,8 +19,11 @@ import datetime from oslo_utils import timeutils from tempest.api.identity import base +from tempest import config from tempest.lib import decorators +CONF = config.CONF + class ApplicationCredentialsV3Test(base.BaseApplicationCredentialsV3Test): @@ -62,6 +65,24 @@ class ApplicationCredentialsV3Test(base.BaseApplicationCredentialsV3Test): expires_str = expires_at.isoformat() self.assertEqual(expires_str, app_cred['expires_at']) + @decorators.idempotent_id('529936eb-aa5d-463d-9f79-01c113d3b88f') + def test_create_application_credential_access_rules(self): + if not CONF.identity_feature_enabled.access_rules: + raise self.skipException("Application credential access rules are " + "not available in this environment") + access_rules = [ + { + "path": "/v2.1/servers/*/ips", + "method": "GET", + "service": "compute" + } + ] + app_cred = self.create_application_credential( + access_rules=access_rules) + access_rule_resp = app_cred['access_rules'][0] + access_rule_resp.pop('id') + self.assertDictEqual(access_rules[0], access_rule_resp) + @decorators.idempotent_id('ff0cd457-6224-46e7-b79e-0ada4964a8a6') def test_list_application_credentials(self): self.create_application_credential() diff --git a/tempest/clients.py b/tempest/clients.py index 6aed92e8e0..f0ba67e250 100644 --- a/tempest/clients.py +++ b/tempest/clients.py @@ -203,6 +203,8 @@ class Manager(clients.ServiceClients): **params_v3) self.application_credentials_client = \ self.identity_v3.ApplicationCredentialsClient(**params_v3) + self.access_rules_client = \ + self.identity_v3.AccessRulesClient(**params_v3) # Token clients do not use the catalog. They only need default_params. # They read auth_url, so they should only be set if the corresponding diff --git a/tempest/config.py b/tempest/config.py index 32cebc58f3..5087c85352 100644 --- a/tempest/config.py +++ b/tempest/config.py @@ -252,6 +252,11 @@ IdentityFeatureGroup = [ default=False, help='Does the environment have application credentials ' 'enabled?'), + # Access rules for application credentials is a default feature in Train. + # This config option can removed once Stein is EOL. + cfg.BoolOpt('access_rules', + default=False, + help='Does the environment have access rules enabled?'), cfg.BoolOpt('immutable_user_source', default=False, help='Set to True if the environment has a read-only ' diff --git a/tempest/lib/services/identity/v3/__init__.py b/tempest/lib/services/identity/v3/__init__.py index da1c51c4ee..86fa991625 100644 --- a/tempest/lib/services/identity/v3/__init__.py +++ b/tempest/lib/services/identity/v3/__init__.py @@ -12,6 +12,8 @@ # License for the specific language governing permissions and limitations under # the License. +from tempest.lib.services.identity.v3.access_rules_client import \ + AccessRulesClient from tempest.lib.services.identity.v3.application_credentials_client import \ ApplicationCredentialsClient from tempest.lib.services.identity.v3.catalog_client import \ @@ -48,9 +50,10 @@ from tempest.lib.services.identity.v3.trusts_client import TrustsClient from tempest.lib.services.identity.v3.users_client import UsersClient from tempest.lib.services.identity.v3.versions_client import VersionsClient -__all__ = ['ApplicationCredentialsClient', 'CatalogClient', - 'CredentialsClient', 'DomainsClient', 'DomainConfigurationClient', - 'EndPointGroupsClient', 'EndPointsClient', 'EndPointsFilterClient', +__all__ = ['AccessRulesClient', 'ApplicationCredentialsClient', + 'CatalogClient', 'CredentialsClient', 'DomainsClient', + 'DomainConfigurationClient', 'EndPointGroupsClient', + 'EndPointsClient', 'EndPointsFilterClient', 'GroupsClient', 'IdentityClient', 'InheritedRolesClient', 'OAUTHConsumerClient', 'OAUTHTokenClient', 'PoliciesClient', 'ProjectsClient', 'ProjectTagsClient', 'RegionsClient', diff --git a/tempest/lib/services/identity/v3/access_rules_client.py b/tempest/lib/services/identity/v3/access_rules_client.py new file mode 100644 index 0000000000..4f13e479cc --- /dev/null +++ b/tempest/lib/services/identity/v3/access_rules_client.py @@ -0,0 +1,68 @@ +# Copyright 2019 SUSE LLC +# +# All Rights Reserved. +# +# 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. + +""" +https://docs.openstack.org/api-ref/identity/v3/index.html#application-credentials +""" + +from oslo_serialization import jsonutils as json +from six.moves.urllib import parse as urllib + +from tempest.lib.common import rest_client + + +class AccessRulesClient(rest_client.RestClient): + api_version = "v3" + + def show_access_rule(self, user_id, access_rule_id): + """Gets details of an access rule. + + For a full list of available parameters, please refer to the official + API reference: + https://docs.openstack.org/api-ref/identity/v3/index.html#show-access-rule-details + """ + resp, body = self.get('users/%s/access_rules/%s' % + (user_id, access_rule_id)) + self.expected_success(200, resp.status) + body = json.loads(body) + return rest_client.ResponseBody(resp, body) + + def list_access_rules(self, user_id, **params): + """Lists out all of a user's access rules. + + For a full list of available parameters, please refer to the official + API reference: + https://docs.openstack.org/api-ref/identity/v3/index.html#list-access-rules + """ + url = 'users/%s/access_rules' % user_id + if params: + url += '?%s' % urllib.urlencode(params) + resp, body = self.get(url) + self.expected_success(200, resp.status) + body = json.loads(body) + return rest_client.ResponseBody(resp, body) + + def delete_access_rule(self, user_id, access_rule_id): + """Deletes an access rule. + + For a full list of available parameters, please refer to the official + API reference: + https://docs.openstack.org/api-ref/identity/v3/index.html#delete-access-rule + """ + resp, body = self.delete('users/%s/access_rules/%s' % + (user_id, access_rule_id)) + self.expected_success(204, resp.status) + return rest_client.ResponseBody(resp, body) diff --git a/tempest/tests/lib/services/identity/v3/test_access_rules_client.py b/tempest/tests/lib/services/identity/v3/test_access_rules_client.py new file mode 100644 index 0000000000..71c9cde2b5 --- /dev/null +++ b/tempest/tests/lib/services/identity/v3/test_access_rules_client.py @@ -0,0 +1,97 @@ +# 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 tempest.lib.services.identity.v3 import access_rules_client +from tempest.tests.lib import fake_auth_provider +from tempest.tests.lib.services import base + + +class TestAccessRulesClient(base.BaseServiceTest): + FAKE_LIST_ACCESS_RULES = { + "links": { + "self": "https://example.com/identity/v3/users/" + + "3e0716ae/access_rules", + "previous": None, + "next": None + }, + "access_rules": [ + { + "path": "/v2.0/metrics", + "links": { + "self": "https://example.com/identity/v3/access_rules/" + + "07d719df00f349ef8de77d542edf010c" + }, + "id": "07d719df00f349ef8de77d542edf010c", + "service": "monitoring", + "method": "GET" + } + ] + } + + FAKE_ACCESS_RULE_INFO = { + "access_rule": { + "path": "/v2.0/metrics", + "links": { + "self": "https://example.com/identity/v3/access_rules/" + + "07d719df00f349ef8de77d542edf010c" + }, + "id": "07d719df00f349ef8de77d542edf010c", + "service": "monitoring", + "method": "GET" + } + } + + def setUp(self): + super(TestAccessRulesClient, self).setUp() + fake_auth = fake_auth_provider.FakeAuthProvider() + self.client = access_rules_client.AccessRulesClient( + fake_auth, 'identity', 'regionOne') + + def _test_show_access_rule(self, bytes_body=False): + self.check_service_client_function( + self.client.show_access_rule, + 'tempest.lib.common.rest_client.RestClient.get', + self.FAKE_ACCESS_RULE_INFO, + bytes_body, + user_id="123456", + access_rule_id="5499a186") + + def _test_list_access_rules(self, bytes_body=False): + self.check_service_client_function( + self.client.list_access_rules, + 'tempest.lib.common.rest_client.RestClient.get', + self.FAKE_LIST_ACCESS_RULES, + bytes_body, + user_id="123456") + + def test_show_access_rule_with_str_body(self): + self._test_show_access_rule() + + def test_show_access_rule_with_bytes_body(self): + self._test_show_access_rule(bytes_body=True) + + def test_list_access_rule_with_str_body(self): + self._test_list_access_rules() + + def test_list_access_rule_with_bytes_body(self): + self._test_list_access_rules(bytes_body=True) + + def test_delete_access_rule(self): + self.check_service_client_function( + self.client.delete_access_rule, + 'tempest.lib.common.rest_client.RestClient.delete', + {}, + user_id="123456", + access_rule_id="5499a186", + status=204)