diff --git a/docs/gl_objects/users.rst b/docs/gl_objects/users.rst index 3e71ac4e3..5b1cf3dd7 100644 --- a/docs/gl_objects/users.rst +++ b/docs/gl_objects/users.rst @@ -153,6 +153,33 @@ Revoke (delete) an impersonation token for a user:: i_t.delete() + +User memberships +========================= + +References +---------- + +* v4 API: + + + :class:`gitlab.v4.objects.UserMembership` + + :class:`gitlab.v4.objects.UserMembershipManager` + + :attr:`gitlab.v4.objects.User.memberships` + +* GitLab API: https://docs.gitlab.com/ee/api/users.html#user-memberships-admin-only + +List direct memberships for a user:: + + memberships = user.memberships.list() + +List only direct project memberships:: + + memberships = user.memberships.list(type='Project') + +List only direct group memberships:: + + memberships = user.memberships.list(type='Namespace') + Current User ============ diff --git a/gitlab/tests/test_gitlab.py b/gitlab/tests/test_gitlab.py index b56889a91..678c9a214 100644 --- a/gitlab/tests/test_gitlab.py +++ b/gitlab/tests/test_gitlab.py @@ -658,6 +658,38 @@ def test_users(self): self.assertEqual(user.name, "name") self.assertEqual(user.id, 1) + def test_user_memberships(self): + @urlmatch( + scheme="http", + netloc="localhost", + path="/api/v4/users/1/memberships", + method="get", + ) + def resp_get_user_memberships(url, request): + headers = {"content-type": "application/json"} + content = """[ + { + "source_id": 1, + "source_name": "Project one", + "source_type": "Project", + "access_level": "20" + }, + { + "source_id": 3, + "source_name": "Group three", + "source_type": "Namespace", + "access_level": "20" + } + ]""" + content = content.encode("utf-8") + return response(200, content, headers, None, 5, request) + + with HTTMock(resp_get_user_memberships): + user = self.gl.users.get(1, lazy=True) + memberships = user.memberships.list() + self.assertIsInstance(memberships[0], UserMembership) + self.assertEqual(memberships[0].source_type, "Project") + def test_user_status(self): @urlmatch( scheme="http", diff --git a/gitlab/v4/objects.py b/gitlab/v4/objects.py index 83f77d365..92650b1ec 100644 --- a/gitlab/v4/objects.py +++ b/gitlab/v4/objects.py @@ -229,6 +229,17 @@ class UserImpersonationTokenManager(NoUpdateMixin, RESTManager): _list_filters = ("state",) +class UserMembership(RESTObject): + _id_attr = "source_id" + + +class UserMembershipManager(RetrieveMixin, RESTManager): + _path = "/users/%(user_id)s/memberships" + _obj_cls = UserMembership + _from_parent_attrs = {"user_id": "id"} + _list_filters = ("type",) + + class UserProject(RESTObject): pass @@ -311,6 +322,7 @@ class User(SaveMixin, ObjectDeleteMixin, RESTObject): ("gpgkeys", "UserGPGKeyManager"), ("impersonationtokens", "UserImpersonationTokenManager"), ("keys", "UserKeyManager"), + ("memberships", "UserMembershipManager"), ("projects", "UserProjectManager"), ("status", "UserStatusManager"), ) diff --git a/tools/cli_test_v4.sh b/tools/cli_test_v4.sh index b7ed708ed..cf157f436 100755 --- a/tools/cli_test_v4.sh +++ b/tools/cli_test_v4.sh @@ -61,6 +61,10 @@ testcase "adding member to a project" ' --user-id "$USER_ID" --access-level 40 >/dev/null 2>&1 ' +testcase "listing user memberships" ' + GITLAB user-membership list --user-id "$USER_ID" >/dev/null 2>&1 +' + testcase "file creation" ' GITLAB project-file create --project-id "$PROJECT_ID" \ --file-path README --branch master --content "CONTENT" \ diff --git a/tools/python_test_v4.py b/tools/python_test_v4.py index 49f99e5ba..0703ee340 100644 --- a/tools/python_test_v4.py +++ b/tools/python_test_v4.py @@ -266,6 +266,35 @@ group2.members.create({"access_level": gitlab.const.OWNER_ACCESS, "user_id": user2.id}) +# User memberships (admin only) +memberships1 = user1.memberships.list() +assert len(memberships1) == 1 + +memberships2 = user2.memberships.list() +assert len(memberships2) == 2 + +membership = memberships1[0] +assert membership.source_type == "Namespace" +assert membership.access_level == gitlab.const.OWNER_ACCESS + +project_memberships = user1.memberships.list(type="Project") +assert len(project_memberships) == 0 + +group_memberships = user1.memberships.list(type="Namespace") +assert len(group_memberships) == 1 + +try: + membership = user1.memberships.list(type="Invalid") +except gitlab.GitlabListError as e: + error_message = e.error_message +assert error_message == "type does not have a valid value" + +try: + user1.memberships.list(sudo=user1.name) +except gitlab.GitlabListError as e: + error_message = e.error_message +assert error_message == "403 Forbidden" + # Administrator belongs to the groups assert len(group1.members.list()) == 3 assert len(group2.members.list()) == 2