From 5bf880cefc73e488e5a10c2497d16a78e55859eb Mon Sep 17 00:00:00 2001 From: Stephane Talbot Date: Tue, 23 Jan 2024 19:56:41 +0100 Subject: [PATCH 01/12] Add POST /personal_access_tokens/self/rotate endpoint Add a new POST `/personal_access_tokens/self/rotate` endpoint in order to rotate passed personal access token. Token must have the `api` scope. Changelog: added --- lib/api/api.rb | 1 + .../helpers/personal_access_tokens_helpers.rb | 12 ++ lib/api/personal_access_tokens.rb | 11 +- .../personal_access_tokens/self_rotation.rb | 35 +++++ .../self_rotation_spec.rb | 142 ++++++++++++++++++ 5 files changed, 192 insertions(+), 9 deletions(-) create mode 100644 lib/api/personal_access_tokens/self_rotation.rb create mode 100644 spec/requests/api/personal_access_tokens/self_rotation_spec.rb diff --git a/lib/api/api.rb b/lib/api/api.rb index 00d7425cc75050..8fe6f395971d0f 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -296,6 +296,7 @@ def initialize(location_url) mount ::API::Pages mount ::API::PagesDomains mount ::API::PersonalAccessTokens::SelfInformation + mount ::API::PersonalAccessTokens::SelfRotation mount ::API::PersonalAccessTokens mount ::API::ProjectClusters mount ::API::ProjectContainerRepositories diff --git a/lib/api/helpers/personal_access_tokens_helpers.rb b/lib/api/helpers/personal_access_tokens_helpers.rb index 4fd72d89f4c7cb..99ff48251608ff 100644 --- a/lib/api/helpers/personal_access_tokens_helpers.rb +++ b/lib/api/helpers/personal_access_tokens_helpers.rb @@ -33,6 +33,18 @@ def revoke_token(token) service.success? ? no_content! : bad_request!(nil) end + + def rotate_token(token, params) + service = ::PersonalAccessTokens::RotateService.new(current_user, token).execute(params) + + if service.success? + status :ok + + service.payload[:personal_access_token] + else + bad_request!(service.message) + end + end end end end diff --git a/lib/api/personal_access_tokens.rb b/lib/api/personal_access_tokens.rb index de00b66ead3a69..8bcacc7f32fc56 100644 --- a/lib/api/personal_access_tokens.rb +++ b/lib/api/personal_access_tokens.rb @@ -82,16 +82,9 @@ class PersonalAccessTokens < ::API::Base token = PersonalAccessToken.find_by_id(params[:id]) if Ability.allowed?(current_user, :manage_user_personal_access_token, token&.user) - response = ::PersonalAccessTokens::RotateService.new(current_user, token).execute(declared_params) + new_token = rotate_token(token, declared_params) - if response.success? - status :ok - - new_token = response.payload[:personal_access_token] - present new_token, with: Entities::PersonalAccessTokenWithToken - else - bad_request!(response.message) - end + present new_token, with: Entities::PersonalAccessTokenWithToken else # Only admins should be informed if the token doesn't exist current_user.can_admin_all_resources? ? not_found! : unauthorized! diff --git a/lib/api/personal_access_tokens/self_rotation.rb b/lib/api/personal_access_tokens/self_rotation.rb new file mode 100644 index 00000000000000..af2410e515616a --- /dev/null +++ b/lib/api/personal_access_tokens/self_rotation.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module API + class PersonalAccessTokens + class SelfRotation < ::API::Base + include APIGuard + + feature_category :system_access + + helpers ::API::Helpers::PersonalAccessTokensHelpers + + allow_access_with_scope :api + + before { authenticate! } + + resource :personal_access_tokens do + desc 'Rotate personal access token' do + detail 'Rotates a personal access token.' + success Entities::PersonalAccessTokenWithToken + end + params do + optional :expires_at, + type: Date, + desc: "The expiration date of the token", + documentation: { example: '2021-01-31' } + end + post 'self/rotate' do + new_token = rotate_token(access_token, declared_params) + + present new_token, with: Entities::PersonalAccessTokenWithToken + end + end + end + end +end diff --git a/spec/requests/api/personal_access_tokens/self_rotation_spec.rb b/spec/requests/api/personal_access_tokens/self_rotation_spec.rb new file mode 100644 index 00000000000000..72b5a1ed0e4fb2 --- /dev/null +++ b/spec/requests/api/personal_access_tokens/self_rotation_spec.rb @@ -0,0 +1,142 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe API::PersonalAccessTokens::SelfRotation, feature_category: :system_access do + let(:path) { '/personal_access_tokens/self/rotate' } + let(:token) { create(:personal_access_token, user: current_user) } + let(:expiry_date) { Date.today + 1.week } + let(:params) { {} } + + let_it_be(:current_user) { create(:user) } + + describe 'POST /personal_access_tokens/self/rotate' do + subject(:rotate_token) { post(api(path, personal_access_token: token), params: params) } + + shared_examples 'rotating token succeeds' do + it 'rotate token', :aggregate_failures do + rotate_token + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['token']).not_to eq(token.token) + expect(json_response['expires_at']).to eq(expiry_date.to_s) + expect(token.reload).to be_revoked + end + end + + shared_examples 'rotating token denied' do |status| + it 'cannot rotate token' do + rotate_token + + expect(response).to have_gitlab_http_status(status) + end + end + + context 'when current_user is an administrator', :enable_admin_mode do + let(:current_user) { create(:admin) } + + it_behaves_like 'rotating token succeeds' + + context 'when expiry is defined' do + let(:expiry_date) { Date.today + 1.month } + let(:params) { { expires_at: expiry_date } } + + it_behaves_like 'rotating token succeeds' + end + + context 'with impersonated token' do + let(:token) { create(:personal_access_token, :impersonation, user: current_user) } + + it_behaves_like 'rotating token succeeds' + end + + Gitlab::Auth.all_available_scopes.each do |scope| + context "with a '#{scope}' scoped token" do + let(:current_user) { create(:admin) } + let(:token) { create(:personal_access_token, scopes: [scope], user: current_user) } + + if [Gitlab::Auth::API_SCOPE].include? scope + it_behaves_like 'rotating token succeeds' + else + it_behaves_like 'rotating token denied', :forbidden + end + end + end + end + + context 'when current_user is not an administrator' do + let(:current_user) { create(:user) } + + it_behaves_like 'rotating token succeeds' + + context 'when expiry is defined' do + let(:expiry_date) { Date.today + 1.month } + let(:params) { { expires_at: expiry_date } } + + it_behaves_like 'rotating token succeeds' + end + + context 'with impersonated token' do + let(:token) { create(:personal_access_token, :impersonation, user: current_user) } + + it_behaves_like 'rotating token succeeds' + end + + Gitlab::Auth.all_available_scopes.each do |scope| + context "with a '#{scope}' scoped token" do + let(:current_user) { create(:user) } + let(:token) { create(:personal_access_token, scopes: [scope], user: current_user) } + + if [Gitlab::Auth::API_SCOPE].include? scope + it_behaves_like 'rotating token succeeds' + else + it_behaves_like 'rotating token denied', :forbidden + end + end + end + end + + context 'when token is invalid' do + let(:current_user) { create(:user) } + + context 'with already revoked token' do + let(:token) { create(:personal_access_token, :revoked, user: current_user) } + + it_behaves_like 'rotating token denied', :unauthorized + end + + context 'when token is expired' do + let(:token) { create(:personal_access_token, expires_at: 1.day.ago, user: current_user) } + + it_behaves_like 'rotating token denied', :unauthorized + end + end + + context 'when current_user is a project bot' do + let(:current_user) { create(:user, :project_bot) } + + it_behaves_like 'rotating token denied', :forbidden + + context 'when expiry is defined' do + let(:expiry_date) { Date.today + 1.month } + let(:params) { { expires_at: expiry_date } } + + it_behaves_like 'rotating token denied', :forbidden + end + + context 'with impersonated token' do + let(:token) { create(:personal_access_token, :impersonation, user: current_user) } + + it_behaves_like 'rotating token denied', :forbidden + end + + Gitlab::Auth.resource_bot_scopes.each do |scope| + context "with a '#{scope}' scoped token" do + let(:token) { create(:personal_access_token, scopes: [scope], user: current_user) } + + it_behaves_like 'rotating token denied', :forbidden + end + end + end + end +end -- GitLab From a4daaa501710baf8d7ab2aefa82279e3e7a1091a Mon Sep 17 00:00:00 2001 From: Stephane Talbot Date: Thu, 25 Jan 2024 20:39:57 +0100 Subject: [PATCH 02/12] Exclude project/group token from self-rotation --- lib/api/personal_access_tokens/self_rotation.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/api/personal_access_tokens/self_rotation.rb b/lib/api/personal_access_tokens/self_rotation.rb index af2410e515616a..03c1fd44763da3 100644 --- a/lib/api/personal_access_tokens/self_rotation.rb +++ b/lib/api/personal_access_tokens/self_rotation.rb @@ -25,6 +25,8 @@ class SelfRotation < ::API::Base documentation: { example: '2021-01-31' } end post 'self/rotate' do + forbidden! if current_user.project_bot? + new_token = rotate_token(access_token, declared_params) present new_token, with: Entities::PersonalAccessTokenWithToken -- GitLab From f3d77f0d1b441c31f87863991a14842cc47a8bf9 Mon Sep 17 00:00:00 2001 From: Stephane Talbot Date: Fri, 26 Jan 2024 20:56:26 +0100 Subject: [PATCH 03/12] Add description of new endpoint --- lib/api/personal_access_tokens/self_rotation.rb | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/api/personal_access_tokens/self_rotation.rb b/lib/api/personal_access_tokens/self_rotation.rb index 03c1fd44763da3..b355f0b0a6fd83 100644 --- a/lib/api/personal_access_tokens/self_rotation.rb +++ b/lib/api/personal_access_tokens/self_rotation.rb @@ -14,9 +14,15 @@ class SelfRotation < ::API::Base before { authenticate! } resource :personal_access_tokens do - desc 'Rotate personal access token' do - detail 'Rotates a personal access token.' - success Entities::PersonalAccessTokenWithToken + desc 'Rotate a personal access token' do + detail 'Rotates a personal access token by passing it to the API in a header' + success code: 200, model: Entities::PersonalAccessTokenWithToken + failure [ + { code: 400, message: 'Bad Request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' } + ] + tags %w[personal_access_tokens] end params do optional :expires_at, -- GitLab From 8a4c8f328fc62390d86c590c2515ea6d98c75c2e Mon Sep 17 00:00:00 2001 From: Stephane Talbot Date: Fri, 26 Jan 2024 20:57:01 +0100 Subject: [PATCH 04/12] Document new API --- doc/api/personal_access_tokens.md | 59 +++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/doc/api/personal_access_tokens.md b/doc/api/personal_access_tokens.md index 9d3edbab0ae00b..ef675add638778 100644 --- a/doc/api/personal_access_tokens.md +++ b/doc/api/personal_access_tokens.md @@ -211,6 +211,20 @@ Example response: ## Rotate a personal access token +Rotate a personal access token by either: + +- Using the ID of the personal access token. +- Passing it to the API in a header. + +### Using a personal access token ID + +Rotate a personal access token by either: + +- Using the ID of the personal access token. +- Passing it to the API in a header. + +### Using a personal access token ID + > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/403042) in GitLab 16.0 Rotate a personal access token. Revokes the previous token and creates a new token that expires in one week. @@ -259,6 +273,51 @@ Example response: - Token with the specified ID does not exist. - `404: Not Found` if the user is an administrator but the token with the specified ID does not exist. +### Using a request header + +Rotate a personal access token that is passed in using a request header. +Revokes the previous token and creates a new token that expires in one week. +Requires: + +- `api` scope. + +You can use the `expires_at` parameter to set a different expiry date. This non-default expiry date can be up to a maximum of one year from the rotation date. + +```plaintext +POST /personal_access_tokens/self/rotate +``` + +```shell +curl --request POST --header "PRIVATE-TOKEN: " "https://gitlab.example.com/api/v4/personal_access_tokens/self/rotate" +``` + +Example response: + +```json +{ + "id": 42, + "name": "Rotated Token", + "revoked": false, + "created_at": "2023-08-01T15:00:00.000Z", + "scopes": ["api"], + "user_id": 1337, + "last_used_at": null, + "active": true, + "expires_at": "2023-08-15", + "token": "s3cr3t" +} +``` + +### Responses + +- `200: OK` if the existing token is successfully revoked and the new token successfully created. +- `400: Bad Request` if not rotated successfully. +- `401: Unauthorized` if either: + - The token does not exist. + - The token has expired. + - The token has been revoked. +- `403: Forbidden` if the token is not allowed to rotate itself. + ### Automatic reuse detection > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/395352) in GitLab 16.3 -- GitLab From 9b96efb88642c32e5a2a056a32773c53efb7223a Mon Sep 17 00:00:00 2001 From: Jon Glassman Date: Wed, 31 Jan 2024 13:00:20 +0000 Subject: [PATCH 05/12] Apply 1 suggestion(s) to 1 file(s) --- doc/api/personal_access_tokens.md | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/doc/api/personal_access_tokens.md b/doc/api/personal_access_tokens.md index ef675add638778..16b462fdc554a6 100644 --- a/doc/api/personal_access_tokens.md +++ b/doc/api/personal_access_tokens.md @@ -211,19 +211,14 @@ Example response: ## Rotate a personal access token -Rotate a personal access token by either: +Rotate a personal access token. Revokes the previous token and creates a new token that expires in one week -- Using the ID of the personal access token. -- Passing it to the API in a header. - -### Using a personal access token ID +You can either: -Rotate a personal access token by either: +- Use the personal access token ID. +- Pass the personal access token to the API in a request header. -- Using the ID of the personal access token. -- Passing it to the API in a header. - -### Using a personal access token ID +### Use a personal access token ID > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/403042) in GitLab 16.0 -- GitLab From cc519152146fd680bc441cf14e65e370c6c06eda Mon Sep 17 00:00:00 2001 From: Jon Glassman Date: Wed, 31 Jan 2024 13:11:34 +0000 Subject: [PATCH 06/12] Apply 2 suggestion(s) to 1 file(s) --- doc/api/personal_access_tokens.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/doc/api/personal_access_tokens.md b/doc/api/personal_access_tokens.md index 16b462fdc554a6..10a2c066b274d7 100644 --- a/doc/api/personal_access_tokens.md +++ b/doc/api/personal_access_tokens.md @@ -222,7 +222,6 @@ You can either: > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/403042) in GitLab 16.0 -Rotate a personal access token. Revokes the previous token and creates a new token that expires in one week. In GitLab 16.6 and later, you can use the `expires_at` parameter to set a different expiry date. This non-default expiry date can be up to a maximum of one year from the rotation date. @@ -268,10 +267,8 @@ Example response: - Token with the specified ID does not exist. - `404: Not Found` if the user is an administrator but the token with the specified ID does not exist. -### Using a request header +### Use a request header -Rotate a personal access token that is passed in using a request header. -Revokes the previous token and creates a new token that expires in one week. Requires: - `api` scope. -- GitLab From 586fa63cdd9f20695810750c40c92efc75cf9208 Mon Sep 17 00:00:00 2001 From: Stephane Talbot Date: Thu, 1 Feb 2024 13:10:35 +0100 Subject: [PATCH 07/12] Test that endpoint returns 405 with OAuth --- .../self_rotation_spec.rb | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/spec/requests/api/personal_access_tokens/self_rotation_spec.rb b/spec/requests/api/personal_access_tokens/self_rotation_spec.rb index 72b5a1ed0e4fb2..165434f057b735 100644 --- a/spec/requests/api/personal_access_tokens/self_rotation_spec.rb +++ b/spec/requests/api/personal_access_tokens/self_rotation_spec.rb @@ -5,13 +5,16 @@ RSpec.describe API::PersonalAccessTokens::SelfRotation, feature_category: :system_access do let(:path) { '/personal_access_tokens/self/rotate' } let(:token) { create(:personal_access_token, user: current_user) } + let(:oauth_token) { nil } let(:expiry_date) { Date.today + 1.week } let(:params) { {} } let_it_be(:current_user) { create(:user) } describe 'POST /personal_access_tokens/self/rotate' do - subject(:rotate_token) { post(api(path, personal_access_token: token), params: params) } + subject(:rotate_token) do + post(api(path, personal_access_token: token, oauth_access_token: oauth_token), params: params) + end shared_examples 'rotating token succeeds' do it 'rotate token', :aggregate_failures do @@ -110,6 +113,24 @@ it_behaves_like 'rotating token denied', :unauthorized end + + context 'with OAuth token' do + let(:token) { nil } + + context 'with default scope' do + let(:oauth_token) { create(:oauth_access_token) } + + it_behaves_like 'rotating token denied', :method_not_allowed + end + + Gitlab::Auth.all_available_scopes.each do |scope| + context "with a '#{scope}' scoped token" do + let(:oauth_token) { create(:oauth_access_token, scopes: [scope]) } + + it_behaves_like 'rotating token denied', :method_not_allowed + end + end + end end context 'when current_user is a project bot' do -- GitLab From 795a677e728f609c1458ebfdf89c8231309eb04d Mon Sep 17 00:00:00 2001 From: Stephane Talbot Date: Thu, 1 Feb 2024 23:35:37 +0100 Subject: [PATCH 08/12] Force endpoint to returns 405 - Method Not Allowed for oauth_token --- lib/api/personal_access_tokens/self_rotation.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/api/personal_access_tokens/self_rotation.rb b/lib/api/personal_access_tokens/self_rotation.rb index b355f0b0a6fd83..bcd2f9402bc17a 100644 --- a/lib/api/personal_access_tokens/self_rotation.rb +++ b/lib/api/personal_access_tokens/self_rotation.rb @@ -11,7 +11,10 @@ class SelfRotation < ::API::Base allow_access_with_scope :api - before { authenticate! } + before do + not_allowed! unless access_token.is_a? PersonalAccessToken + authenticate! + end resource :personal_access_tokens do desc 'Rotate a personal access token' do -- GitLab From d713db1dab78d7a49c7dab450f3ec3e24039e82b Mon Sep 17 00:00:00 2001 From: Stephane Talbot Date: Fri, 2 Feb 2024 00:18:58 +0100 Subject: [PATCH 09/12] Update api description and documentation --- doc/api/personal_access_tokens.md | 6 +++--- lib/api/personal_access_tokens/self_rotation.rb | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/api/personal_access_tokens.md b/doc/api/personal_access_tokens.md index 10a2c066b274d7..938c37ee3e437a 100644 --- a/doc/api/personal_access_tokens.md +++ b/doc/api/personal_access_tokens.md @@ -222,7 +222,6 @@ You can either: > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/403042) in GitLab 16.0 - In GitLab 16.6 and later, you can use the `expires_at` parameter to set a different expiry date. This non-default expiry date can be up to a maximum of one year from the rotation date. ```plaintext @@ -258,7 +257,7 @@ Example response: } ``` -### Responses +#### Responses - `200: OK` if the existing token is successfully revoked and the new token successfully created. - `400: Bad Request` if not rotated successfully. @@ -300,7 +299,7 @@ Example response: } ``` -### Responses +#### Responses - `200: OK` if the existing token is successfully revoked and the new token successfully created. - `400: Bad Request` if not rotated successfully. @@ -309,6 +308,7 @@ Example response: - The token has expired. - The token has been revoked. - `403: Forbidden` if the token is not allowed to rotate itself. +- `405: Method Not Allowed` if the token is not a personal access token. ### Automatic reuse detection diff --git a/lib/api/personal_access_tokens/self_rotation.rb b/lib/api/personal_access_tokens/self_rotation.rb index bcd2f9402bc17a..65a43cb066c1c1 100644 --- a/lib/api/personal_access_tokens/self_rotation.rb +++ b/lib/api/personal_access_tokens/self_rotation.rb @@ -23,7 +23,8 @@ class SelfRotation < ::API::Base failure [ { code: 400, message: 'Bad Request' }, { code: 401, message: 'Unauthorized' }, - { code: 403, message: 'Forbidden' } + { code: 403, message: 'Forbidden' }, + { code: 405, message: 'Method not allowed' } ] tags %w[personal_access_tokens] end -- GitLab From cfad94c891b7942faa555d835f8459830933b8d6 Mon Sep 17 00:00:00 2001 From: Stephane Talbot Date: Thu, 8 Feb 2024 00:54:58 +0100 Subject: [PATCH 10/12] Add tests with invalid/deploy/job/rotated token --- .../self_rotation_spec.rb | 74 ++++++++++++++----- 1 file changed, 54 insertions(+), 20 deletions(-) diff --git a/spec/requests/api/personal_access_tokens/self_rotation_spec.rb b/spec/requests/api/personal_access_tokens/self_rotation_spec.rb index 165434f057b735..697f8f3a07fb79 100644 --- a/spec/requests/api/personal_access_tokens/self_rotation_spec.rb +++ b/spec/requests/api/personal_access_tokens/self_rotation_spec.rb @@ -5,16 +5,13 @@ RSpec.describe API::PersonalAccessTokens::SelfRotation, feature_category: :system_access do let(:path) { '/personal_access_tokens/self/rotate' } let(:token) { create(:personal_access_token, user: current_user) } - let(:oauth_token) { nil } let(:expiry_date) { Date.today + 1.week } let(:params) { {} } let_it_be(:current_user) { create(:user) } describe 'POST /personal_access_tokens/self/rotate' do - subject(:rotate_token) do - post(api(path, personal_access_token: token, oauth_access_token: oauth_token), params: params) - end + subject(:rotate_token) { post(api(path, personal_access_token: token), params: params) } shared_examples 'rotating token succeeds' do it 'rotate token', :aggregate_failures do @@ -101,38 +98,75 @@ context 'when token is invalid' do let(:current_user) { create(:user) } + let(:token) { instance_double(PersonalAccessToken, token: 'invalidtoken') } + + it_behaves_like 'rotating token denied', :unauthorized + end - context 'with already revoked token' do - let(:token) { create(:personal_access_token, :revoked, user: current_user) } + context 'with a revoked token' do + let(:token) { create(:personal_access_token, :revoked, user: current_user) } - it_behaves_like 'rotating token denied', :unauthorized - end + it_behaves_like 'rotating token denied', :unauthorized + end + + context 'with an expired token' do + let(:token) { create(:personal_access_token, expires_at: 1.day.ago, user: current_user) } + + it_behaves_like 'rotating token denied', :unauthorized + end + + context 'with a rotated token' do + let(:token) { create(:personal_access_token, :revoked, user: current_user) } + let!(:child_token) { create(:personal_access_token, previous_personal_access_token_id: token.id) } - context 'when token is expired' do - let(:token) { create(:personal_access_token, expires_at: 1.day.ago, user: current_user) } + it_behaves_like 'rotating token denied', :unauthorized + + it 'revokes token family' do + rotate_token - it_behaves_like 'rotating token denied', :unauthorized + expect(child_token.reload).to be_revoked end + end - context 'with OAuth token' do - let(:token) { nil } + context 'with an OAuth token' do + subject(:rotate_token) { post(api(path, oauth_access_token: token), params: params) } - context 'with default scope' do - let(:oauth_token) { create(:oauth_access_token) } + context 'with default scope' do + let(:token) { create(:oauth_access_token) } - it_behaves_like 'rotating token denied', :method_not_allowed - end + it_behaves_like 'rotating token denied', :forbidden + end - Gitlab::Auth.all_available_scopes.each do |scope| - context "with a '#{scope}' scoped token" do - let(:oauth_token) { create(:oauth_access_token, scopes: [scope]) } + Gitlab::Auth.all_available_scopes.each do |scope| + context "with a '#{scope}' scoped token" do + let(:token) { create(:oauth_access_token, scopes: [scope]) } + if [Gitlab::Auth::API_SCOPE].include? scope it_behaves_like 'rotating token denied', :method_not_allowed + else + it_behaves_like 'rotating token denied', :forbidden end end end end + context 'with a deploy token' do + let(:token) { create(:deploy_token) } + let(:headers) { { Gitlab::Auth::AuthFinders::DEPLOY_TOKEN_HEADER => token.token } } + + subject(:rotate_token) { post(api(path), params: params, headers: headers) } + + it_behaves_like 'rotating token denied', :unauthorized + end + + context 'with a job token' do + let(:job) { create(:ci_build, :running, user: current_user) } + + subject(:rotate_token) { post(api(path, job_token: job.token), params: params) } + + it_behaves_like 'rotating token denied', :unauthorized + end + context 'when current_user is a project bot' do let(:current_user) { create(:user, :project_bot) } -- GitLab From d6efc9509094ab77d98768c1bd44043d1855aace Mon Sep 17 00:00:00 2001 From: Stephane Talbot Date: Thu, 8 Feb 2024 00:57:40 +0100 Subject: [PATCH 11/12] bugfix: Suppress error 500, for some access tokens --- lib/api/personal_access_tokens/self_rotation.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/api/personal_access_tokens/self_rotation.rb b/lib/api/personal_access_tokens/self_rotation.rb index 65a43cb066c1c1..da21fca8b554be 100644 --- a/lib/api/personal_access_tokens/self_rotation.rb +++ b/lib/api/personal_access_tokens/self_rotation.rb @@ -11,10 +11,7 @@ class SelfRotation < ::API::Base allow_access_with_scope :api - before do - not_allowed! unless access_token.is_a? PersonalAccessToken - authenticate! - end + before { authenticate! } resource :personal_access_tokens do desc 'Rotate a personal access token' do @@ -35,6 +32,7 @@ class SelfRotation < ::API::Base documentation: { example: '2021-01-31' } end post 'self/rotate' do + not_allowed! unless access_token.is_a? PersonalAccessToken forbidden! if current_user.project_bot? new_token = rotate_token(access_token, declared_params) -- GitLab From e0b2aa2418986ae9a14934846a1ab5cdaf2fd75f Mon Sep 17 00:00:00 2001 From: Stephane Talbot Date: Thu, 8 Feb 2024 00:58:52 +0100 Subject: [PATCH 12/12] Add automatic reuse detection for /self/rotate endpoint --- lib/gitlab/auth/auth_finders.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/auth/auth_finders.rb b/lib/gitlab/auth/auth_finders.rb index 25465e73b95d95..b4ba2a8e50ea98 100644 --- a/lib/gitlab/auth/auth_finders.rb +++ b/lib/gitlab/auth/auth_finders.rb @@ -426,7 +426,8 @@ def revoke_token_family(token) end def access_token_rotation_request? - current_request.path.match(%r{access_tokens/\d+/rotate$}) + current_request.path.match(%r{access_tokens/\d+/rotate$}) || + current_request.path.match(%r{/personal_access_tokens/self/rotate$}) end end end -- GitLab