2021-03-08 18:12:59 +05:30
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
require 'spec_helper'
|
|
|
|
|
|
|
|
RSpec.describe API::Helpers::Authentication do
|
|
|
|
let_it_be(:user) { create(:user) }
|
|
|
|
let_it_be(:project, reload: true) { create(:project, :public) }
|
|
|
|
let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
|
|
|
|
let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
|
2021-04-29 21:17:54 +05:30
|
|
|
let_it_be(:ci_build) { create(:ci_build, :running, user: user) }
|
2021-03-08 18:12:59 +05:30
|
|
|
|
|
|
|
describe 'class methods' do
|
|
|
|
subject { Class.new.include(described_class::ClassMethods).new }
|
|
|
|
|
|
|
|
describe '.authenticate_with' do
|
|
|
|
it 'sets namespace_inheritable :authentication to correctly when body is empty' do
|
|
|
|
expect(subject).to receive(:namespace_inheritable).with(:authentication, {})
|
|
|
|
|
|
|
|
subject.authenticate_with { |allow| }
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'sets namespace_inheritable :authentication to correctly when body is not empty' do
|
|
|
|
expect(subject).to receive(:namespace_inheritable).with(:authentication, { basic: [:pat, :job], oauth: [:pat, :job] })
|
|
|
|
|
|
|
|
subject.authenticate_with { |allow| allow.token_type(:pat, :job).sent_through(:basic, :oauth) }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe 'helper methods' do
|
|
|
|
let(:object) do
|
|
|
|
cls = Class.new
|
|
|
|
|
|
|
|
class << cls
|
|
|
|
def helpers(*modules, &block)
|
|
|
|
modules.each { |m| include m }
|
|
|
|
include Module.new.tap { |m| m.class_eval(&block) } if block_given?
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
cls.define_method(:unauthorized!) { raise '401' }
|
|
|
|
cls.define_method(:bad_request!) { |m| raise "400 - #{m}" }
|
|
|
|
|
|
|
|
# Include the helper class methods, as instance methods
|
|
|
|
cls.include described_class::ClassMethods
|
|
|
|
|
|
|
|
# Include the methods under test
|
|
|
|
cls.include described_class
|
|
|
|
|
|
|
|
cls.new
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#token_from_namespace_inheritable' do
|
|
|
|
let(:object) do
|
|
|
|
o = super()
|
|
|
|
|
|
|
|
o.instance_eval do
|
|
|
|
# It doesn't matter what this returns as long as the method is defined
|
|
|
|
def current_request
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
|
|
|
|
# Spoof Grape's namespace inheritable system
|
|
|
|
def namespace_inheritable(key, value = nil)
|
|
|
|
return unless key == :authentication
|
|
|
|
|
|
|
|
if value
|
|
|
|
@authentication = value
|
|
|
|
else
|
|
|
|
@authentication
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
o
|
|
|
|
end
|
|
|
|
|
|
|
|
let(:authentication) do
|
|
|
|
object.authenticate_with { |allow| allow.token_types(*resolvers).sent_through(*locators) }
|
|
|
|
end
|
|
|
|
|
|
|
|
subject { object.token_from_namespace_inheritable }
|
|
|
|
|
|
|
|
before do
|
|
|
|
# Skip validation of token transports and types to simplify testing
|
|
|
|
allow(Gitlab::APIAuthentication::TokenLocator).to receive(:new) { |type| type }
|
|
|
|
allow(Gitlab::APIAuthentication::TokenResolver).to receive(:new) { |type| type }
|
|
|
|
|
|
|
|
authentication
|
|
|
|
end
|
|
|
|
|
|
|
|
shared_examples 'stops early' do |response_method|
|
|
|
|
it "calls ##{response_method}" do
|
|
|
|
errcls = Class.new(StandardError)
|
|
|
|
expect(object).to receive(response_method).and_raise(errcls)
|
|
|
|
expect { subject }.to raise_error(errcls)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
shared_examples 'an anonymous request' do
|
|
|
|
it 'returns nil' do
|
|
|
|
expect(subject).to be(nil)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
shared_examples 'an authenticated request' do
|
|
|
|
it 'returns the token' do
|
|
|
|
expect(subject).to be(token)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
shared_examples 'an unauthorized request' do
|
|
|
|
it_behaves_like 'stops early', :unauthorized!
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with no allowed authentication strategies' do
|
|
|
|
let(:authentication) { nil }
|
|
|
|
|
|
|
|
it_behaves_like 'an anonymous request'
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with no located credentials' do
|
|
|
|
let(:locators) { [double(extract: nil)] }
|
|
|
|
let(:resolvers) { [] }
|
|
|
|
|
|
|
|
it_behaves_like 'an anonymous request'
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with one set of located credentials' do
|
|
|
|
let(:locators) { [double(extract: true)] }
|
|
|
|
|
|
|
|
context 'when the credentials contain a valid token' do
|
|
|
|
let(:token) { double }
|
|
|
|
let(:resolvers) { [double(resolve: token)] }
|
|
|
|
|
|
|
|
it_behaves_like 'an authenticated request'
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the credentials do not contain a valid token' do
|
|
|
|
let(:resolvers) { [double(resolve: nil)] }
|
|
|
|
|
|
|
|
it_behaves_like 'an unauthorized request'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with multiple located credentials' do
|
|
|
|
let(:locators) { [double(extract: true), double(extract: true)] }
|
|
|
|
let(:resolvers) { [] }
|
|
|
|
|
|
|
|
it_behaves_like 'stops early', :bad_request!
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when a resolver raises UnauthorizedError' do
|
|
|
|
let(:locators) { [double(extract: true)] }
|
|
|
|
let(:resolvers) do
|
|
|
|
r = double
|
|
|
|
expect(r).to receive(:resolve).and_raise(Gitlab::Auth::UnauthorizedError)
|
|
|
|
r
|
|
|
|
end
|
|
|
|
|
|
|
|
it_behaves_like 'an unauthorized request'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#access_token_from_namespace_inheritable' do
|
|
|
|
subject { object.access_token_from_namespace_inheritable }
|
|
|
|
|
|
|
|
it 'returns #token_from_namespace_inheritable if it is a personal access token' do
|
|
|
|
expect(object).to receive(:token_from_namespace_inheritable).and_return(personal_access_token)
|
|
|
|
expect(subject).to be(personal_access_token)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns nil if #token_from_namespace_inheritable is not a personal access token' do
|
|
|
|
token = double
|
|
|
|
expect(object).to receive(:token_from_namespace_inheritable).and_return(token)
|
|
|
|
expect(subject).to be(nil)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-04-29 21:17:54 +05:30
|
|
|
describe '#ci_build_from_namespace_inheritable' do
|
|
|
|
subject { object.ci_build_from_namespace_inheritable }
|
|
|
|
|
|
|
|
it 'returns #token_from_namespace_inheritable if it is a ci build' do
|
|
|
|
expect(object).to receive(:token_from_namespace_inheritable).and_return(ci_build)
|
|
|
|
expect(subject).to be(ci_build)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns nil if #token_from_namespace_inheritable is not a ci build' do
|
|
|
|
expect(object).to receive(:token_from_namespace_inheritable).and_return(personal_access_token)
|
|
|
|
expect(subject).to eq(nil)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-03-08 18:12:59 +05:30
|
|
|
describe '#user_from_namespace_inheritable' do
|
|
|
|
subject { object.user_from_namespace_inheritable }
|
|
|
|
|
|
|
|
it 'returns #token_from_namespace_inheritable if it is a deploy token' do
|
|
|
|
expect(object).to receive(:token_from_namespace_inheritable).and_return(deploy_token)
|
|
|
|
expect(subject).to be(deploy_token)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns #token_from_namespace_inheritable.user if the token is not a deploy token' do
|
|
|
|
user = double
|
|
|
|
token = double(user: user)
|
|
|
|
expect(object).to receive(:token_from_namespace_inheritable).and_return(token)
|
|
|
|
|
|
|
|
expect(subject).to be(user)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'falls back to #find_user_from_warden if #token_from_namespace_inheritable.user is nil' do
|
|
|
|
token = double(user: nil)
|
|
|
|
expect(object).to receive(:token_from_namespace_inheritable).and_return(token)
|
|
|
|
subject
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'falls back to #find_user_from_warden if #token_from_namespace_inheritable is nil' do
|
|
|
|
expect(object).to receive(:token_from_namespace_inheritable).and_return(nil)
|
|
|
|
subject
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|