Commit 967b3ddf authored by Max Woolf's avatar Max Woolf Committed by Mike Jang

Add API endpoint to revoke PATs

Adds the DELETE /personal_access_tokens/:id
REST endpoint for users to revoke their own
PATs or admins to do it on their behalf
parent 559a7d21
...@@ -4,7 +4,7 @@ You can read more about [personal access tokens](../user/profile/personal_access ...@@ -4,7 +4,7 @@ You can read more about [personal access tokens](../user/profile/personal_access
## List personal access tokens ## List personal access tokens
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/22726) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.3. > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/227264) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.3.
Get a list of personal access tokens. Get a list of personal access tokens.
...@@ -60,3 +60,29 @@ curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/a ...@@ -60,3 +60,29 @@ curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/a
} }
] ]
``` ```
## Revoke a personal access token
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216004) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.3.
Revoke a personal access token.
```plaintext
DELETE /personal_access_tokens/:id
```
| Attribute | Type | required | Description |
|-----------|---------|----------|---------------------|
| `id` | integer/string | yes | ID of personal access token |
NOTE: **Note:**
Non-administrators can revoke their own tokens. Administrators can revoke tokens of any user.
```shell
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/personal_access_tokens/<personal_access_token_id>"
```
### Responses
- `204: No Content` if successfully revoked.
- `400 Bad Request` if not revoked successfully.
---
title: Add API endpoint to revoke PATs
merge_request: 39072
author:
type: added
...@@ -34,16 +34,31 @@ module API ...@@ -34,16 +34,31 @@ module API
unauthorized! unless Ability.allowed?(current_user, :read_user_personal_access_tokens, user(params[:user_id])) unauthorized! unless Ability.allowed?(current_user, :read_user_personal_access_tokens, user(params[:user_id]))
end end
def find_token(id)
PersonalAccessToken.find(id) || not_found!
end
def authenticate! def authenticate!
unauthorized! unless ::License.feature_available?(:personal_access_token_api_management) unauthorized! unless ::License.feature_available?(:personal_access_token_api_management)
super super
end end
end end
get :personal_access_tokens do resources :personal_access_tokens do
tokens = PersonalAccessTokensFinder.new(finder_params(current_user), current_user).execute get do
tokens = PersonalAccessTokensFinder.new(finder_params(current_user), current_user).execute
present paginate(tokens), with: Entities::PersonalAccessToken
end
delete ':id' do
service = ::PersonalAccessTokens::RevokeService.new(
current_user,
{ token: find_token(params[:id]) }
).execute
present paginate(tokens), with: Entities::PersonalAccessToken service.success? ? no_content! : bad_request!(nil)
end
end end
end end
end end
...@@ -8,80 +8,140 @@ RSpec.describe API::PersonalAccessTokens do ...@@ -8,80 +8,140 @@ RSpec.describe API::PersonalAccessTokens do
let_it_be(:token2) { create(:personal_access_token) } let_it_be(:token2) { create(:personal_access_token) }
let_it_be(:current_user) { create(:user) } let_it_be(:current_user) { create(:user) }
context 'when unlicensed' do describe 'GET /personal_access_tokens' do
before do context 'when unlicensed' do
stub_licensed_features(personal_access_token_api_management: false) before do
end stub_licensed_features(personal_access_token_api_management: false)
end
it 'responds with unauthorized' do it 'responds with unauthorized' do
get api(path, current_user) get api(path, current_user)
expect(response).to have_gitlab_http_status(:unauthorized) expect(response).to have_gitlab_http_status(:unauthorized)
end
end end
end
context 'when licensed' do context 'when licensed' do
before do before do
stub_licensed_features(personal_access_token_api_management: true) stub_licensed_features(personal_access_token_api_management: true)
end end
context 'logged in as an Administrator' do context 'logged in as an Administrator' do
let_it_be(:current_user) { create(:admin) } let_it_be(:current_user) { create(:admin) }
it 'returns all PATs by default' do it 'returns all PATs by default' do
get api(path, current_user) get api(path, current_user)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.count).to eq(PersonalAccessToken.all.count)
end
expect(response).to have_gitlab_http_status(:ok) context 'filtered with user_id parameter' do
expect(json_response.count).to eq(PersonalAccessToken.all.count) it 'returns only PATs belonging to that user' do
get api(path, current_user), params: { user_id: token1.user.id }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.count).to eq(1)
expect(json_response.first['user_id']).to eq(token1.user.id)
end
end
end end
context 'filtered with user_id parameter' do context 'logged in as a non-Administrator' do
it 'returns only PATs belonging to that user' do let_it_be(:current_user) { create(:user) }
get api(path, current_user), params: { user_id: token1.user.id } let_it_be(:user) { create(:user) }
let_it_be(:token) { create(:personal_access_token, user: current_user)}
let_it_be(:other_token) { create(:personal_access_token, user: user) }
it 'returns all PATs belonging to the signed-in user' do
get api(path, current_user, personal_access_token: token)
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(json_response.count).to eq(1) expect(json_response.count).to eq(1)
expect(json_response.first['user_id']).to eq(token1.user.id) expect(json_response.map { |r| r['user_id'] }.uniq).to contain_exactly(current_user.id)
end
context 'filtered with user_id parameter' do
it 'returns PATs belonging to the specific user' do
get api(path, current_user, personal_access_token: token), params: { user_id: current_user.id }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.count).to eq(1)
expect(json_response.map { |r| r['user_id'] }.uniq).to contain_exactly(current_user.id)
end
it 'is unauthorized if filtered by a user other than current_user' do
get api(path, current_user, personal_access_token: token), params: { user_id: user.id }
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
end
context 'not authenticated' do
it 'is forbidden' do
get api(path)
expect(response).to have_gitlab_http_status(:unauthorized)
end end
end end
end end
end
describe 'DELETE /personal_access_tokens/:id' do
let(:path) { "/personal_access_tokens/#{token1.id}" }
context 'logged in as a non-Administrator' do context 'when unlicensed' do
let_it_be(:current_user) { create(:user) } before do
let_it_be(:user) { create(:user) } stub_licensed_features(personal_access_token_api_management: false)
let_it_be(:token) { create(:personal_access_token, user: current_user)} end
let_it_be(:other_token) { create(:personal_access_token, user: user) }
it 'returns all PATs belonging to the signed-in user' do it 'responds with unauthorized' do
get api(path, current_user, personal_access_token: token) delete api(path, current_user)
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:unauthorized)
expect(json_response.count).to eq(1)
expect(json_response.map { |r| r['user_id'] }.uniq).to contain_exactly(current_user.id)
end end
end
context 'filtered with user_id parameter' do context 'when licensed' do
it 'returns PATs belonging to the specific user' do before do
get api(path, current_user, personal_access_token: token), params: { user_id: current_user.id } stub_licensed_features(personal_access_token_api_management: true)
end
expect(response).to have_gitlab_http_status(:ok) context 'when current_user is an administrator', :enable_admin_mode do
expect(json_response.count).to eq(1) let_it_be(:admin_user) { create(:admin) }
expect(json_response.map { |r| r['user_id'] }.uniq).to contain_exactly(current_user.id) let_it_be(:admin_token) { create(:personal_access_token, user: admin_user) }
let_it_be(:admin_path) { "/personal_access_tokens/#{admin_token.id}" }
it 'revokes a different users token' do
delete api(path, admin_user)
expect(response).to have_gitlab_http_status(:no_content)
expect(token1.reload.revoked?).to be true
end end
it 'is unauthorized if filtered by a user other than current_user' do it 'revokes their own token' do
get api(path, current_user, personal_access_token: token), params: { user_id: user.id } delete api(admin_path, admin_user)
expect(response).to have_gitlab_http_status(:unauthorized) expect(response).to have_gitlab_http_status(:no_content)
end end
end end
end
context 'not authenticated' do context 'when current_user is not an administrator' do
it 'is forbidden' do let_it_be(:user_token) { create(:personal_access_token, user: current_user) }
get api(path) let_it_be(:user_token_path) { "/personal_access_tokens/#{user_token.id}" }
expect(response).to have_gitlab_http_status(:unauthorized) it 'fails revokes a different users token' do
delete api(path, current_user)
expect(response).to have_gitlab_http_status(:bad_request)
end
it 'revokes their own token' do
delete api(user_token_path, current_user)
expect(response).to have_gitlab_http_status(:no_content)
end
end end
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment