From d7d6464c6df1c5649792096e89969c2e79751019 Mon Sep 17 00:00:00 2001 From: Artem Goncharov Date: Thu, 26 Aug 2021 18:19:38 +0200 Subject: [PATCH] Add user group assignment support in identity Add possibility to add/remove/check user into/from group. Change-Id: I1e012471badcf6264dced53354472abd8312f62c --- doc/source/user/proxies/identity_v3.rst | 3 +- openstack/cloud/_identity.py | 21 +----- openstack/identity/v3/_proxy.py | 39 +++++++++++ openstack/identity/v3/group.py | 30 +++++++++ .../tests/unit/identity/v3/test_group.py | 50 ++++++++++++++ .../tests/unit/identity/v3/test_proxy.py | 65 ++++++++++++++++++- ...ser-group-assignment-9c419b6c6bfe392c.yaml | 4 ++ 7 files changed, 192 insertions(+), 20 deletions(-) create mode 100644 releasenotes/notes/add-user-group-assignment-9c419b6c6bfe392c.yaml diff --git a/doc/source/user/proxies/identity_v3.rst b/doc/source/user/proxies/identity_v3.rst index 10892d29b..31dccad94 100644 --- a/doc/source/user/proxies/identity_v3.rst +++ b/doc/source/user/proxies/identity_v3.rst @@ -42,7 +42,8 @@ Group Operations .. autoclass:: openstack.identity.v3._proxy.Proxy :noindex: :members: create_group, update_group, delete_group, get_group, find_group, - groups + groups, add_user_to_group, remove_user_from_group, + check_user_in_group Policy Operations ^^^^^^^^^^^^^^^^^ diff --git a/openstack/cloud/_identity.py b/openstack/cloud/_identity.py index 6999c2c29..ea88711b0 100644 --- a/openstack/cloud/_identity.py +++ b/openstack/cloud/_identity.py @@ -295,11 +295,7 @@ class IdentityCloudMixin(_normalize.Normalizer): """ user, group = self._get_user_and_group(name_or_id, group_name_or_id) - error_msg = "Error adding user {user} to group {group}".format( - user=name_or_id, group=group_name_or_id) - self._identity_client.put( - '/groups/{g}/users/{u}'.format(g=group['id'], u=user['id']), - error_message=error_msg) + self.identity.add_user_to_group(user, group) def is_user_in_group(self, name_or_id, group_name_or_id): """Check to see if a user is in a group. @@ -314,14 +310,7 @@ class IdentityCloudMixin(_normalize.Normalizer): """ user, group = self._get_user_and_group(name_or_id, group_name_or_id) - try: - self._identity_client.head( - '/groups/{g}/users/{u}'.format(g=group['id'], u=user['id'])) - return True - except exc.OpenStackCloudURINotFound: - # NOTE(samueldmq): knowing this URI exists, let's interpret this as - # user not found in group rather than URI not found. - return False + return self.identity.check_user_in_group(user, group) def remove_user_from_group(self, name_or_id, group_name_or_id): """Remove a user from a group. @@ -334,11 +323,7 @@ class IdentityCloudMixin(_normalize.Normalizer): """ user, group = self._get_user_and_group(name_or_id, group_name_or_id) - error_msg = "Error removing user {user} from group {group}".format( - user=name_or_id, group=group_name_or_id) - self._identity_client.delete( - '/groups/{g}/users/{u}'.format(g=group['id'], u=user['id']), - error_message=error_msg) + self.identity.remove_user_from_group(user, group) @_utils.valid_kwargs('type', 'service_type', 'description') def create_service(self, name, enabled=True, **kwargs): diff --git a/openstack/identity/v3/_proxy.py b/openstack/identity/v3/_proxy.py index 58df68b84..282f4b5db 100644 --- a/openstack/identity/v3/_proxy.py +++ b/openstack/identity/v3/_proxy.py @@ -362,6 +362,45 @@ class Proxy(proxy.Proxy): """ return self._update(_group.Group, group, **attrs) + def add_user_to_group(self, user, group): + """Add user to group + + :param user: Either the ID of a user or a + :class:`~openstack.identity.v3.user.User` instance. + :param group: Either the ID of a group or a + :class:`~openstack.identity.v3.group.Group` instance. + :return: ``None`` + """ + user = self._get_resource(_user.User, user) + group = self._get_resource(_group.Group, group) + group.add_user(self, user) + + def remove_user_from_group(self, user, group): + """Remove user to group + + :param user: Either the ID of a user or a + :class:`~openstack.identity.v3.user.User` instance. + :param group: Either the ID of a group or a + :class:`~openstack.identity.v3.group.Group` instance. + :return: ``None`` + """ + user = self._get_resource(_user.User, user) + group = self._get_resource(_group.Group, group) + group.remove_user(self, user) + + def check_user_in_group(self, user, group): + """Check whether user belongsto group + + :param user: Either the ID of a user or a + :class:`~openstack.identity.v3.user.User` instance. + :param group: Either the ID of a group or a + :class:`~openstack.identity.v3.group.Group` instance. + :return: A boolean representing current relation + """ + user = self._get_resource(_user.User, user) + group = self._get_resource(_group.Group, group) + return group.check_user(self, user) + def create_policy(self, **attrs): """Create a new policy from attributes diff --git a/openstack/identity/v3/group.py b/openstack/identity/v3/group.py index 001f5124c..41d876356 100644 --- a/openstack/identity/v3/group.py +++ b/openstack/identity/v3/group.py @@ -10,7 +10,9 @@ # License for the specific language governing permissions and limitations # under the License. +from openstack import exceptions from openstack import resource +from openstack import utils class Group(resource.Resource): @@ -40,3 +42,31 @@ class Group(resource.Resource): domain_id = resource.Body('domain_id') #: Unique group name, within the owning domain. *Type: string* name = resource.Body('name') + + def add_user(self, session, user): + """Add user to the group""" + url = utils.urljoin( + self.base_path, self.id, 'users', user.id) + resp = session.put(url,) + exceptions.raise_from_response(resp) + + def remove_user(self, session, user): + """Remove user from the group""" + url = utils.urljoin( + self.base_path, self.id, 'users', user.id) + resp = session.delete(url,) + exceptions.raise_from_response(resp) + + def check_user(self, session, user): + """Check whether user belongs to group""" + url = utils.urljoin( + self.base_path, self.id, 'users', user.id) + resp = session.head(url,) + if resp.status_code == 404: + # If we recieve 404 - treat this as False, + # rather then returning exception + return False + exceptions.raise_from_response(resp) + if resp.status_code == 204: + return True + return False diff --git a/openstack/tests/unit/identity/v3/test_group.py b/openstack/tests/unit/identity/v3/test_group.py index 8b53636cc..46b17ae58 100644 --- a/openstack/tests/unit/identity/v3/test_group.py +++ b/openstack/tests/unit/identity/v3/test_group.py @@ -10,7 +10,12 @@ # License for the specific language governing permissions and limitations # under the License. +from unittest import mock + +from keystoneauth1 import adapter + from openstack.identity.v3 import group +from openstack.identity.v3 import user from openstack.tests.unit import base @@ -25,6 +30,16 @@ EXAMPLE = { class TestGroup(base.TestCase): + def setUp(self): + super(TestGroup, self).setUp() + self.sess = mock.Mock(spec=adapter.Adapter) + self.sess.default_microversion = 1 + self.sess._get_connection = mock.Mock(return_value=self.cloud) + self.good_resp = mock.Mock() + self.good_resp.body = None + self.good_resp.json = mock.Mock(return_value=self.good_resp.body) + self.good_resp.status_code = 204 + def test_basic(self): sot = group.Group() self.assertEqual('group', sot.resource_key) @@ -52,3 +67,38 @@ class TestGroup(base.TestCase): self.assertEqual(EXAMPLE['domain_id'], sot.domain_id) self.assertEqual(EXAMPLE['id'], sot.id) self.assertEqual(EXAMPLE['name'], sot.name) + + def test_add_user(self): + sot = group.Group(**EXAMPLE) + resp = self.good_resp + self.sess.put = mock.Mock(return_value=resp) + + sot.add_user( + self.sess, user.User(id='1')) + + self.sess.put.assert_called_with( + 'groups/IDENTIFIER/users/1') + + def test_remove_user(self): + sot = group.Group(**EXAMPLE) + resp = self.good_resp + self.sess.delete = mock.Mock(return_value=resp) + + sot.remove_user( + self.sess, user.User(id='1')) + + self.sess.delete.assert_called_with( + 'groups/IDENTIFIER/users/1') + + def test_check_user(self): + sot = group.Group(**EXAMPLE) + resp = self.good_resp + self.sess.head = mock.Mock(return_value=resp) + + self.assertTrue( + sot.check_user( + self.sess, + user.User(id='1'))) + + self.sess.head.assert_called_with( + 'groups/IDENTIFIER/users/1') diff --git a/openstack/tests/unit/identity/v3/test_proxy.py b/openstack/tests/unit/identity/v3/test_proxy.py index 3608cc933..b2af2b570 100644 --- a/openstack/tests/unit/identity/v3/test_proxy.py +++ b/openstack/tests/unit/identity/v3/test_proxy.py @@ -35,7 +35,7 @@ class TestIdentityProxyBase(test_proxy_base.TestProxyBase): self.proxy = _proxy.Proxy(self.session) -class TestIdentityProxy(TestIdentityProxyBase): +class TestIdentityProxyCredential(TestIdentityProxyBase): def test_credential_create_attrs(self): self.verify_create(self.proxy.create_credential, @@ -61,6 +61,9 @@ class TestIdentityProxy(TestIdentityProxyBase): def test_credential_update(self): self.verify_update(self.proxy.update_credential, credential.Credential) + +class TestIdentityProxyDomain(TestIdentityProxyBase): + def test_domain_create_attrs(self): self.verify_create(self.proxy.create_domain, domain.Domain) @@ -82,6 +85,9 @@ class TestIdentityProxy(TestIdentityProxyBase): def test_domain_update(self): self.verify_update(self.proxy.update_domain, domain.Domain) + +class TestIdentityProxyEndpoint(TestIdentityProxyBase): + def test_endpoint_create_attrs(self): self.verify_create(self.proxy.create_endpoint, endpoint.Endpoint) @@ -105,6 +111,9 @@ class TestIdentityProxy(TestIdentityProxyBase): def test_endpoint_update(self): self.verify_update(self.proxy.update_endpoint, endpoint.Endpoint) + +class TestIdentityProxyGroup(TestIdentityProxyBase): + def test_group_create_attrs(self): self.verify_create(self.proxy.create_group, group.Group) @@ -126,6 +135,42 @@ class TestIdentityProxy(TestIdentityProxyBase): def test_group_update(self): self.verify_update(self.proxy.update_group, group.Group) + def test_add_user_to_group(self): + self._verify( + "openstack.identity.v3.group.Group.add_user", + self.proxy.add_user_to_group, + method_args=['uid', 'gid'], + expected_args=[ + self.proxy, + self.proxy._get_resource(user.User, 'uid'), + ] + ) + + def test_remove_user_from_group(self): + self._verify( + "openstack.identity.v3.group.Group.remove_user", + self.proxy.remove_user_from_group, + method_args=['uid', 'gid'], + expected_args=[ + self.proxy, + self.proxy._get_resource(user.User, 'uid'), + ] + ) + + def test_check_user_in_group(self): + self._verify( + "openstack.identity.v3.group.Group.check_user", + self.proxy.check_user_in_group, + method_args=['uid', 'gid'], + expected_args=[ + self.proxy, + self.proxy._get_resource(user.User, 'uid'), + ] + ) + + +class TestIdentityProxyPolicy(TestIdentityProxyBase): + def test_policy_create_attrs(self): self.verify_create(self.proxy.create_policy, policy.Policy) @@ -147,6 +192,9 @@ class TestIdentityProxy(TestIdentityProxyBase): def test_policy_update(self): self.verify_update(self.proxy.update_policy, policy.Policy) + +class TestIdentityProxyProject(TestIdentityProxyBase): + def test_project_create_attrs(self): self.verify_create(self.proxy.create_project, project.Project) @@ -176,6 +224,9 @@ class TestIdentityProxy(TestIdentityProxyBase): def test_project_update(self): self.verify_update(self.proxy.update_project, project.Project) + +class TestIdentityProxyService(TestIdentityProxyBase): + def test_service_create_attrs(self): self.verify_create(self.proxy.create_service, service.Service) @@ -197,6 +248,9 @@ class TestIdentityProxy(TestIdentityProxyBase): def test_service_update(self): self.verify_update(self.proxy.update_service, service.Service) + +class TestIdentityProxyUser(TestIdentityProxyBase): + def test_user_create_attrs(self): self.verify_create(self.proxy.create_user, user.User) @@ -218,6 +272,9 @@ class TestIdentityProxy(TestIdentityProxyBase): def test_user_update(self): self.verify_update(self.proxy.update_user, user.User) + +class TestIdentityProxyTrust(TestIdentityProxyBase): + def test_trust_create_attrs(self): self.verify_create(self.proxy.create_trust, trust.Trust) @@ -236,6 +293,9 @@ class TestIdentityProxy(TestIdentityProxyBase): def test_trusts(self): self.verify_list(self.proxy.trusts, trust.Trust) + +class TestIdentityProxyRegion(TestIdentityProxyBase): + def test_region_create_attrs(self): self.verify_create(self.proxy.create_region, region.Region) @@ -257,6 +317,9 @@ class TestIdentityProxy(TestIdentityProxyBase): def test_region_update(self): self.verify_update(self.proxy.update_region, region.Region) + +class TestIdentityProxyRole(TestIdentityProxyBase): + def test_role_create_attrs(self): self.verify_create(self.proxy.create_role, role.Role) diff --git a/releasenotes/notes/add-user-group-assignment-9c419b6c6bfe392c.yaml b/releasenotes/notes/add-user-group-assignment-9c419b6c6bfe392c.yaml new file mode 100644 index 000000000..266b5f3c4 --- /dev/null +++ b/releasenotes/notes/add-user-group-assignment-9c419b6c6bfe392c.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Add support for user group assignments in identity service.