debian-mirror-gitlab/spec/requests/api/helpers_spec.rb

595 lines
18 KiB
Ruby
Raw Normal View History

2019-12-26 22:10:19 +05:30
# frozen_string_literal: true
2017-01-15 13:20:01 +05:30
require 'spec_helper'
2018-03-17 18:26:18 +05:30
require 'raven/transports/dummy'
require_relative '../../../config/initializers/sentry'
2017-01-15 13:20:01 +05:30
2020-07-28 23:09:34 +05:30
RSpec.describe API::Helpers do
2017-08-17 22:00:37 +05:30
include API::APIGuard::HelperMethods
include described_class
2018-10-15 14:42:47 +05:30
include TermsHelper
2017-01-15 13:20:01 +05:30
2021-01-03 14:25:43 +05:30
let_it_be(:user, reload: true) { create(:user) }
2017-01-15 13:20:01 +05:30
let(:admin) { create(:admin) }
let(:key) { create(:key, user: user) }
2017-09-10 17:25:29 +05:30
let(:csrf_token) { SecureRandom.base64(ActionController::RequestForgeryProtection::AUTHENTICITY_TOKEN_LENGTH) }
let(:env) do
{
'rack.input' => '',
'rack.session' => {
_csrf_token: csrf_token
},
2018-03-17 18:26:18 +05:30
'REQUEST_METHOD' => 'GET',
'CONTENT_TYPE' => 'text/plain;charset=utf-8'
2017-09-10 17:25:29 +05:30
}
end
2020-10-24 23:57:45 +05:30
2017-08-17 22:00:37 +05:30
let(:header) { }
2018-03-17 18:26:18 +05:30
let(:request) { Grape::Request.new(env)}
let(:params) { request.params }
2017-01-15 13:20:01 +05:30
2017-09-10 17:25:29 +05:30
before do
allow_any_instance_of(self.class).to receive(:options).and_return({})
end
2017-01-15 13:20:01 +05:30
def warden_authenticate_returns(value)
warden = double("warden", authenticate: value)
env['warden'] = warden
end
2017-08-17 22:00:37 +05:30
def error!(message, status, header)
2020-11-24 15:15:51 +05:30
raise StandardError.new("#{status} - #{message}")
2017-01-15 13:20:01 +05:30
end
2018-03-17 18:26:18 +05:30
def set_param(key, value)
request.update_param(key, value)
end
2017-01-15 13:20:01 +05:30
describe ".current_user" do
subject { current_user }
2017-09-10 17:25:29 +05:30
describe "Warden authentication", :allow_forgery_protection do
2017-01-15 13:20:01 +05:30
context "with invalid credentials" do
context "GET request" do
2017-09-10 17:25:29 +05:30
before do
env['REQUEST_METHOD'] = 'GET'
end
2017-01-15 13:20:01 +05:30
it { is_expected.to be_nil }
end
end
context "with valid credentials" do
2017-09-10 17:25:29 +05:30
before do
warden_authenticate_returns user
end
2017-01-15 13:20:01 +05:30
context "GET request" do
2017-09-10 17:25:29 +05:30
before do
env['REQUEST_METHOD'] = 'GET'
end
2017-01-15 13:20:01 +05:30
it { is_expected.to eq(user) }
2018-03-17 18:26:18 +05:30
it 'sets the environment with data of the current user' do
subject
expect(env[API::Helpers::API_USER_ENV]).to eq({ user_id: subject.id, username: subject.username })
end
2017-01-15 13:20:01 +05:30
end
context "HEAD request" do
2017-09-10 17:25:29 +05:30
before do
env['REQUEST_METHOD'] = 'HEAD'
end
2017-01-15 13:20:01 +05:30
it { is_expected.to eq(user) }
2020-09-03 11:15:55 +05:30
context 'when user should have 2fa enabled' do
before do
allow(user).to receive(:require_two_factor_authentication_from_group?).and_return(true)
allow_next_instance_of(Gitlab::Auth::TwoFactorAuthVerifier) do |verifier|
allow(verifier).to receive(:two_factor_grace_period_expired?).and_return(true)
end
end
context 'when 2fa is not enabled' do
it { is_expected.to be_nil }
end
context 'when 2fa is enabled' do
before do
allow(user).to receive(:two_factor_enabled?).and_return(true)
end
it { is_expected.to eq(user) }
end
end
2017-01-15 13:20:01 +05:30
end
context "PUT request" do
2017-09-10 17:25:29 +05:30
before do
env['REQUEST_METHOD'] = 'PUT'
end
context 'without CSRF token' do
it { is_expected.to be_nil }
end
context 'with CSRF token' do
before do
env['HTTP_X_CSRF_TOKEN'] = csrf_token
end
it { is_expected.to eq(user) }
end
2017-01-15 13:20:01 +05:30
end
context "POST request" do
2017-09-10 17:25:29 +05:30
before do
env['REQUEST_METHOD'] = 'POST'
end
context 'without CSRF token' do
it { is_expected.to be_nil }
end
context 'with CSRF token' do
before do
env['HTTP_X_CSRF_TOKEN'] = csrf_token
end
it { is_expected.to eq(user) }
end
2017-01-15 13:20:01 +05:30
end
context "DELETE request" do
2017-09-10 17:25:29 +05:30
before do
env['REQUEST_METHOD'] = 'DELETE'
end
context 'without CSRF token' do
it { is_expected.to be_nil }
end
context 'with CSRF token' do
before do
env['HTTP_X_CSRF_TOKEN'] = csrf_token
end
it { is_expected.to eq(user) }
end
2017-01-15 13:20:01 +05:30
end
end
end
describe "when authenticating using a user's personal access tokens" do
let(:personal_access_token) { create(:personal_access_token, user: user) }
2018-03-17 18:26:18 +05:30
it "returns a 401 response for an invalid token" do
2020-01-01 13:55:28 +05:30
env[Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_HEADER] = 'invalid token'
2017-08-17 22:00:37 +05:30
2018-03-17 18:26:18 +05:30
expect { current_user }.to raise_error /401/
2017-01-15 13:20:01 +05:30
end
2018-03-17 18:26:18 +05:30
it "returns a 403 response for a user without access" do
2020-01-01 13:55:28 +05:30
env[Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token
2017-01-15 13:20:01 +05:30
allow_any_instance_of(Gitlab::UserAccess).to receive(:allowed?).and_return(false)
2017-08-17 22:00:37 +05:30
2018-03-17 18:26:18 +05:30
expect { current_user }.to raise_error /403/
2017-08-17 22:00:37 +05:30
end
2018-03-17 18:26:18 +05:30
it 'returns a 403 response for a user who is blocked' do
user.block!
2020-01-01 13:55:28 +05:30
env[Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token
2017-08-17 22:00:37 +05:30
2018-03-17 18:26:18 +05:30
expect { current_user }.to raise_error /403/
2017-01-15 13:20:01 +05:30
end
2018-10-15 14:42:47 +05:30
context 'when terms are enforced' do
before do
enforce_terms
2020-01-01 13:55:28 +05:30
env[Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token
2018-10-15 14:42:47 +05:30
end
it 'returns a 403 when a user has not accepted the terms' do
2018-11-08 19:23:39 +05:30
expect { current_user }.to raise_error /must accept the Terms of Service/
2018-10-15 14:42:47 +05:30
end
it 'sets the current user when the user accepted the terms' do
accept_terms(user)
expect(current_user).to eq(user)
end
end
2017-01-15 13:20:01 +05:30
2018-03-17 18:26:18 +05:30
it "sets current_user" do
2020-01-01 13:55:28 +05:30
env[Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token
2017-01-15 13:20:01 +05:30
expect(current_user).to eq(user)
2018-03-17 18:26:18 +05:30
end
2017-08-17 22:00:37 +05:30
2018-03-17 18:26:18 +05:30
it "does not allow tokens without the appropriate scope" do
personal_access_token = create(:personal_access_token, user: user, scopes: ['read_user'])
2020-01-01 13:55:28 +05:30
env[Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token
2018-03-17 18:26:18 +05:30
expect { current_user }.to raise_error Gitlab::Auth::InsufficientScopeError
2017-01-15 13:20:01 +05:30
end
it 'does not allow revoked tokens' do
personal_access_token.revoke!
2020-01-01 13:55:28 +05:30
env[Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token
2017-08-17 22:00:37 +05:30
2018-03-17 18:26:18 +05:30
expect { current_user }.to raise_error Gitlab::Auth::RevokedError
2017-01-15 13:20:01 +05:30
end
it 'does not allow expired tokens' do
2018-11-18 11:00:15 +05:30
personal_access_token.update!(expires_at: 1.day.ago)
2020-01-01 13:55:28 +05:30
env[Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token
2017-01-15 13:20:01 +05:30
2018-03-17 18:26:18 +05:30
expect { current_user }.to raise_error Gitlab::Auth::ExpiredError
2017-01-15 13:20:01 +05:30
end
2019-02-15 15:39:39 +05:30
context 'when impersonation is disabled' do
let(:personal_access_token) { create(:personal_access_token, :impersonation, user: user) }
before do
stub_config_setting(impersonation_enabled: false)
2020-01-01 13:55:28 +05:30
env[Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token
2019-02-15 15:39:39 +05:30
end
it 'does not allow impersonation tokens' do
expect { current_user }.to raise_error Gitlab::Auth::ImpersonationDisabled
end
end
2017-01-15 13:20:01 +05:30
end
2021-01-03 14:25:43 +05:30
describe "when authenticating using a job token" do
let_it_be(:job, reload: true) do
create(:ci_build, user: user, status: :running)
end
let(:route_authentication_setting) { {} }
before do
allow_any_instance_of(self.class).to receive(:route_authentication_setting)
.and_return(route_authentication_setting)
end
context 'when route is allowed to be authenticated' do
let(:route_authentication_setting) { { job_token_allowed: true } }
it "returns a 401 response for an invalid token" do
env[Gitlab::Auth::AuthFinders::JOB_TOKEN_HEADER] = 'invalid token'
expect { current_user }.to raise_error /401/
end
it "returns a 401 response for a job that's not running" do
job.update!(status: :success)
env[Gitlab::Auth::AuthFinders::JOB_TOKEN_HEADER] = job.token
expect { current_user }.to raise_error /401/
end
it "returns a 403 response for a user without access" do
env[Gitlab::Auth::AuthFinders::JOB_TOKEN_HEADER] = job.token
allow_any_instance_of(Gitlab::UserAccess).to receive(:allowed?).and_return(false)
expect { current_user }.to raise_error /403/
end
it 'returns a 403 response for a user who is blocked' do
user.block!
env[Gitlab::Auth::AuthFinders::JOB_TOKEN_HEADER] = job.token
expect { current_user }.to raise_error /403/
end
it "sets current_user" do
env[Gitlab::Auth::AuthFinders::JOB_TOKEN_HEADER] = job.token
expect(current_user).to eq(user)
end
end
context 'when route is not allowed to be authenticated' do
let(:route_authentication_setting) { { job_token_allowed: false } }
it "sets current_user to nil" do
env[Gitlab::Auth::AuthFinders::JOB_TOKEN_HEADER] = job.token
allow_any_instance_of(Gitlab::UserAccess).to receive(:allowed?).and_return(true)
expect(current_user).to be_nil
end
end
end
2017-01-15 13:20:01 +05:30
end
describe '.handle_api_exception' do
before do
allow_any_instance_of(self.class).to receive(:rack_response)
2019-02-15 15:39:39 +05:30
2019-09-30 21:07:59 +05:30
stub_sentry_settings
2020-01-01 13:55:28 +05:30
expect(Gitlab::ErrorTracking).to receive(:sentry_dsn).and_return(Gitlab.config.sentry.dsn)
Gitlab::ErrorTracking.configure
2019-02-15 15:39:39 +05:30
Raven.client.configuration.encoding = 'json'
2017-01-15 13:20:01 +05:30
end
it 'does not report a MethodNotAllowed exception to Sentry' do
exception = Grape::Exceptions::MethodNotAllowed.new({ 'X-GitLab-Test' => '1' })
allow(exception).to receive(:backtrace).and_return(caller)
expect(Raven).not_to receive(:capture_exception).with(exception)
handle_api_exception(exception)
end
it 'does report RuntimeError to Sentry' do
exception = RuntimeError.new('test error')
allow(exception).to receive(:backtrace).and_return(caller)
2019-09-04 21:01:54 +05:30
expect(Raven).to receive(:capture_exception).with(exception, tags:
a_hash_including(correlation_id: 'new-correlation-id'), extra: {})
2017-01-15 13:20:01 +05:30
2019-07-31 22:56:46 +05:30
Labkit::Correlation::CorrelationId.use_id('new-correlation-id') do
2019-02-15 15:39:39 +05:30
handle_api_exception(exception)
end
2017-01-15 13:20:01 +05:30
end
2018-03-17 18:26:18 +05:30
context 'with a personal access token given' do
let(:token) { create(:personal_access_token, scopes: ['api'], user: user) }
2019-12-04 20:38:33 +05:30
# Regression test for https://gitlab.com/gitlab-org/gitlab-foss/issues/38571
2018-03-17 18:26:18 +05:30
it 'does not raise an additional exception because of missing `request`' do
# We need to stub at a lower level than #sentry_enabled? otherwise
# Sentry is not enabled when the request below is made, and the test
# would pass even without the fix
expect(ProjectsFinder).to receive(:new).and_raise('Runtime Error!')
get api('/projects', personal_access_token: token)
# The 500 status is expected as we're testing a case where an exception
# is raised, but Grape shouldn't raise an additional exception
2020-04-08 14:13:33 +05:30
expect(response).to have_gitlab_http_status(:internal_server_error)
2018-03-17 18:26:18 +05:30
expect(json_response['message']).not_to include("undefined local variable or method `request'")
expect(json_response['message']).to start_with("\nRuntimeError (Runtime Error!):")
end
end
context 'extra information' do
# Sentry events are an array of the form [auth_header, data, options]
let(:event_data) { Raven.client.transport.events.first[1] }
it 'sends the params, excluding confidential values' do
expect(ProjectsFinder).to receive(:new).and_raise('Runtime Error!')
2019-02-15 15:39:39 +05:30
get api('/projects', user), params: { password: 'dont_send_this', other_param: 'send_this' }
2018-03-17 18:26:18 +05:30
expect(event_data).to include('other_param=send_this')
expect(event_data).to include('password=********')
end
end
2017-01-15 13:20:01 +05:30
end
2017-08-17 22:00:37 +05:30
describe '.authenticate_non_get!' do
%w[HEAD GET].each do |method_name|
context "method is #{method_name}" do
before do
expect_any_instance_of(self.class).to receive(:route).and_return(double(request_method: method_name))
end
it 'does not raise an error' do
expect_any_instance_of(self.class).not_to receive(:authenticate!)
expect { authenticate_non_get! }.not_to raise_error
end
end
end
%w[POST PUT PATCH DELETE].each do |method_name|
context "method is #{method_name}" do
before do
expect_any_instance_of(self.class).to receive(:route).and_return(double(request_method: method_name))
end
it 'calls authenticate!' do
expect_any_instance_of(self.class).to receive(:authenticate!)
authenticate_non_get!
end
end
end
end
describe '.authenticate!' do
context 'current_user is nil' do
before do
expect_any_instance_of(self.class).to receive(:current_user).and_return(nil)
end
it 'returns a 401 response' do
2018-03-17 18:26:18 +05:30
expect { authenticate! }.to raise_error /401/
2020-05-24 23:13:21 +05:30
expect(env[described_class::API_RESPONSE_STATUS_CODE]).to eq(401)
2017-08-17 22:00:37 +05:30
end
end
context 'current_user is present' do
let(:user) { build(:user) }
before do
2018-03-17 18:26:18 +05:30
expect_any_instance_of(self.class).to receive(:current_user).and_return(user)
2017-08-17 22:00:37 +05:30
end
it 'does not raise an error' do
expect { authenticate! }.not_to raise_error
2020-05-24 23:13:21 +05:30
expect(env[described_class::API_RESPONSE_STATUS_CODE]).to be_nil
2017-08-17 22:00:37 +05:30
end
end
2018-03-17 18:26:18 +05:30
end
2017-08-17 22:00:37 +05:30
2018-03-17 18:26:18 +05:30
context 'sudo' do
2019-12-21 20:55:43 +05:30
include_context 'custom session'
2018-03-17 18:26:18 +05:30
shared_examples 'successful sudo' do
it 'sets current_user' do
expect(current_user).to eq(user)
end
it 'sets sudo?' do
expect(sudo?).to be_truthy
end
end
shared_examples 'sudo' do
context 'when admin' do
before do
token.user = admin
token.save!
end
context 'when token has sudo scope' do
before do
token.scopes = %w[sudo]
token.save!
end
context 'when user exists' do
context 'when using header' do
context 'when providing username' do
before do
env[API::Helpers::SUDO_HEADER] = user.username
end
it_behaves_like 'successful sudo'
end
2018-12-13 13:39:08 +05:30
context 'when providing username (case insensitive)' do
before do
env[API::Helpers::SUDO_HEADER] = user.username.upcase
end
it_behaves_like 'successful sudo'
end
2018-03-17 18:26:18 +05:30
context 'when providing user ID' do
before do
env[API::Helpers::SUDO_HEADER] = user.id.to_s
end
it_behaves_like 'successful sudo'
end
end
context 'when using param' do
context 'when providing username' do
before do
set_param(API::Helpers::SUDO_PARAM, user.username)
end
it_behaves_like 'successful sudo'
end
2018-12-13 13:39:08 +05:30
context 'when providing username (case insensitive)' do
before do
set_param(API::Helpers::SUDO_PARAM, user.username.upcase)
end
it_behaves_like 'successful sudo'
end
2018-03-17 18:26:18 +05:30
context 'when providing user ID' do
before do
set_param(API::Helpers::SUDO_PARAM, user.id.to_s)
end
it_behaves_like 'successful sudo'
end
end
end
context 'when user does not exist' do
before do
set_param(API::Helpers::SUDO_PARAM, 'nonexistent')
end
it 'raises an error' do
expect { current_user }.to raise_error /User with ID or username 'nonexistent' Not Found/
end
end
end
context 'when token does not have sudo scope' do
before do
token.scopes = %w[api]
token.save!
set_param(API::Helpers::SUDO_PARAM, user.id.to_s)
end
it 'raises an error' do
expect { current_user }.to raise_error Gitlab::Auth::InsufficientScopeError
end
end
end
context 'when not admin' do
before do
token.user = user
token.save!
set_param(API::Helpers::SUDO_PARAM, user.id.to_s)
end
it 'raises an error' do
expect { current_user }.to raise_error /Must be admin to use sudo/
end
end
end
context 'using an OAuth token' do
let(:token) { create(:oauth_access_token) }
2017-08-17 22:00:37 +05:30
before do
2018-03-17 18:26:18 +05:30
env['HTTP_AUTHORIZATION'] = "Bearer #{token.token}"
2017-08-17 22:00:37 +05:30
end
2018-03-17 18:26:18 +05:30
it_behaves_like 'sudo'
end
2017-08-17 22:00:37 +05:30
2018-03-17 18:26:18 +05:30
context 'using a personal access token' do
let(:token) { create(:personal_access_token) }
context 'passed as param' do
before do
2020-01-01 13:55:28 +05:30
set_param(Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_PARAM, token.token)
2018-03-17 18:26:18 +05:30
end
it_behaves_like 'sudo'
2017-08-17 22:00:37 +05:30
end
2018-03-17 18:26:18 +05:30
context 'passed as header' do
before do
2020-01-01 13:55:28 +05:30
env[Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_HEADER] = token.token
2018-03-17 18:26:18 +05:30
end
2017-08-17 22:00:37 +05:30
2018-03-17 18:26:18 +05:30
it_behaves_like 'sudo'
end
end
2017-08-17 22:00:37 +05:30
2018-03-17 18:26:18 +05:30
context 'using warden authentication' do
before do
warden_authenticate_returns admin
env[API::Helpers::SUDO_HEADER] = user.username
end
it 'raises an error' do
expect { current_user }.to raise_error /Must be authenticated using an OAuth or Personal Access Token to use sudo/
2017-08-17 22:00:37 +05:30
end
end
end
2017-01-15 13:20:01 +05:30
end