Skip to content

Commit da40e09

Browse files
Massimiliano RivaJohnVillalovos
Massimiliano Riva
authored andcommitted
feat(api): add support for token self-rotation
1 parent 938b0d9 commit da40e09

File tree

7 files changed

+100
-3
lines changed

7 files changed

+100
-3
lines changed

docs/gl_objects/group_access_tokens.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,9 @@ Rotate a group access token and retrieve its new value::
4646
# or directly using a token ID
4747
new_token = group.access_tokens.rotate(42)
4848
print(new_token.token)
49+
50+
Self-Rotate the group access token you are using to authenticate the request and retrieve its new value::
51+
52+
token = group.access_tokens.get(42, lazy=True)
53+
token.rotate(self_rotate=True)
54+
print(token.token)

docs/gl_objects/personal_access_tokens.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ Rotate a personal access token and retrieve its new value::
6161
new_token_dict = gl.personal_access_tokens.rotate(42)
6262
print(new_token_dict)
6363

64+
Self-Rotate the personal access token you are using to authenticate the request and retrieve its new value::
65+
66+
token = gl.personal_access_tokens.get(42, lazy=True)
67+
token.rotate(self_rotate=True)
68+
print(token.token)
69+
6470
Create a personal access token for a user (admin only)::
6571

6672
user = gl.users.get(25, lazy=True)

docs/gl_objects/project_access_tokens.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,9 @@ Rotate a project access token and retrieve its new value::
4646
# or directly using a token ID
4747
new_token = project.access_tokens.rotate(42)
4848
print(new_token.token)
49+
50+
Self-Rotate the project access token you are using to authenticate the request and retrieve its new value::
51+
52+
token = project.access_tokens.get(42, lazy=True)
53+
token.rotate(self_rotate=True)
54+
print(new_token.token)

