Skip to content

Commit b0b82bc

Browse files
flynn-eyeclaude
andcommitted
feat(users): add support for current user OAuth applications
Add CurrentUserApplicationManager for the GitLab 19.0 User Applications API (/user/applications), exposed as gl.user.applications. Closes #3451 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 8daa940 commit b0b82bc

3 files changed

Lines changed: 200 additions & 0 deletions

File tree

‎docs/gl_objects/users.rst‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,53 @@ Delete an email for a user::
435435
# or
436436
email.delete()
437437

438+
Current user applications
439+
=========================
440+
441+
References
442+
----------
443+
444+
You can manage OAuth applications owned by the current user (GitLab 19.0 and
445+
later). To manage instance-wide applications, see :doc:`applications`.
446+
447+
* v4 API:
448+
449+
+ :class:`gitlab.v4.objects.CurrentUserApplication`
450+
+ :class:`gitlab.v4.objects.CurrentUserApplicationManager`
451+
+ :attr:`gitlab.v4.objects.CurrentUser.applications`
452+
453+
* GitLab API: https://docs.gitlab.com/api/user_applications
454+
455+
Examples
456+
--------
457+
458+
List the current user's applications::
459+
460+
applications = gl.user.applications.list(get_all=True)
461+
462+
Get an application::
463+
464+
application = gl.user.applications.get(app_id)
465+
466+
Create an application::
467+
468+
application = gl.user.applications.create({
469+
'name': 'your_app',
470+
'redirect_uri': 'http://application.url',
471+
'scopes': 'read_user openid profile email',
472+
})
473+
474+
Update an application::
475+
476+
application.name = 'new_name'
477+
application.save()
478+
479+
Delete an application::
480+
481+
gl.user.applications.delete(app_id)
482+
# or
483+
application.delete()
484+
438485
Users activities
439486
================
440487

‎gitlab/v4/objects/users.py‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
from .personal_access_tokens import UserPersonalAccessTokenManager # noqa: F401
3434

3535
__all__ = [
36+
"CurrentUserApplication",
37+
"CurrentUserApplicationManager",
3638
"CurrentUserEmail",
3739
"CurrentUserEmailManager",
3840
"CurrentUserGPGKey",
@@ -73,6 +75,19 @@
7375
]
7476

7577

78+
class CurrentUserApplication(SaveMixin, ObjectDeleteMixin, RESTObject):
79+
_repr_attr = "application_name"
80+
81+
82+
class CurrentUserApplicationManager(CRUDMixin[CurrentUserApplication]):
83+
_path = "/user/applications"
84+
_obj_cls = CurrentUserApplication
85+
_create_attrs = RequiredOptional(
86+
required=("name", "redirect_uri", "scopes"), optional=("confidential",)
87+
)
88+
_update_attrs = RequiredOptional(optional=("name", "scopes"))
89+
90+
7691
class CurrentUserEmail(ObjectDeleteMixin, RESTObject):
7792
_repr_attr = "email"
7893

@@ -157,6 +172,7 @@ class CurrentUser(RESTObject):
157172
_id_attr = None
158173
_repr_attr = "username"
159174

175+
applications: CurrentUserApplicationManager
160176
emails: CurrentUserEmailManager
161177
gpgkeys: CurrentUserGPGKeyManager
162178
keys: CurrentUserKeyManager
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
"""
2+
GitLab API: https://docs.gitlab.com/api/user_applications
3+
"""
4+
5+
import pytest
6+
import responses
7+
8+
from gitlab.v4.objects import CurrentUserApplication
9+
10+
application_content = {
11+
"id": 1,
12+
"application_id": "5832fc6e14300a0d962240a8144466eef4ee93ef",
13+
"application_name": "MyApplication",
14+
"callback_url": "http://redirect.uri",
15+
"confidential": True,
16+
}
17+
updated_content = {**application_content, "application_name": "UpdatedApplication"}
18+
19+
20+
@pytest.fixture
21+
def resp_create_application():
22+
with responses.RequestsMock() as rsps:
23+
rsps.add(
24+
method=responses.POST,
25+
url="http://localhost/api/v4/user/applications",
26+
json={**application_content, "secret": "secret"},
27+
content_type="application/json",
28+
status=201,
29+
match=[
30+
responses.matchers.json_params_matcher(
31+
{
32+
"name": "MyApplication",
33+
"redirect_uri": "http://redirect.uri",
34+
"scopes": "api read_user",
35+
"confidential": True,
36+
}
37+
)
38+
],
39+
)
40+
yield rsps
41+
42+
43+
@pytest.fixture
44+
def resp_list_application():
45+
with responses.RequestsMock() as rsps:
46+
rsps.add(
47+
method=responses.GET,
48+
url="http://localhost/api/v4/user/applications",
49+
json=[application_content],
50+
content_type="application/json",
51+
status=200,
52+
)
53+
yield rsps
54+
55+
56+
@pytest.fixture
57+
def resp_get_application():
58+
with responses.RequestsMock() as rsps:
59+
rsps.add(
60+
method=responses.GET,
61+
url="http://localhost/api/v4/user/applications/1",
62+
json=application_content,
63+
content_type="application/json",
64+
status=200,
65+
)
66+
yield rsps
67+
68+
69+
@pytest.fixture
70+
def resp_update_application():
71+
with responses.RequestsMock() as rsps:
72+
rsps.add(
73+
method=responses.GET,
74+
url="http://localhost/api/v4/user/applications/1",
75+
json=application_content,
76+
content_type="application/json",
77+
status=200,
78+
)
79+
rsps.add(
80+
method=responses.PUT,
81+
url="http://localhost/api/v4/user/applications/1",
82+
json=updated_content,
83+
content_type="application/json",
84+
status=200,
85+
match=[
86+
responses.matchers.json_params_matcher({"name": "UpdatedApplication"})
87+
],
88+
)
89+
yield rsps
90+
91+
92+
@pytest.fixture
93+
def resp_delete_application():
94+
with responses.RequestsMock() as rsps:
95+
rsps.add(
96+
method=responses.DELETE,
97+
url="http://localhost/api/v4/user/applications/1",
98+
status=204,
99+
)
100+
yield rsps
101+
102+
103+
def test_create_current_user_application(current_user, resp_create_application):
104+
application = current_user.applications.create(
105+
{
106+
"name": "MyApplication",
107+
"redirect_uri": "http://redirect.uri",
108+
"scopes": "api read_user",
109+
"confidential": True,
110+
}
111+
)
112+
assert isinstance(application, CurrentUserApplication)
113+
assert application.id == 1
114+
assert application.secret == "secret"
115+
116+
117+
def test_list_current_user_applications(current_user, resp_list_application):
118+
applications = current_user.applications.list()
119+
assert isinstance(applications[0], CurrentUserApplication)
120+
assert applications[0].application_name == "MyApplication"
121+
122+
123+
def test_get_current_user_application(current_user, resp_get_application):
124+
application = current_user.applications.get(1)
125+
assert isinstance(application, CurrentUserApplication)
126+
assert application.callback_url == "http://redirect.uri"
127+
128+
129+
def test_update_current_user_application(current_user, resp_update_application):
130+
application = current_user.applications.get(1)
131+
application.name = "UpdatedApplication"
132+
application.save()
133+
assert application.application_name == "UpdatedApplication"
134+
135+
136+
def test_delete_current_user_application(current_user, resp_delete_application):
137+
current_user.applications.delete(1)

0 commit comments

Comments
 (0)