gitlab-org--gitlab-foss/spec/requests/api/personal_access_tokens_spec.rb

505 lines
21 KiB
Ruby

# frozen_string_literal: true
require 'spec_helper'
RSpec.describe API::PersonalAccessTokens do
let_it_be(:path) { '/personal_access_tokens' }
describe 'GET /personal_access_tokens' do
using RSpec::Parameterized::TableSyntax
def map_id(json_resonse)
json_response.map { |pat| pat['id'] }
end
shared_examples 'response as expected' do |params|
subject { get api(path, personal_access_token: current_users_token), params: params }
it "status, count and result as expected" do
subject
case status
when :bad_request
expect(json_response).to eq(result)
when :ok
expect(map_id(json_response)).to a_collection_containing_exactly(*result)
end
expect(response).to have_gitlab_http_status(status)
expect(json_response.count).to eq(result_count)
end
end
context 'logged in as an Administrator' do
let_it_be(:current_user) { create(:admin) }
let_it_be(:current_users_token) { create(:personal_access_token, user: current_user) }
it 'returns all PATs by default' do
get api(path, current_user)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.count).to eq(PersonalAccessToken.all.count)
end
context 'filtered with user_id parameter' do
let_it_be(:token) { create(:personal_access_token) }
let_it_be(:token_impersonated) { create(:personal_access_token, impersonation: true, user: token.user) }
it 'returns only PATs belonging to that user' do
get api(path, current_user), params: { user_id: token.user.id }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.count).to eq(2)
expect(json_response.first['user_id']).to eq(token.user.id)
expect(json_response.last['id']).to eq(token_impersonated.id)
end
end
context 'filter with revoked parameter' do
let_it_be(:revoked_token) { create(:personal_access_token, revoked: true) }
let_it_be(:not_revoked_token1) { create(:personal_access_token, revoked: false) }
let_it_be(:not_revoked_token2) { create(:personal_access_token, revoked: false) }
where(:revoked, :status, :result_count, :result) do
true | :ok | 1 | lazy { [revoked_token.id] }
false | :ok | 3 | lazy { [not_revoked_token1.id, not_revoked_token2.id, current_users_token.id] }
'asdf' | :bad_request | 1 | { "error" => "revoked is invalid" }
end
with_them do
it_behaves_like 'response as expected', revoked: params[:revoked]
end
end
context 'filter with active parameter' do
let_it_be(:inactive_token1) { create(:personal_access_token, revoked: true) }
let_it_be(:inactive_token2) { create(:personal_access_token, expires_at: Time.new(2022, 01, 01, 00, 00, 00)) }
let_it_be(:active_token) { create(:personal_access_token) }
where(:state, :status, :result_count, :result) do
'inactive' | :ok | 2 | lazy { [inactive_token1.id, inactive_token2.id] }
'active' | :ok | 2 | lazy { [active_token.id, current_users_token.id] }
'asdf' | :bad_request | 1 | { "error" => "state does not have a valid value" }
end
with_them do
it_behaves_like 'response as expected', state: params[:state]
end
end
context 'filter with created parameter' do
let_it_be(:token1) { create(:personal_access_token, created_at: DateTime.new(2022, 01, 01, 12, 30, 25)) }
context 'test created_before' do
where(:created_at, :status, :result_count, :result) do
'2022-01-02' | :ok | 1 | lazy { [token1.id] }
'2022-01-01' | :ok | 0 | lazy { [] }
'2022-01-01T12:30:24' | :ok | 0 | lazy { [] }
'2022-01-01T12:30:25' | :ok | 1 | lazy { [token1.id] }
'2022-01-01T:12:30:26' | :ok | 1 | lazy { [token1.id] }
'asdf' | :bad_request | 1 | { "error" => "created_before is invalid" }
end
with_them do
it_behaves_like 'response as expected', created_before: params[:created_at]
end
end
context 'test created_after' do
where(:created_at, :status, :result_count, :result) do
'2022-01-03' | :ok | 1 | lazy { [current_users_token.id] }
'2022-01-01' | :ok | 2 | lazy { [token1.id, current_users_token.id] }
'2022-01-01T12:30:25' | :ok | 2 | lazy { [token1.id, current_users_token.id] }
'2022-01-01T12:30:26' | :ok | 1 | lazy { [current_users_token.id] }
(DateTime.now + 1).to_s | :ok | 0 | lazy { [] }
'asdf' | :bad_request | 1 | { "error" => "created_after is invalid" }
end
with_them do
it_behaves_like 'response as expected', created_after: params[:created_at]
end
end
end
context 'filter with last_used parameter' do
let_it_be(:token1) { create(:personal_access_token, last_used_at: DateTime.new(2022, 01, 01, 12, 30, 25)) }
let_it_be(:never_used_token) { create(:personal_access_token) }
context 'test last_used_before' do
where(:last_used_at, :status, :result_count, :result) do
'2022-01-02' | :ok | 1 | lazy { [token1.id] }
'2022-01-01' | :ok | 0 | lazy { [] }
'2022-01-01T12:30:24' | :ok | 0 | lazy { [] }
'2022-01-01T12:30:25' | :ok | 1 | lazy { [token1.id] }
'2022-01-01T12:30:26' | :ok | 1 | lazy { [token1.id] }
'asdf' | :bad_request | 1 | { "error" => "last_used_before is invalid" }
end
with_them do
it_behaves_like 'response as expected', last_used_before: params[:last_used_at]
end
end
context 'test last_used_after' do
where(:last_used_at, :status, :result_count, :result) do
'2022-01-03' | :ok | 1 | lazy { [current_users_token.id] }
'2022-01-01' | :ok | 2 | lazy { [token1.id, current_users_token.id] }
'2022-01-01T12:30:26' | :ok | 1 | lazy { [current_users_token.id] }
'2022-01-01T12:30:25' | :ok | 2 | lazy { [token1.id, current_users_token.id] }
(DateTime.now + 1).to_s | :ok | 0 | lazy { [] }
'asdf' | :bad_request | 1 | { "error" => "last_used_after is invalid" }
end
with_them do
it_behaves_like 'response as expected', last_used_after: params[:last_used_at]
end
end
end
context 'filter with search parameter' do
let_it_be(:token1) { create(:personal_access_token, name: 'test_1') }
let_it_be(:token2) { create(:personal_access_token, name: 'test_2') }
let_it_be(:token3) { create(:personal_access_token, name: '') }
where(:pattern, :status, :result_count, :result) do
'test' | :ok | 2 | lazy { [token1.id, token2.id] }
'' | :ok | 4 | lazy { [token1.id, token2.id, token3.id, current_users_token.id] }
'test_1' | :ok | 1 | lazy { [token1.id] }
'asdf' | :ok | 0 | lazy { [] }
end
with_them do
it_behaves_like 'response as expected', search: params[:pattern]
end
end
context 'filter created_before/created_after combined with last_used_before/last_used_after' do
let_it_be(:date) { DateTime.new(2022, 01, 02) }
let_it_be(:token1) { create(:personal_access_token, created_at: date, last_used_at: date) }
where(:date_before, :date_after, :status, :result_count, :result) do
'2022-01-03' | '2022-01-01' | :ok | 1 | lazy { [token1.id] }
'2022-01-01' | '2022-01-03' | :ok | 0 | lazy { [] }
'2022-01-03' | nil | :ok | 1 | lazy { [token1.id] }
nil | '2022-01-01' | :ok | 2 | lazy { [token1.id, current_users_token.id] }
end
with_them do
it_behaves_like 'response as expected', { created_before: params[:date_before],
created_after: params[:date_after] }
it_behaves_like 'response as expected', { last_used_before: params[:date_before],
last_used_after: params[:date_after] }
end
end
context 'filter created_before and created_after combined is valid' do
let_it_be(:token1) { create(:personal_access_token, created_at: DateTime.new(2022, 01, 02)) }
where(:created_before, :created_after, :status, :result) do
'2022-01-02' | '2022-01-02' | :ok | lazy { [token1.id] }
'2022-01-03' | '2022-01-01' | :ok | lazy { [token1.id] }
'2022-01-01' | '2022-01-03' | :ok | lazy { [] }
'2022-01-03' | nil | :ok | lazy { [token1.id] }
nil | '2022-01-01' | :ok | lazy { [token1.id] }
end
with_them do
it "returns all valid tokens" do
get api(path, personal_access_token: current_users_token),
params: { created_before: created_before, created_after: created_after }
expect(response).to have_gitlab_http_status(status)
expect(json_response.map { |pat| pat['id'] }).to include(*result) if status == :ok
end
end
end
context 'filter last_used_before and last_used_after combined is valid' do
let_it_be(:token1) { create(:personal_access_token, last_used_at: DateTime.new(2022, 01, 02)) }
where(:last_used_before, :last_used_after, :status, :result) do
'2022-01-02' | '2022-01-02' | :ok | lazy { [token1.id] }
'2022-01-03' | '2022-01-01' | :ok | lazy { [token1.id] }
'2022-01-01' | '2022-01-03' | :ok | lazy { [] }
'2022-01-03' | nil | :ok | lazy { [token1.id] }
nil | '2022-01-01' | :ok | lazy { [token1.id] }
end
with_them do
it "returns all valid tokens" do
get api(path, personal_access_token: current_users_token),
params: { last_used_before: last_used_before, last_used_after: last_used_after }
expect(response).to have_gitlab_http_status(status)
expect(json_response.map { |pat| pat['id'] }).to include(*result) if status == :ok
end
end
end
end
context 'logged in as a non-Administrator' do
let_it_be(:current_user) { create(:user) }
let_it_be(:current_users_token) { create(:personal_access_token, user: current_user) }
it 'returns all PATs belonging to the signed-in user' do
get api(path, personal_access_token: current_users_token)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.count).to eq(1)
expect(json_response.map { |r| r['id'] }.uniq).to contain_exactly(current_users_token.id)
expect(json_response.map { |r| r['user_id'] }.uniq).to contain_exactly(current_user.id)
end
context 'filtered with user_id parameter' do
let_it_be(:user) { create(:user) }
it 'returns PATs belonging to the specific user' do
get api(path, current_user, personal_access_token: current_users_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['id'] }.uniq).to contain_exactly(current_users_token.id)
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: current_users_token), params: { user_id: user.id }
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
context 'filter with revoked parameter' do
let_it_be(:users_revoked_token) { create(:personal_access_token, revoked: true, user: current_user) }
let_it_be(:not_revoked_token) { create(:personal_access_token, revoked: false) }
let_it_be(:oter_revoked_token) { create(:personal_access_token, revoked: true) }
where(:revoked, :status, :result_count, :result) do
true | :ok | 1 | lazy { [users_revoked_token.id] }
false | :ok | 1 | lazy { [current_users_token.id] }
end
with_them do
it_behaves_like 'response as expected', revoked: params[:revoked]
end
end
context 'filter with active parameter' do
let_it_be(:users_inactive_token) { create(:personal_access_token, revoked: true, user: current_user) }
let_it_be(:inactive_token) { create(:personal_access_token, expires_at: Time.new(2022, 01, 01, 00, 00, 00)) }
let_it_be(:other_active_token) { create(:personal_access_token) }
where(:state, :status, :result_count, :result) do
'inactive' | :ok | 1 | lazy { [users_inactive_token.id] }
'active' | :ok | 1 | lazy { [current_users_token.id] }
end
with_them do
it_behaves_like 'response as expected', state: params[:state]
end
end
# The created_before filter has been extensively tested in the 'logged in as administrator' section.
# Here it is only tested whether PATs to which the user has no access right are excluded from the filter function.
context 'filter with created parameter' do
let_it_be(:token1) do
create(:personal_access_token, created_at: DateTime.new(2022, 01, 02, 12, 30, 25), user: current_user)
end
let_it_be(:token2) { create(:personal_access_token, created_at: DateTime.new(2022, 01, 02, 12, 30, 25)) }
let_it_be(:status) { :ok }
context 'created_before' do
let_it_be(:result_count) { 1 }
let_it_be(:result) { [token1.id] }
it_behaves_like 'response as expected', created_before: '2022-01-03'
end
context 'created_after' do
let_it_be(:result_count) { 2 }
let_it_be(:result) { [token1.id, current_users_token.id] }
it_behaves_like 'response as expected', created_after: '2022-01-01'
end
end
# The last_used_before filter has been extensively tested in the 'logged in as administrator' section.
# Here it is only tested whether PATs to which the user has no access right are excluded from the filter function.
context 'filter with last_used' do
let_it_be(:token1) do
create(:personal_access_token, last_used_at: DateTime.new(2022, 01, 01, 12, 30, 25), user: current_user)
end
let_it_be(:token2) { create(:personal_access_token, last_used_at: DateTime.new(2022, 01, 01, 12, 30, 25)) }
let_it_be(:never_used_token) { create(:personal_access_token) }
let_it_be(:status) { :ok }
context 'last_used_before' do
let_it_be(:result_count) { 1 }
let_it_be(:result) { [token1.id] }
it_behaves_like 'response as expected', last_used_before: '2022-01-02'
end
context 'last_used_after' do
let_it_be(:result_count) { 2 }
let_it_be(:result) { [token1.id, current_users_token.id] }
it_behaves_like 'response as expected', last_used_after: '2022-01-01'
end
end
# The search filter has been extensively tested in the 'logged in as administrator' section.
# Here it is only tested whether PATs to which the user has no access right are excluded from the filter function.
context 'filter with search parameter' do
let_it_be(:token1) { create(:personal_access_token, name: 'test_1', user: current_user) }
let_it_be(:token2) { create(:personal_access_token, name: 'test_1') }
let_it_be(:token3) { create(:personal_access_token, name: '') }
where(:pattern, :status, :result_count, :result) do
'test' | :ok | 1 | lazy { [token1.id] }
'' | :ok | 2 | lazy { [token1.id, current_users_token.id] }
'test_1' | :ok | 1 | lazy { [token1.id] }
end
with_them do
it_behaves_like 'response as expected', search: params[:pattern]
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
describe 'GET /personal_access_tokens/:id' do
let_it_be(:current_user) { create(:user) }
let_it_be(:user_token) { create(:personal_access_token, user: current_user) }
let_it_be(:token1) { create(:personal_access_token) }
let_it_be(:user_read_only_token) { create(:personal_access_token, scopes: ['read_repository'], user: current_user) }
let_it_be(:user_token_path) { "/personal_access_tokens/#{user_token.id}" }
let_it_be(:invalid_path) { "/personal_access_tokens/#{non_existing_record_id}" }
context 'when current_user is an administrator', :enable_admin_mode do
let_it_be(:admin_user) { create(:admin) }
let_it_be(:admin_token) { create(:personal_access_token, user: admin_user) }
let_it_be(:admin_path) { "/personal_access_tokens/#{admin_token.id}" }
it 'returns admins own PAT by id' do
get api(admin_path, admin_user)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['id']).to eq(admin_token.id)
end
it 'returns a different users PAT by id' do
get api(user_token_path, admin_user)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['id']).to eq(user_token.id)
end
it 'fails to return PAT because no PAT exists with this id' do
get api(invalid_path, admin_user)
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when current_user is not an administrator' do
let_it_be(:other_users_path) { "/personal_access_tokens/#{token1.id}" }
it 'returns users own PAT by id' do
get api(user_token_path, current_user)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['id']).to eq(user_token.id)
end
it 'fails to return other users PAT by id' do
get api(other_users_path, current_user)
expect(response).to have_gitlab_http_status(:unauthorized)
end
it 'fails to return PAT because no PAT exists with this id' do
get api(invalid_path, current_user)
expect(response).to have_gitlab_http_status(:unauthorized)
end
it 'fails to return own PAT by id with read_repository token' do
get api(user_token_path, current_user, personal_access_token: user_read_only_token)
expect(response).to have_gitlab_http_status(:forbidden)
end
end
end
describe 'DELETE /personal_access_tokens/:id' do
let_it_be(:current_user) { create(:user) }
let_it_be(:token1) { create(:personal_access_token) }
let(:path) { "/personal_access_tokens/#{token1.id}" }
context 'when current_user is an administrator', :enable_admin_mode do
let_it_be(:admin_user) { create(:admin) }
let_it_be(:admin_token) { create(:personal_access_token, user: admin_user) }
let_it_be(:admin_path) { "/personal_access_tokens/#{admin_token.id}" }
let_it_be(:admin_read_only_token) do
create(:personal_access_token, scopes: ['read_repository'], user: admin_user)
end
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
it 'revokes their own token' do
delete api(admin_path, admin_user)
expect(response).to have_gitlab_http_status(:no_content)
end
it 'fails to revoke a different user token using a readonly scope' do
delete api(path, personal_access_token: admin_read_only_token)
expect(token1.reload.revoked?).to be false
end
end
context 'when current_user is not an administrator' do
let_it_be(:user_token) { create(:personal_access_token, user: current_user) }
let_it_be(:user_token_path) { "/personal_access_tokens/#{user_token.id}" }
let_it_be(:token_impersonated) { create(:personal_access_token, impersonation: true, user: current_user) }
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
it 'cannot revoke impersonation token' do
delete api("/personal_access_tokens/#{token_impersonated.id}", current_user)
expect(response).to have_gitlab_http_status(:bad_request)
end
end
end
end