gitlab/mixins.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -660,10 +660,11 @@ class ObjectRotateMixin(_RestObjectBase):
660660
optional=("expires_at",),
661661
)
662662
@exc.on_http_error(exc.GitlabRotateError)
663-
def rotate(self, **kwargs: Any) -> dict[str, Any]:
663+
def rotate(self, *, self_rotate: bool = False, **kwargs: Any) -> dict[str, Any]:
664664
"""Rotate the current access token object.
665665
666666
Args:
667+
self_rotate: If True, the current access token object will be rotated.
667668
**kwargs: Extra options to send to the server (e.g. sudo)
668669
669670
Raises:
@@ -673,7 +674,8 @@ def rotate(self, **kwargs: Any) -> dict[str, Any]:
673674
if TYPE_CHECKING:
674675
assert isinstance(self.manager, RotateMixin)
675676
assert self.encoded_id is not None
676-
server_data = self.manager.rotate(self.encoded_id, **kwargs)
677+
token_id = "self" if self_rotate else self.encoded_id
678+
server_data = self.manager.rotate(token_id, **kwargs)
677679
self._update_attrs(server_data)
678680
return server_data
679681

tests/unit/objects/test_group_access_tokens.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,19 @@ def resp_rotate_group_access_token(token_content):
9191
yield rsps
9292

9393

94+
@pytest.fixture
95+
def resp_self_rotate_group_access_token(token_content):
96+
with responses.RequestsMock() as rsps:
97+
rsps.add(
98+
method=responses.POST,
99+
url="http://localhost/api/v4/groups/1/access_tokens/self/rotate",
100+
json=token_content,
101+
content_type="application/json",
102+
status=200,
103+
)
104+
yield rsps
105+
106+
94107
def test_list_group_access_tokens(gl, resp_list_group_access_token):
95108
access_tokens = gl.groups.get(1, lazy=True).access_tokens.list()
96109
assert len(access_tokens) == 1
@@ -127,3 +140,15 @@ def test_rotate_group_access_token(group, resp_rotate_group_access_token):
127140
access_token.rotate()
128141
assert isinstance(access_token, GroupAccessToken)
129142
assert access_token.token == "s3cr3t"
143+
144+
145+
def test_self_rotate_group_access_token(group, resp_self_rotate_group_access_token):
146+
access_token = group.access_tokens.get(1, lazy=True)
147+
access_token.rotate(self_rotate=True)
148+
assert isinstance(access_token, GroupAccessToken)
149+
assert access_token.token == "s3cr3t"
150+
151+
# Verify that the url contains "self"
152+
rotation_calls = resp_self_rotate_group_access_token.calls
153+
assert len(rotation_calls) == 1
154+
assert "self/rotate" in rotation_calls[0].request.url

tests/unit/objects/test_personal_access_tokens.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,19 @@ def resp_rotate_personal_access_token(token_content):
102102
yield rsps
103103

104104

105+
@pytest.fixture
106+
def resp_self_rotate_personal_access_token(token_content):
107+
with responses.RequestsMock() as rsps:
108+
rsps.add(
109+
method=responses.POST,
110+
url="http://localhost/api/v4/personal_access_tokens/self/rotate",
111+
json=token_content,
112+
content_type="application/json",
113+
status=200,
114+
)
115+
yield rsps
116+
117+
105118
def test_create_personal_access_token(gl, resp_create_user_personal_access_token):
106119
user = gl.users.get(1, lazy=True)
107120
access_token = user.personal_access_tokens.create(
@@ -148,8 +161,20 @@ def test_revoke_personal_access_token_by_id(gl, resp_delete_personal_access_toke
148161
gl.personal_access_tokens.delete(token_id)
149162

150163

151-
def test_rotate_project_access_token(gl, resp_rotate_personal_access_token):
164+
def test_rotate_personal_access_token(gl, resp_rotate_personal_access_token):
152165
access_token = gl.personal_access_tokens.get(1, lazy=True)
153166
access_token.rotate()
154167
assert isinstance(access_token, PersonalAccessToken)
155168
assert access_token.token == "s3cr3t"
169+
170+
171+
def test_self_rotate_personal_access_token(gl, resp_self_rotate_personal_access_token):
172+
access_token = gl.personal_access_tokens.get(1, lazy=True)
173+
access_token.rotate(self_rotate=True)
174+
assert isinstance(access_token, PersonalAccessToken)
175+
assert access_token.token == "s3cr3t"
176+
177+
# Verify that the url contains "self"
178+
rotation_calls = resp_self_rotate_personal_access_token.calls
179+
assert len(rotation_calls) == 1
180+
assert "self/rotate" in rotation_calls[0].request.url

tests/unit/objects/test_project_access_tokens.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,19 @@ def resp_rotate_project_access_token(token_content):
9191
yield rsps
9292

9393

94+
@pytest.fixture
95+
def resp_self_rotate_project_access_token(token_content):
96+
with responses.RequestsMock() as rsps:
97+
rsps.add(
98+
method=responses.POST,
99+
url="http://localhost/api/v4/projects/1/access_tokens/self/rotate",
100+
json=token_content,
101+
content_type="application/json",
102+
status=200,
103+
)
104+
yield rsps
105+
106+
94107
def test_list_project_access_tokens(gl, resp_list_project_access_token):
95108
access_tokens = gl.projects.get(1, lazy=True).access_tokens.list()
96109
assert len(access_tokens) == 1
@@ -127,3 +140,17 @@ def test_rotate_project_access_token(project, resp_rotate_project_access_token):
127140
access_token.rotate()
128141
assert isinstance(access_token, ProjectAccessToken)
129142
assert access_token.token == "s3cr3t"
143+
144+
145+
def test_self_rotate_project_access_token(
146+
project, resp_self_rotate_project_access_token
147+
):
148+
access_token = project.access_tokens.get(1, lazy=True)
149+
access_token.rotate(self_rotate=True)
150+
assert isinstance(access_token, ProjectAccessToken)
151+
assert access_token.token == "s3cr3t"
152+
153+
# Verify that the url contains "self"
154+
rotation_calls = resp_self_rotate_project_access_token.calls
155+
assert len(rotation_calls) == 1
156+
assert "self/rotate" in rotation_calls[0].request.url

0 commit comments

Comments
 (0)