From b86e4af1038dec66442e0aaa0593f8c49cf5af58 Mon Sep 17 00:00:00 2001 From: Massimo Gengarelli Date: Fri, 6 Jun 2025 06:16:54 +0200 Subject: [PATCH] gitlab_*_access_token: handle `revoked` field in group and project access tokens (#10196) fix(gitlab): handle `revoked` field in group and project access tokens --- .../10196-fix-gitlab-access-tokens.yml | 2 + plugins/modules/gitlab_group_access_token.py | 2 +- .../modules/gitlab_project_access_token.py | 2 +- tests/unit/plugins/modules/gitlab.py | 74 ++++++++++++++++--- .../modules/test_gitlab_group_access_token.py | 28 ++++++- .../test_gitlab_project_access_token.py | 28 ++++++- 6 files changed, 118 insertions(+), 18 deletions(-) create mode 100644 changelogs/fragments/10196-fix-gitlab-access-tokens.yml diff --git a/changelogs/fragments/10196-fix-gitlab-access-tokens.yml b/changelogs/fragments/10196-fix-gitlab-access-tokens.yml new file mode 100644 index 0000000000..988f5c78ed --- /dev/null +++ b/changelogs/fragments/10196-fix-gitlab-access-tokens.yml @@ -0,0 +1,2 @@ +bugfixes: + - "gitlab_group_access_token, gitlab_project_access_token - fix handling of group and project access tokens for changes in GitLab 17.10 (https://github.com/ansible-collections/community.general/pull/10196)." diff --git a/plugins/modules/gitlab_group_access_token.py b/plugins/modules/gitlab_group_access_token.py index bcf75e056b..62851c1a8c 100644 --- a/plugins/modules/gitlab_group_access_token.py +++ b/plugins/modules/gitlab_group_access_token.py @@ -185,7 +185,7 @@ class GitLabGroupAccessToken(object): @param name of the access token ''' def find_access_token(self, group, name): - access_tokens = group.access_tokens.list(all=True) + access_tokens = [x for x in group.access_tokens.list(all=True) if not getattr(x, 'revoked', False)] for access_token in access_tokens: if (access_token.name == name): self.access_token_object = access_token diff --git a/plugins/modules/gitlab_project_access_token.py b/plugins/modules/gitlab_project_access_token.py index a93d5531bf..d0251d88a6 100644 --- a/plugins/modules/gitlab_project_access_token.py +++ b/plugins/modules/gitlab_project_access_token.py @@ -183,7 +183,7 @@ class GitLabProjectAccessToken(object): @param name of the access token ''' def find_access_token(self, project, name): - access_tokens = project.access_tokens.list(all=True) + access_tokens = [x for x in project.access_tokens.list(all=True) if not getattr(x, 'revoked', False)] for access_token in access_tokens: if (access_token.name == name): self.access_token_object = access_token diff --git a/tests/unit/plugins/modules/gitlab.py b/tests/unit/plugins/modules/gitlab.py index d388a5c66b..a66ecf856f 100644 --- a/tests/unit/plugins/modules/gitlab.py +++ b/tests/unit/plugins/modules/gitlab.py @@ -287,11 +287,36 @@ def resp_delete_group(url, request): @urlmatch(scheme="http", netloc="localhost", path="/api/v4/groups/1/access_tokens", method="get") def resp_list_group_access_tokens(url, request): headers = {'content-type': 'application/json'} - content = ('[{"user_id" : 1, "scopes" : ["api"], "name" : "token1", "expires_at" : "2021-01-31",' - '"id" : 1, "active" : false, "created_at" : "2021-01-20T22:11:48.151Z", "revoked" : true,' - '"access_level": 40},{"user_id" : 2, "scopes" : ["api"], "name" : "token2", "expires_at" : "2021-02-31",' - '"id" : 2, "active" : true, "created_at" : "2021-02-20T22:11:48.151Z", "revoked" : false,' - '"access_level": 40}]') + content = ( + '[{"id":689,"name":"test-token","revoked":true,"created_at":"2025-06-02T09:18:01.484Z",' + '"description":null,"scopes":["read_repository","write_repository"],"user_id":1779,' + '"last_used_at":null,"active":false,"expires_at":"2025-07-02","access_level":40,' + '"resource_type":"group","resource_id":1730},' + '{"id":690,"name":"test-token","revoked":true,"created_at":"2025-06-02T09:36:30.650Z",' + '"description":null,"scopes":["read_repository","write_repository"],"user_id":1780,' + '"last_used_at":null,"active":false,"expires_at":"2025-07-02","access_level":40,' + '"resource_type":"group","resource_id":1730},' + '{"id":691,"name":"test-token","revoked":false,"created_at":"2025-06-02T09:39:18.252Z",' + '"description":null,"scopes":["read_repository","write_repository"],"user_id":1781,' + '"last_used_at":null,"active":false,"expires_at":"2025-07-02","access_level":40,' + '"resource_type":"group","resource_id":1730},' + '{"id":695,"name":"test-token-no-revoked","created_at":"2025-06-02T09:39:18.252Z",' + '"description":null,"scopes":["read_repository","write_repository"],"user_id":1781,' + '"last_used_at":null,"active":false,"expires_at":"2025-07-02","access_level":40,' + '"resource_type":"group","resource_id":1730},' + '{"id":692,"name":"test-token-two","revoked":true,"created_at":"2025-06-02T09:41:18.442Z",' + '"description":null,"scopes":["read_repository","write_repository"],"user_id":1782,' + '"last_used_at":null,"active":false,"expires_at":"2025-07-02","access_level":40,' + '"resource_type":"group","resource_id":1730},' + '{"id":693,"name":"test-token-three","revoked":true,"created_at":"2025-06-02T09:50:00.976Z"' + ',"description":null,"scopes":["read_repository","write_repository"],"user_id":1783,' + '"last_used_at":null,"active":false,"expires_at":"2025-06-04","access_level":40,' + '"resource_type":"group","resource_id":1730},' + '{"id":694,"name":"test-token-three","revoked":true,"created_at":"2025-06-02T09:56:45.779Z"' + ',"description":null,"scopes":["read_repository","write_repository"],"user_id":1784,' + '"last_used_at":null,"active":false,"expires_at":"2025-06-04","access_level":40,' + '"resource_type":"group","resource_id":1730}]' + ) content = content.encode("utf-8") return response(200, content, headers, None, 5, request) @@ -306,7 +331,7 @@ def resp_create_group_access_tokens(url, request): return response(201, content, headers, None, 5, request) -@urlmatch(scheme="http", netloc="localhost", path="/api/v4/groups/1/access_tokens/1", method="delete") +@urlmatch(scheme="http", netloc="localhost", path="/api/v4/groups/1/access_tokens/[0-9]+", method="delete") def resp_revoke_group_access_tokens(url, request): headers = {'content-type': 'application/json'} content = ('') @@ -567,11 +592,36 @@ def resp_delete_protected_branch(url, request): @urlmatch(scheme="http", netloc="localhost", path="/api/v4/projects/1/access_tokens", method="get") def resp_list_project_access_tokens(url, request): headers = {'content-type': 'application/json'} - content = ('[{"user_id" : 1, "scopes" : ["api"], "name" : "token1", "expires_at" : "2021-01-31",' - '"id" : 1, "active" : false, "created_at" : "2021-01-20T22:11:48.151Z", "revoked" : true,' - '"access_level": 40},{"user_id" : 2, "scopes" : ["api"], "name" : "token2", "expires_at" : "2021-02-31",' - '"id" : 2, "active" : true, "created_at" : "2021-02-20T22:11:48.151Z", "revoked" : false,' - '"access_level": 40}]') + content = ( + '[{"id":689,"name":"test-token","revoked":true,"created_at":"2025-06-02T09:18:01.484Z",' + '"description":null,"scopes":["read_repository","write_repository"],"user_id":1779,' + '"last_used_at":null,"active":false,"expires_at":"2025-07-02","access_level":40,' + '"resource_type":"project","resource_id":1730},' + '{"id":690,"name":"test-token","revoked":true,"created_at":"2025-06-02T09:36:30.650Z",' + '"description":null,"scopes":["read_repository","write_repository"],"user_id":1780,' + '"last_used_at":null,"active":false,"expires_at":"2025-07-02","access_level":40,' + '"resource_type":"project","resource_id":1730},' + '{"id":691,"name":"test-token","revoked":false,"created_at":"2025-06-02T09:39:18.252Z",' + '"description":null,"scopes":["read_repository","write_repository"],"user_id":1781,' + '"last_used_at":null,"active":false,"expires_at":"2025-07-02","access_level":40,' + '"resource_type":"project","resource_id":1730},' + '{"id":695,"name":"test-token-no-revoked","created_at":"2025-06-02T09:39:18.252Z",' + '"description":null,"scopes":["read_repository","write_repository"],"user_id":1781,' + '"last_used_at":null,"active":false,"expires_at":"2025-07-02","access_level":40,' + '"resource_type":"group","resource_id":1730},' + '{"id":692,"name":"test-token-two","revoked":true,"created_at":"2025-06-02T09:41:18.442Z",' + '"description":null,"scopes":["read_repository","write_repository"],"user_id":1782,' + '"last_used_at":null,"active":false,"expires_at":"2025-07-02","access_level":40,' + '"resource_type":"project","resource_id":1730},' + '{"id":693,"name":"test-token-three","revoked":true,"created_at":"2025-06-02T09:50:00.976Z"' + ',"description":null,"scopes":["read_repository","write_repository"],"user_id":1783,' + '"last_used_at":null,"active":false,"expires_at":"2025-06-04","access_level":40,' + '"resource_type":"project","resource_id":1730},' + '{"id":694,"name":"test-token-three","revoked":true,"created_at":"2025-06-02T09:56:45.779Z"' + ',"description":null,"scopes":["read_repository","write_repository"],"user_id":1784,' + '"last_used_at":null,"active":false,"expires_at":"2025-06-04","access_level":40,' + '"resource_type":"project","resource_id":1730}]' + ) content = content.encode("utf-8") return response(200, content, headers, None, 5, request) @@ -586,7 +636,7 @@ def resp_create_project_access_tokens(url, request): return response(201, content, headers, None, 5, request) -@urlmatch(scheme="http", netloc="localhost", path="/api/v4/projects/1/access_tokens/1", method="delete") +@urlmatch(scheme="http", netloc="localhost", path="/api/v4/projects/1/access_tokens/[0-9]+", method="delete") def resp_revoke_project_access_tokens(url, request): headers = {'content-type': 'application/json'} content = ('') diff --git a/tests/unit/plugins/modules/test_gitlab_group_access_token.py b/tests/unit/plugins/modules/test_gitlab_group_access_token.py index 06af948204..cc7644060f 100644 --- a/tests/unit/plugins/modules/test_gitlab_group_access_token.py +++ b/tests/unit/plugins/modules/test_gitlab_group_access_token.py @@ -68,9 +68,33 @@ class TestGitlabGroupAccessToken(GitlabModuleTestCase): group = self.gitlab_instance.groups.get(1) self.assertIsNotNone(group) - rvalue = self.moduleUtil.find_access_token(group, "token1") + rvalue = self.moduleUtil.find_access_token(group, "test-token") self.assertEqual(rvalue, False) self.assertIsNotNone(self.moduleUtil.access_token_object) + self.assertEqual(self.moduleUtil.access_token_object.id, 691) + self.assertFalse(self.moduleUtil.access_token_object.revoked) + + @with_httmock(resp_get_group) + @with_httmock(resp_list_group_access_tokens) + def test_find_access_token_old_format(self): + group = self.gitlab_instance.groups.get(1) + self.assertIsNotNone(group) + + rvalue = self.moduleUtil.find_access_token(group, "test-token-no-revoked") + self.assertEqual(rvalue, False) + self.assertIsNotNone(self.moduleUtil.access_token_object) + self.assertEqual(self.moduleUtil.access_token_object.id, 695) + self.assertFalse(hasattr(self.moduleUtil.access_token_object, "revoked")) + + @with_httmock(resp_get_group) + @with_httmock(resp_list_group_access_tokens) + def test_find_revoked_access_token(self): + group = self.gitlab_instance.groups.get(1) + self.assertIsNotNone(group) + + rvalue = self.moduleUtil.find_access_token(group, "test-token-three") + self.assertEqual(rvalue, False) + self.assertIsNone(self.moduleUtil.access_token_object) @with_httmock(resp_get_group) @with_httmock(resp_list_group_access_tokens) @@ -99,7 +123,7 @@ class TestGitlabGroupAccessToken(GitlabModuleTestCase): groups = self.gitlab_instance.groups.get(1) self.assertIsNotNone(groups) - rvalue = self.moduleUtil.find_access_token(groups, "token1") + rvalue = self.moduleUtil.find_access_token(groups, "test-token") self.assertEqual(rvalue, False) self.assertIsNotNone(self.moduleUtil.access_token_object) diff --git a/tests/unit/plugins/modules/test_gitlab_project_access_token.py b/tests/unit/plugins/modules/test_gitlab_project_access_token.py index ebc324b889..050c2435fa 100644 --- a/tests/unit/plugins/modules/test_gitlab_project_access_token.py +++ b/tests/unit/plugins/modules/test_gitlab_project_access_token.py @@ -68,9 +68,33 @@ class TestGitlabProjectAccessToken(GitlabModuleTestCase): project = self.gitlab_instance.projects.get(1) self.assertIsNotNone(project) - rvalue = self.moduleUtil.find_access_token(project, "token1") + rvalue = self.moduleUtil.find_access_token(project, "test-token") self.assertEqual(rvalue, False) self.assertIsNotNone(self.moduleUtil.access_token_object) + self.assertEqual(self.moduleUtil.access_token_object.id, 691) + self.assertFalse(self.moduleUtil.access_token_object.revoked) + + @with_httmock(resp_get_project) + @with_httmock(resp_list_project_access_tokens) + def test_find_access_token_old_format(self): + project = self.gitlab_instance.projects.get(1) + self.assertIsNotNone(project) + + rvalue = self.moduleUtil.find_access_token(project, "test-token-no-revoked") + self.assertEqual(rvalue, False) + self.assertIsNotNone(self.moduleUtil.access_token_object) + self.assertEqual(self.moduleUtil.access_token_object.id, 695) + self.assertFalse(hasattr(self.moduleUtil.access_token_object, "revoked")) + + @with_httmock(resp_get_project) + @with_httmock(resp_list_project_access_tokens) + def test_find_revoked_access_token(self): + project = self.gitlab_instance.projects.get(1) + self.assertIsNotNone(project) + + rvalue = self.moduleUtil.find_access_token(project, "test-token-three") + self.assertEqual(rvalue, False) + self.assertIsNone(self.moduleUtil.access_token_object) @with_httmock(resp_get_project) @with_httmock(resp_list_project_access_tokens) @@ -99,7 +123,7 @@ class TestGitlabProjectAccessToken(GitlabModuleTestCase): project = self.gitlab_instance.projects.get(1) self.assertIsNotNone(project) - rvalue = self.moduleUtil.find_access_token(project, "token1") + rvalue = self.moduleUtil.find_access_token(project, "test-token") self.assertEqual(rvalue, False) self.assertIsNotNone(self.moduleUtil.access_token_object)