Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions docs/gl_objects/users.rst
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,53 @@ Delete an email for a user::
# or
email.delete()

Current user applications
=========================

References
----------

You can manage OAuth applications owned by the current user (GitLab 19.0 and
later). To manage instance-wide applications, see :doc:`applications`.

* v4 API:

+ :class:`gitlab.v4.objects.CurrentUserApplication`
+ :class:`gitlab.v4.objects.CurrentUserApplicationManager`
+ :attr:`gitlab.v4.objects.CurrentUser.applications`

* GitLab API: https://docs.gitlab.com/api/user_applications

Examples
--------

List the current user's applications::

applications = gl.user.applications.list(get_all=True)

Get an application::

application = gl.user.applications.get(app_id)

Create an application::

application = gl.user.applications.create({
'name': 'your_app',
'redirect_uri': 'http://application.url',
'scopes': 'read_user openid profile email',
})

Update an application::

application.name = 'new_name'
application.save()

Delete an application::

gl.user.applications.delete(app_id)
# or
application.delete()

Users activities
================

Expand Down
16 changes: 16 additions & 0 deletions gitlab/v4/objects/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
from .personal_access_tokens import UserPersonalAccessTokenManager # noqa: F401

__all__ = [
"CurrentUserApplication",
"CurrentUserApplicationManager",
"CurrentUserEmail",
"CurrentUserEmailManager",
"CurrentUserGPGKey",
Expand Down Expand Up @@ -73,6 +75,19 @@
]


class CurrentUserApplication(SaveMixin, ObjectDeleteMixin, RESTObject):
_repr_attr = "application_name"


class CurrentUserApplicationManager(CRUDMixin[CurrentUserApplication]):
_path = "/user/applications"
_obj_cls = CurrentUserApplication
_create_attrs = RequiredOptional(
required=("name", "redirect_uri", "scopes"), optional=("confidential",)
)
_update_attrs = RequiredOptional(optional=("name", "scopes"))


class CurrentUserEmail(ObjectDeleteMixin, RESTObject):
_repr_attr = "email"

Expand Down Expand Up @@ -157,6 +172,7 @@ class CurrentUser(RESTObject):
_id_attr = None
_repr_attr = "username"

applications: CurrentUserApplicationManager
emails: CurrentUserEmailManager
gpgkeys: CurrentUserGPGKeyManager
keys: CurrentUserKeyManager
Expand Down
137 changes: 137 additions & 0 deletions tests/unit/objects/test_current_user_applications.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
"""
GitLab API: https://docs.gitlab.com/api/user_applications
"""

import pytest
import responses

from gitlab.v4.objects import CurrentUserApplication

application_content = {
"id": 1,
"application_id": "5832fc6e14300a0d962240a8144466eef4ee93ef",
"application_name": "MyApplication",
"callback_url": "http://redirect.uri",
"confidential": True,
}
updated_content = {**application_content, "application_name": "UpdatedApplication"}


@pytest.fixture
def resp_create_application():
with responses.RequestsMock() as rsps:
rsps.add(
method=responses.POST,
url="http://localhost/api/v4/user/applications",
json={**application_content, "secret": "secret"},
content_type="application/json",
status=201,
match=[
responses.matchers.json_params_matcher(
{
"name": "MyApplication",
"redirect_uri": "http://redirect.uri",
"scopes": "api read_user",
"confidential": True,
}
)
],
)
yield rsps


@pytest.fixture
def resp_list_application():
with responses.RequestsMock() as rsps:
rsps.add(
method=responses.GET,
url="http://localhost/api/v4/user/applications",
json=[application_content],
content_type="application/json",
status=200,
)
yield rsps


@pytest.fixture
def resp_get_application():
with responses.RequestsMock() as rsps:
rsps.add(
method=responses.GET,
url="http://localhost/api/v4/user/applications/1",
json=application_content,
content_type="application/json",
status=200,
)
yield rsps


@pytest.fixture
def resp_update_application():
with responses.RequestsMock() as rsps:
rsps.add(
method=responses.GET,
url="http://localhost/api/v4/user/applications/1",
json=application_content,
content_type="application/json",
status=200,
)
rsps.add(
method=responses.PUT,
url="http://localhost/api/v4/user/applications/1",
json=updated_content,
content_type="application/json",
status=200,
match=[
responses.matchers.json_params_matcher({"name": "UpdatedApplication"})
],
)
yield rsps


@pytest.fixture
def resp_delete_application():
with responses.RequestsMock() as rsps:
rsps.add(
method=responses.DELETE,
url="http://localhost/api/v4/user/applications/1",
status=204,
)
yield rsps


def test_create_current_user_application(current_user, resp_create_application):
application = current_user.applications.create(
{
"name": "MyApplication",
"redirect_uri": "http://redirect.uri",
"scopes": "api read_user",
"confidential": True,
}
)
assert isinstance(application, CurrentUserApplication)
assert application.id == 1
assert application.secret == "secret"


def test_list_current_user_applications(current_user, resp_list_application):
applications = current_user.applications.list()
assert isinstance(applications[0], CurrentUserApplication)
assert applications[0].application_name == "MyApplication"


def test_get_current_user_application(current_user, resp_get_application):
application = current_user.applications.get(1)
assert isinstance(application, CurrentUserApplication)
assert application.callback_url == "http://redirect.uri"


def test_update_current_user_application(current_user, resp_update_application):
application = current_user.applications.get(1)
application.name = "UpdatedApplication"
application.save()
assert application.application_name == "UpdatedApplication"


def test_delete_current_user_application(current_user, resp_delete_application):
current_user.applications.delete(1)
Loading