Add user group assignment support in identity

Add possibility to add/remove/check user into/from group.

Change-Id: I1e012471badcf6264dced53354472abd8312f62c
This commit is contained in:
Artem Goncharov 2021-08-26 18:19:38 +02:00
parent 21795acff6
commit d7d6464c6d
7 changed files with 192 additions and 20 deletions

View File

@ -42,7 +42,8 @@ Group Operations
.. autoclass:: openstack.identity.v3._proxy.Proxy .. autoclass:: openstack.identity.v3._proxy.Proxy
:noindex: :noindex:
:members: create_group, update_group, delete_group, get_group, find_group, :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 Policy Operations
^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^

View File

@ -295,11 +295,7 @@ class IdentityCloudMixin(_normalize.Normalizer):
""" """
user, group = self._get_user_and_group(name_or_id, group_name_or_id) user, group = self._get_user_and_group(name_or_id, group_name_or_id)
error_msg = "Error adding user {user} to group {group}".format( self.identity.add_user_to_group(user, group)
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)
def is_user_in_group(self, name_or_id, group_name_or_id): def is_user_in_group(self, name_or_id, group_name_or_id):
"""Check to see if a user is in a group. """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) user, group = self._get_user_and_group(name_or_id, group_name_or_id)
try: return self.identity.check_user_in_group(user, group)
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
def remove_user_from_group(self, name_or_id, group_name_or_id): def remove_user_from_group(self, name_or_id, group_name_or_id):
"""Remove a user from a group. """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) user, group = self._get_user_and_group(name_or_id, group_name_or_id)
error_msg = "Error removing user {user} from group {group}".format( self.identity.remove_user_from_group(user, group)
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)
@_utils.valid_kwargs('type', 'service_type', 'description') @_utils.valid_kwargs('type', 'service_type', 'description')
def create_service(self, name, enabled=True, **kwargs): def create_service(self, name, enabled=True, **kwargs):

View File

@ -362,6 +362,45 @@ class Proxy(proxy.Proxy):
""" """
return self._update(_group.Group, group, **attrs) 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): def create_policy(self, **attrs):
"""Create a new policy from attributes """Create a new policy from attributes

View File

@ -10,7 +10,9 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from openstack import exceptions
from openstack import resource from openstack import resource
from openstack import utils
class Group(resource.Resource): class Group(resource.Resource):
@ -40,3 +42,31 @@ class Group(resource.Resource):
domain_id = resource.Body('domain_id') domain_id = resource.Body('domain_id')
#: Unique group name, within the owning domain. *Type: string* #: Unique group name, within the owning domain. *Type: string*
name = resource.Body('name') 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

View File

@ -10,7 +10,12 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from unittest import mock
from keystoneauth1 import adapter
from openstack.identity.v3 import group from openstack.identity.v3 import group
from openstack.identity.v3 import user
from openstack.tests.unit import base from openstack.tests.unit import base
@ -25,6 +30,16 @@ EXAMPLE = {
class TestGroup(base.TestCase): 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): def test_basic(self):
sot = group.Group() sot = group.Group()
self.assertEqual('group', sot.resource_key) 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['domain_id'], sot.domain_id)
self.assertEqual(EXAMPLE['id'], sot.id) self.assertEqual(EXAMPLE['id'], sot.id)
self.assertEqual(EXAMPLE['name'], sot.name) 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')

View File

