Skip to content

Commit c313c2b

Browse files
committed
feat: add support for user memberships API (#1009)
1 parent e8f0921 commit c313c2b

File tree

5 files changed

+104
-0
lines changed

5 files changed

+104
-0
lines changed

docs/gl_objects/users.rst

+27
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,33 @@ Revoke (delete) an impersonation token for a user::
153153

154154
i_t.delete()
155155

156+
157+
User memberships
158+
=========================
159+
160+
References
161+
----------
162+
163+
* v4 API:
164+
165+
+ :class:`gitlab.v4.objects.UserMembership`
166+
+ :class:`gitlab.v4.objects.UserMembershipManager`
167+
+ :attr:`gitlab.v4.objects.User.memberships`
168+
169+
* GitLab API: https://docs.gitlab.com/ee/api/users.html#user-memberships-admin-only
170+
171+
List direct memberships for a user::
172+
173+
memberships = user.memberships.list()
174+
175+
List only direct project memberships::
176+
177+
memberships = user.memberships.list(type='Project')
178+
179+
List only direct group memberships::
180+
181+
memberships = user.memberships.list('Namespace')
182+
156183
Current User
157184
============
158185

gitlab/tests/test_gitlab.py

+32
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,38 @@ def test_users(self):
658658
self.assertEqual(user.name, "name")
659659
self.assertEqual(user.id, 1)
660660

661+
def test_user_memberships(self):
662+
@urlmatch(
663+
scheme="http",
664+
netloc="localhost",
665+
path="/api/v4/users/1/memberships",
666+
method="get",
667+
)
668+
def resp_get_user_memberships(url, request):
669+
headers = {"content-type": "application/json"}
670+
content = """[
671+
{
672+
"source_id": 1,
673+
"source_name": "Project one",
674+
"source_type": "Project",
675+
"access_level": "20"
676+
},
677+
{
678+
"source_id": 3,
679+
"source_name": "Group three",
680+
"source_type": "Namespace",
681+
"access_level": "20"
682+
}
683+
]"""
684+
content = content.encode("utf-8")
685+
return response(200, content, headers, None, 5, request)
686+
687+
with HTTMock(resp_get_user_memberships):
688+
user = self.gl.users.get(1, lazy=True)
689+
memberships = user.memberships.list()
690+
self.assertIsInstance(memberships[0], UserMembership)
691+
self.assertEqual(memberships[0].source_type, "Project")
692+
661693
def test_user_status(self):
662694
@urlmatch(
663695
scheme="http",

gitlab/v4/objects.py

+12
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,17 @@ class UserImpersonationTokenManager(NoUpdateMixin, RESTManager):
229229
_list_filters = ("state",)
230230

231231

232+
class UserMembership(RESTObject):
233+
_id_attr = "source_id"
234+
235+
236+
class UserMembershipManager(RetrieveMixin, RESTManager):
237+
_path = "/users/%(user_id)s/memberships"
238+
_obj_cls = UserMembership
239+
_from_parent_attrs = {"user_id": "id"}
240+
_list_filters = ("type",)
241+
242+
232243
class UserProject(RESTObject):
233244
pass
234245

@@ -311,6 +322,7 @@ class User(SaveMixin, ObjectDeleteMixin, RESTObject):
311322
("gpgkeys", "UserGPGKeyManager"),
312323
("impersonationtokens", "UserImpersonationTokenManager"),
313324
("keys", "UserKeyManager"),
325+
("memberships", "UserMembershipManager"),
314326
("projects", "UserProjectManager"),
315327
("status", "UserStatusManager"),
316328
)

tools/cli_test_v4.sh

+4
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ testcase "adding member to a project" '
6161
--user-id "$USER_ID" --access-level 40 >/dev/null 2>&1
6262
'
6363

64+
testcase "listing user memberships" '
65+
GITLAB user-membership list --user-id "$USER_ID" >/dev/null 2>&1
66+
'
67+
6468
testcase "file creation" '
6569
GITLAB project-file create --project-id "$PROJECT_ID" \
6670
--file-path README --branch master --content "CONTENT" \

tools/python_test_v4.py

+29
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,35 @@
266266

267267
group2.members.create({"access_level": gitlab.const.OWNER_ACCESS, "user_id": user2.id})
268268

269+
# User memberships (admin only)
270+
memberships1 = user1.memberships.list()
271+
assert len(memberships1) == 1
272+
273+
memberships2 = user2.memberships.list()
274+
assert len(memberships2) == 2
275+
276+
membership = memberships1[0]
277+
assert membership.source_type == "Namespace"
278+
assert membership.access_level == gitlab.const.OWNER_ACCESS
279+
280+
project_memberships = user1.memberships.list(type="Project")
281+
assert len(project_memberships) == 0
282+
283+
group_memberships = user1.memberships.list(type="Namespace")
284+
assert len(group_memberships) == 1
285+
286+
try:
287+
membership = user1.memberships.list(type="Invalid")
288+
except gitlab.GitlabListError as e:
289+
error_message = e.error_message
290+
assert error_message == "type does not have a valid value"
291+
292+
try:
293+
user1.memberships.list(sudo=user1.name)
294+
except gitlab.GitlabListError as e:
295+
error_message = e.error_message
296+
assert error_message == "403 Forbidden"
297+
269298
# Administrator belongs to the groups
270299
assert len(group1.members.list()) == 3
271300
assert len(group2.members.list()) == 2

0 commit comments

Comments
 (0)