summaryrefslogtreecommitdiff
path: root/spec/lib/api/helpers/authentication_spec.rb
diff options
context:
space:
mode:
Diffstat (limited to 'spec/lib/api/helpers/authentication_spec.rb')
-rw-r--r--spec/lib/api/helpers/authentication_spec.rb207
1 files changed, 207 insertions, 0 deletions
diff --git a/spec/lib/api/helpers/authentication_spec.rb b/spec/lib/api/helpers/authentication_spec.rb
new file mode 100644
index 00000000000..461b0d2f6f9
--- /dev/null
+++ b/spec/lib/api/helpers/authentication_spec.rb
@@ -0,0 +1,207 @@
+# 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) }
+
+ 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
+
+ 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