Skip to content

Commit f071390

Browse files
authored
Merge pull request #1026 from nejch/feat/user-memberships
feat: add support for user memberships API (#1009)
2 parents 292dfff + 33889bc commit f071390

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(type='Namespace')
182+
156183
Current User
157184
============
158185

gitlab/tests/test_gitlab.py

+32
Original file line numberDiff line numberDiff line change
@@ -675,6 +675,38 @@ def test_users(self):
675675
self.assertEqual(user.name, "name")
676676
self.assertEqual(user.id, 1)
677677

678+
def test_user_memberships(self):
679+
@urlmatch(
680+
scheme="http",
681+
netloc="localhost",
682+
path="/api/v4/users/1/memberships",
683+
method="get",
684+
)
685+
def resp_get_user_memberships(url, request):
686+
headers = {"content-type": "application/json"}
687+
content = """[
688+
{
689+
"source_id": 1,
690+
"source_name": "Project one",
691+
"source_type": "Project",
692+
"access_level": "20"
693+
},
694+
{
695+
"source_id": 3,
696+
"source_name": "Group three",
697+
"source_type": "Namespace",
698+
"access_level": "20"
699+
}
700+
]"""
701+
content = content.encode("utf-8")
702+
return response(200, content, headers, None, 5, request)
703+
704+
with HTTMock(resp_get_user_memberships):
705+
user = self.gl.users.get(1, lazy=True)
706+
memberships = user.memberships.list()
707+
self.assertIsInstance(memberships[0], UserMembership)
708+
self.assertEqual(memberships[0].source_type, "Project")
709+
678710
def test_user_status(self):
679711
@urlmatch(
680712
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)