@ -35,7 +35,7 @@ class TestIdentityProxyBase(test_proxy_base.TestProxyBase):
self.proxy = _proxy.Proxy(self.session) self.proxy = _proxy.Proxy(self.session)
class TestIdentityProxy(TestIdentityProxyBase): class TestIdentityProxyCredential(TestIdentityProxyBase):
def test_credential_create_attrs(self): def test_credential_create_attrs(self):
self.verify_create(self.proxy.create_credential, self.verify_create(self.proxy.create_credential,
@ -61,6 +61,9 @@ class TestIdentityProxy(TestIdentityProxyBase):
def test_credential_update(self): def test_credential_update(self):
self.verify_update(self.proxy.update_credential, credential.Credential) self.verify_update(self.proxy.update_credential, credential.Credential)
class TestIdentityProxyDomain(TestIdentityProxyBase):
def test_domain_create_attrs(self): def test_domain_create_attrs(self):
self.verify_create(self.proxy.create_domain, domain.Domain) self.verify_create(self.proxy.create_domain, domain.Domain)
@ -82,6 +85,9 @@ class TestIdentityProxy(TestIdentityProxyBase):
def test_domain_update(self): def test_domain_update(self):
self.verify_update(self.proxy.update_domain, domain.Domain) self.verify_update(self.proxy.update_domain, domain.Domain)
class TestIdentityProxyEndpoint(TestIdentityProxyBase):
def test_endpoint_create_attrs(self): def test_endpoint_create_attrs(self):
self.verify_create(self.proxy.create_endpoint, endpoint.Endpoint) self.verify_create(self.proxy.create_endpoint, endpoint.Endpoint)
@ -105,6 +111,9 @@ class TestIdentityProxy(TestIdentityProxyBase):
def test_endpoint_update(self): def test_endpoint_update(self):
self.verify_update(self.proxy.update_endpoint, endpoint.Endpoint) self.verify_update(self.proxy.update_endpoint, endpoint.Endpoint)
class TestIdentityProxyGroup(TestIdentityProxyBase):
def test_group_create_attrs(self): def test_group_create_attrs(self):
self.verify_create(self.proxy.create_group, group.Group) self.verify_create(self.proxy.create_group, group.Group)
@ -126,6 +135,42 @@ class TestIdentityProxy(TestIdentityProxyBase):
def test_group_update(self): def test_group_update(self):
self.verify_update(self.proxy.update_group, group.Group) 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): def test_policy_create_attrs(self):
self.verify_create(self.proxy.create_policy, policy.Policy) self.verify_create(self.proxy.create_policy, policy.Policy)
@ -147,6 +192,9 @@ class TestIdentityProxy(TestIdentityProxyBase):
def test_policy_update(self): def test_policy_update(self):
self.verify_update(self.proxy.update_policy, policy.Policy) self.verify_update(self.proxy.update_policy, policy.Policy)
class TestIdentityProxyProject(TestIdentityProxyBase):
def test_project_create_attrs(self): def test_project_create_attrs(self):
self.verify_create(self.proxy.create_project, project.Project) self.verify_create(self.proxy.create_project, project.Project)
@ -176,6 +224,9 @@ class TestIdentityProxy(TestIdentityProxyBase):
def test_project_update(self): def test_project_update(self):
self.verify_update(self.proxy.update_project, project.Project) self.verify_update(self.proxy.update_project, project.Project)
class TestIdentityProxyService(TestIdentityProxyBase):
def test_service_create_attrs(self): def test_service_create_attrs(self):
self.verify_create(self.proxy.create_service, service.Service) self.verify_create(self.proxy.create_service, service.Service)
@ -197,6 +248,9 @@ class TestIdentityProxy(TestIdentityProxyBase):
def test_service_update(self): def test_service_update(self):
self.verify_update(self.proxy.update_service, service.Service) self.verify_update(self.proxy.update_service, service.Service)
class TestIdentityProxyUser(TestIdentityProxyBase):
def test_user_create_attrs(self): def test_user_create_attrs(self):
self.verify_create(self.proxy.create_user, user.User) self.verify_create(self.proxy.create_user, user.User)
@ -218,6 +272,9 @@ class TestIdentityProxy(TestIdentityProxyBase):
def test_user_update(self): def test_user_update(self):
self.verify_update(self.proxy.update_user, user.User) self.verify_update(self.proxy.update_user, user.User)
class TestIdentityProxyTrust(TestIdentityProxyBase):
def test_trust_create_attrs(self): def test_trust_create_attrs(self):
self.verify_create(self.proxy.create_trust, trust.Trust) self.verify_create(self.proxy.create_trust, trust.Trust)
@ -236,6 +293,9 @@ class TestIdentityProxy(TestIdentityProxyBase):
def test_trusts(self): def test_trusts(self):
self.verify_list(self.proxy.trusts, trust.Trust) self.verify_list(self.proxy.trusts, trust.Trust)
class TestIdentityProxyRegion(TestIdentityProxyBase):
def test_region_create_attrs(self): def test_region_create_attrs(self):
self.verify_create(self.proxy.create_region, region.Region) self.verify_create(self.proxy.create_region, region.Region)
@ -257,6 +317,9 @@ class TestIdentityProxy(TestIdentityProxyBase):
def test_region_update(self): def test_region_update(self):
self.verify_update(self.proxy.update_region, region.Region) self.verify_update(self.proxy.update_region, region.Region)
class TestIdentityProxyRole(TestIdentityProxyBase):
def test_role_create_attrs(self): def test_role_create_attrs(self):
self.verify_create(self.proxy.create_role, role.Role) self.verify_create(self.proxy.create_role, role.Role)

View File

@ -0,0 +1,4 @@
---
features:
- |
Add support for user group assignments in identity service.