Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-01-01 15:10:11 +00:00
parent 1047df9d86
commit 6c3cbf7063
21 changed files with 808 additions and 64 deletions

View File

@ -0,0 +1,6 @@
---
title: Add a new Ruby API for specifying allowed authentication mechanisms
for REST API endpoints
merge_request: 38627
author: Ethan Reesor (@firelizzard)
type: other

View File

@ -0,0 +1,6 @@
---
title: The NuGet endpoints will no longer ignore an invalid username when a personal
access token or deploy token is passed via HTTP Basic authentication
merge_request: 38627
author: Ethan Reesor (@firelizzard)
type: security

View File

@ -69,10 +69,15 @@ module API
def find_user_from_sources
strong_memoize(:find_user_from_sources) do
deploy_token_from_request ||
find_user_from_bearer_token ||
find_user_from_job_token ||
user_from_warden
if try(:namespace_inheritable, :authentication)
user_from_namespace_inheritable ||
user_from_warden
else
deploy_token_from_request ||
find_user_from_bearer_token ||
find_user_from_job_token ||
user_from_warden
end
end
end

View File

@ -56,9 +56,6 @@ module API
desc 'The NuGet Service Index' do
detail 'This feature was introduced in GitLab 12.6'
end
route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true
get 'index', format: :json do
authorize_read_package!(project_or_group)
track_package_event('cli_metadata', :nuget, category: 'API::NugetPackages')
@ -79,9 +76,6 @@ module API
desc 'The NuGet Metadata Service - Package name level' do
detail 'This feature was introduced in GitLab 12.8'
end
route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true
get 'index', format: :json do
present ::Packages::Nuget::PackagesMetadataPresenter.new(find_packages(params[:package_name])),
with: ::API::Entities::Nuget::PackagesMetadata
@ -93,9 +87,6 @@ module API
params do
requires :package_version, type: String, desc: 'The NuGet package version', regexp: API::NO_SLASH_URL_PART_REGEX
end
route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true
get '*package_version', format: :json do
present ::Packages::Nuget::PackageMetadataPresenter.new(find_package(params[:package_name], params[:package_version])),
with: ::API::Entities::Nuget::PackageMetadata
@ -117,9 +108,6 @@ module API
desc 'The NuGet Search Service' do
detail 'This feature was introduced in GitLab 12.8'
end
route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true
get format: :json do
search_options = {
include_prerelease_versions: params[:prerelease],

View File

@ -0,0 +1,73 @@
# frozen_string_literal: true
module API
module Helpers
module Authentication
extend ActiveSupport::Concern
class_methods do
def authenticate_with(&block)
strategies = ::Gitlab::APIAuthentication::Builder.new.build(&block)
namespace_inheritable :authentication, strategies
end
end
included do
helpers ::Gitlab::Utils::StrongMemoize
helpers do
def token_from_namespace_inheritable
strong_memoize(:token_from_namespace_inheritable) do
strategies = namespace_inheritable(:authentication)
next unless strategies&.any?
# Extract credentials from the request
found = strategies.to_h { |location, _| [location, ::Gitlab::APIAuthentication::TokenLocator.new(location).extract(current_request)] }
found.filter! { |location, raw| raw }
next unless found.any?
# Specifying multiple credentials is an error
# https://gitlab.com/gitlab-org/gitlab/-/merge_requests/38627#note_475984136
bad_request!('Found more than one set of credentials') if found.size > 1
location, raw = found.first
find_token_from_raw_credentials(strategies[location], raw)
end
rescue ::Gitlab::Auth::UnauthorizedError
# TODO: this should be rescued and converted by the exception handling middleware
# https://gitlab.com/gitlab-org/gitlab/-/merge_requests/38627#note_475174516
unauthorized!
end
def access_token_from_namespace_inheritable
token = token_from_namespace_inheritable
token if token.is_a? PersonalAccessToken
end
def user_from_namespace_inheritable
token = token_from_namespace_inheritable
return token if token.is_a? DeployToken
token&.user
end
private
def find_token_from_raw_credentials(token_types, raw)
token_types.each do |token_type|
# Resolve a token from the raw credentials
token = ::Gitlab::APIAuthentication::TokenResolver.new(token_type).resolve(raw)
return token if token
end
# If a request provides credentials via an allowed transport, the
# credentials must be valid. If we reach this point, the credentials
# must not be valid credentials of an allowed type.
raise ::Gitlab::Auth::UnauthorizedError
end
end
end
end
end
end

View File

@ -9,13 +9,19 @@
# This is the group level API.
module API
class NugetGroupPackages < ::API::Base
helpers ::API::Helpers::PackagesManagerClientsHelpers
helpers ::API::Helpers::PackagesHelpers
helpers ::API::Helpers::Packages::BasicAuthHelpers
include ::API::Helpers::Authentication
feature_category :package_registry
default_format :json
authenticate_with do |accept|
accept.token_types(:personal_access_token, :deploy_token, :job_token)
.sent_through(:http_basic_auth)
end
rescue_from ArgumentError do |e|
render_api_error!(e.message, 400)
end
@ -38,8 +44,6 @@ module API
requires :id, type: String, desc: 'The ID of a group', regexp: ::API::Concerns::Packages::NugetEndpoints::POSITIVE_INTEGER_REGEX
end
route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true
resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
namespace ':id/packages/nuget' do
after_validation do

View File

@ -9,8 +9,9 @@
# This is the project level API.
module API
class NugetProjectPackages < ::API::Base
helpers ::API::Helpers::PackagesManagerClientsHelpers
helpers ::API::Helpers::PackagesHelpers
helpers ::API::Helpers::Packages::BasicAuthHelpers
include ::API::Helpers::Authentication
feature_category :package_registry
@ -18,6 +19,11 @@ module API
default_format :json
authenticate_with do |accept|
accept.token_types(:personal_access_token, :deploy_token, :job_token)
.sent_through(:http_basic_auth)
end
rescue_from ArgumentError do |e|
render_api_error!(e.message, 400)
end
@ -35,9 +41,6 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project', regexp: ::API::Concerns::Packages::NugetEndpoints::POSITIVE_INTEGER_REGEX
end
route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
namespace ':id/packages/nuget' do
include ::API::Concerns::Packages::NugetEndpoints
@ -50,9 +53,6 @@ module API
params do
requires :package, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)'
end
route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true
put do
authorize_upload!(project_or_group)
bad_request!('File is too large') if project_or_group.actual_limits.exceeded?(:nuget_max_file_size, params[:package].size)
@ -78,9 +78,6 @@ module API
forbidden!
end
route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true
put 'authorize' do
authorize_workhorse!(
subject: project_or_group,
@ -101,9 +98,6 @@ module API
desc 'The NuGet Content Service - index request' do
detail 'This feature was introduced in GitLab 12.8'
end
route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true
get 'index', format: :json do
present ::Packages::Nuget::PackagesVersionsPresenter.new(find_packages(params[:package_name])),
with: ::API::Entities::Nuget::PackagesVersions
@ -116,9 +110,6 @@ module API
requires :package_version, type: String, desc: 'The NuGet package version', regexp: API::NO_SLASH_URL_PART_REGEX
requires :package_filename, type: String, desc: 'The NuGet package filename', regexp: API::NO_SLASH_URL_PART_REGEX
end
route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true
get '*package_version/*package_filename', format: :nupkg do
filename = "#{params[:package_filename]}.#{params[:format]}"
package_file = ::Packages::PackageFileFinder.new(find_package(params[:package_name], params[:package_version]), filename, with_file_name_like: true)

View File

@ -0,0 +1,18 @@
# frozen_string_literal: true
# Authentication Strategies Builder
#
# AuthBuilder and its child classes, TokenType and SentThrough, support
# declaring allowed authentication strategies with patterns like
# `accept.token_type(:job_token).sent_through(:http_basic)`.
module Gitlab
module APIAuthentication
class Builder
def build
strategies = Hash.new([])
yield ::Gitlab::APIAuthentication::TokenTypeBuilder.new(strategies)
strategies
end
end
end
end

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
# See Gitlab::APIAuthentication::Builder
module Gitlab
module APIAuthentication
class SentThroughBuilder
def initialize(strategies, resolvers)
@strategies = strategies
@resolvers = resolvers
end
def sent_through(*locators)
locators.each do |locator|
@strategies[locator] |= @resolvers
end
end
end
end
end

View File

@ -0,0 +1,37 @@
# frozen_string_literal: true
module Gitlab
module APIAuthentication
class TokenLocator
UsernameAndPassword = Struct.new(:username, :password)
include ActiveModel::Validations
include ActionController::HttpAuthentication::Basic
attr_reader :location
validates :location, inclusion: { in: %i[http_basic_auth] }
def initialize(location)
@location = location
validate!
end
def extract(request)
case @location
when :http_basic_auth
extract_from_http_basic_auth request
end
end
private
def extract_from_http_basic_auth(request)
username, password = user_name_and_password(request)
return unless username.present? && password.present?
UsernameAndPassword.new(username, password)
end
end
end
end

View File

@ -0,0 +1,87 @@
# frozen_string_literal: true
module Gitlab
module APIAuthentication
class TokenResolver
include ActiveModel::Validations
attr_reader :token_type
validates :token_type, inclusion: { in: %i[personal_access_token job_token deploy_token] }
def initialize(token_type)
@token_type = token_type
validate!
end
# Existing behavior is known to be inconsistent across authentication
# methods with regards to whether to silently ignore present but invalid
# credentials or to raise an error/respond with 401.
#
# If a token can be located from the provided credentials, but the token
# or credentials are in some way invalid, this implementation opts to
# raise an error.
#
# For example, if the raw credentials include a username and password, and
# a token is resolved from the password, but the username does not match
# the token, an error will be raised.
#
# See https://gitlab.com/gitlab-org/gitlab/-/issues/246569
def resolve(raw)
case @token_type
when :personal_access_token
resolve_personal_access_token raw
when :job_token
resolve_job_token raw
when :deploy_token
resolve_deploy_token raw
end
end
private
def resolve_personal_access_token(raw)
# Check if the password is a personal access token
pat = ::PersonalAccessToken.find_by_token(raw.password)
return unless pat
# Ensure that the username matches the token. This check is a subtle
# departure from the existing behavior of #find_personal_access_token_from_http_basic_auth.
# https://gitlab.com/gitlab-org/gitlab/-/merge_requests/38627#note_435907856
raise ::Gitlab::Auth::UnauthorizedError unless pat.user.username == raw.username
pat
end
def resolve_job_token(raw)
# Only look for a job if the username is correct
return if ::Gitlab::Auth::CI_JOB_USER != raw.username
job = ::Ci::AuthJobFinder.new(token: raw.password).execute
# Actively reject credentials with the username `gitlab-ci-token` if
# the password is not a valid job token. This replicates existing
# behavior of #find_user_from_job_token.
raise ::Gitlab::Auth::UnauthorizedError unless job
job
end
def resolve_deploy_token(raw)
# Check if the password is a deploy token
token = ::DeployToken.active.find_by_token(raw.password)
return unless token
# Ensure that the username matches the token. This check is a subtle
# departure from the existing behavior of #deploy_token_from_request.
# https://gitlab.com/gitlab-org/gitlab/-/merge_requests/38627#note_474826205
raise ::Gitlab::Auth::UnauthorizedError unless token.username == raw.username
token
end
end
end
end

View File

@ -0,0 +1,18 @@
# frozen_string_literal: true
# See Gitlab::Auth::AuthBuilder
module Gitlab
module APIAuthentication
class TokenTypeBuilder
def initialize(strategies)
@strategies = strategies
end
def token_types(*resolvers)
::Gitlab::APIAuthentication::SentThroughBuilder.new(@strategies, resolvers)
end
alias_method :token_type, :token_types
end
end
end

View File

@ -194,11 +194,15 @@ module Gitlab
def access_token
strong_memoize(:access_token) do
# The token can be a PAT or an OAuth (doorkeeper) token
# It is also possible that a PAT is encapsulated in a `Bearer` OAuth token
# (e.g. NPM client registry auth), this case will be properly handled
# by find_personal_access_token
find_oauth_access_token || find_personal_access_token
if try(:namespace_inheritable, :authentication)
access_token_from_namespace_inheritable
else
# The token can be a PAT or an OAuth (doorkeeper) token
# It is also possible that a PAT is encapsulated in a `Bearer` OAuth token
# (e.g. NPM client registry auth), this case will be properly handled
# by find_personal_access_token
find_oauth_access_token || find_personal_access_token
end
end
end

View File

@ -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

View File

@ -0,0 +1,76 @@
# frozen_string_literal: true
require 'fast_spec_helper'
RSpec.describe Gitlab::APIAuthentication::Builder do
describe '#build' do
shared_examples 'builds the correct result' do |token_type:, sent_through:, builds:|
context "with #{token_type.size} token type(s) and #{sent_through.size} sent through(s)" do
it 'works when passed together' do
strategies = described_class.new.build { |allow| allow.token_types(*token_type).sent_through(*sent_through) }
expect(strategies).to eq(builds)
end
it 'works when token types are passed separately' do
strategies = described_class.new.build { |allow| token_type.each { |t| allow.token_types(t).sent_through(*sent_through) } }
expect(strategies).to eq(builds)
end
it 'works when sent throughs are passed separately' do
strategies = described_class.new.build { |allow| sent_through.each { |s| allow.token_types(*token_type).sent_through(s) } }
expect(strategies).to eq(builds)
end
it 'works when token types and sent throughs are passed separately' do
strategies = described_class.new.build { |allow| token_type.each { |t| sent_through.each { |s| allow.token_types(t).sent_through(s) } } }
expect(strategies).to eq(builds)
end
end
end
it_behaves_like 'builds the correct result',
token_type: [:pat],
sent_through: [:basic],
builds: { basic: [:pat] }
it_behaves_like 'builds the correct result',
token_type: [:pat],
sent_through: [:basic, :oauth],
builds: { basic: [:pat], oauth: [:pat] }
it_behaves_like 'builds the correct result',
token_type: [:pat, :job],
sent_through: [:basic],
builds: { basic: [:pat, :job] }
it_behaves_like 'builds the correct result',
token_type: [:pat, :job],
sent_through: [:basic, :oauth],
builds: { basic: [:pat, :job], oauth: [:pat, :job] }
context 'with a complex auth strategy' do
it 'builds the correct result' do
strategies = described_class.new.build do |allow|
allow.token_types(:pat, :job, :deploy).sent_through(:http_basic, :oauth)
allow.token_types(:pat).sent_through(:http_private, :query_private)
allow.token_types(:oauth2).sent_through(:http_bearer, :query_access)
end
expect(strategies).to eq({
http_basic: [:pat, :job, :deploy],
oauth: [:pat, :job, :deploy],
http_private: [:pat],
query_private: [:pat],
http_bearer: [:oauth2],
query_access: [:oauth2]
})
end
end
end
end

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
require 'fast_spec_helper'
RSpec.describe Gitlab::APIAuthentication::SentThroughBuilder do
describe '#sent_through' do
let(:resolvers) { Array.new(3) { double } }
let(:locators) { Array.new(3) { double } }
it 'adds a strategy for each of locators x resolvers' do
strategies = locators.to_h { |l| [l, []] }
described_class.new(strategies, resolvers).sent_through(*locators)
expect(strategies).to eq(locators.to_h { |l| [l, resolvers] })
end
end
end

View File

@ -0,0 +1,55 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::APIAuthentication::TokenLocator 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(:ci_job) { create(:ci_build, project: project, user: user, status: :running) }
let_it_be(:ci_job_done) { create(:ci_build, project: project, user: user, status: :success) }
let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
describe '.new' do
context 'with a valid type' do
it 'creates a new instance' do
expect(described_class.new(:http_basic_auth)).to be_a(described_class)
end
end
context 'with an invalid type' do
it 'raises ActiveModel::ValidationError' do
expect { described_class.new(:not_a_real_locator) }.to raise_error(ActiveModel::ValidationError)
end
end
end
describe '#extract' do
let(:locator) { described_class.new(type) }
subject { locator.extract(request) }
context 'with :http_basic_auth' do
let(:type) { :http_basic_auth }
context 'without credentials' do
let(:request) { double(authorization: nil) }
it 'returns nil' do
expect(subject).to be(nil)
end
end
context 'with credentials' do
let(:username) { 'foo' }
let(:password) { 'bar' }
let(:request) { double(authorization: "Basic #{::Base64.strict_encode64("#{username}:#{password}")}") }
it 'returns the credentials' do
expect(subject.username).to eq(username)
expect(subject.password).to eq(password)
end
end
end
end
end

View File

@ -0,0 +1,117 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::APIAuthentication::TokenResolver 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(:ci_job) { create(:ci_build, project: project, user: user, status: :running) }
let_it_be(:ci_job_done) { create(:ci_build, project: project, user: user, status: :success) }
let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
shared_examples 'an authorized request' do
it 'returns the correct token' do
expect(subject).to eq(token)
end
end
shared_examples 'an unauthorized request' do
it 'raises an error' do
expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError)
end
end
shared_examples 'an anoymous request' do
it 'returns nil' do
expect(subject).to eq(nil)
end
end
describe '.new' do
context 'with a valid type' do
it 'creates a new instance' do
expect(described_class.new(:personal_access_token)).to be_a(described_class)
end
end
context 'with an invalid type' do
it 'raises a validation error' do
expect { described_class.new(:not_a_real_locator) }.to raise_error(ActiveModel::ValidationError)
end
end
end
describe '#resolve' do
let(:resolver) { described_class.new(type) }
subject { resolver.resolve(raw) }
context 'with :personal_access_token' do
let(:type) { :personal_access_token }
let(:token) { personal_access_token }
context 'with valid credentials' do
let(:raw) { username_and_password(user.username, token.token) }
it_behaves_like 'an authorized request'
end
context 'with an invalid username' do
let(:raw) { username_and_password("not-my-#{user.username}", token.token) }
it_behaves_like 'an unauthorized request'
end
end
context 'with :job_token' do
let(:type) { :job_token }
let(:token) { ci_job }
context 'with valid credentials' do
let(:raw) { username_and_password(Gitlab::Auth::CI_JOB_USER, token.token) }
it_behaves_like 'an authorized request'
end
context 'when the job is not running' do
let(:raw) { username_and_password(Gitlab::Auth::CI_JOB_USER, ci_job_done.token) }
it_behaves_like 'an unauthorized request'
end
context 'with the wrong username' do
let(:raw) { username_and_password("not-#{Gitlab::Auth::CI_JOB_USER}", nil) }
it_behaves_like 'an anoymous request'
end
context 'with an invalid job token' do
let(:raw) { username_and_password(Gitlab::Auth::CI_JOB_USER, "not a valid CI job token") }
it_behaves_like 'an unauthorized request'
end
end
context 'with :deploy_token' do
let(:type) { :deploy_token }
let(:token) { deploy_token }
context 'with a valid deploy token' do
let(:raw) { username_and_password(token.username, token.token) }
it_behaves_like 'an authorized request'
end
context 'with an invalid username' do
let(:raw) { username_and_password("not-my-#{token.username}", token.token) }
it_behaves_like 'an unauthorized request'
end
end
end
def username_and_password(username, password)
::Gitlab::APIAuthentication::TokenLocator::UsernameAndPassword.new(username, password)
end
end

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
require 'fast_spec_helper'
RSpec.describe Gitlab::APIAuthentication::TokenTypeBuilder do
describe '#token_types' do
it 'passes strategies and resolvers to SentThroughBuilder' do
strategies = double
resolvers = Array.new(3) { double }
retval = double
expect(Gitlab::APIAuthentication::SentThroughBuilder).to receive(:new).with(strategies, resolvers).and_return(retval)
expect(described_class.new(strategies).token_types(*resolvers)).to be(retval)
end
end
end

View File

@ -49,12 +49,12 @@ RSpec.describe API::NugetProjectPackages do
where(:visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
'PUBLIC' | :developer | true | true | 'process nuget download versions request' | :success
'PUBLIC' | :guest | true | true | 'process nuget download versions request' | :success
'PUBLIC' | :developer | true | false | 'process nuget download versions request' | :success
'PUBLIC' | :guest | true | false | 'process nuget download versions request' | :success
'PUBLIC' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
'PUBLIC' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
'PUBLIC' | :developer | false | true | 'process nuget download versions request' | :success
'PUBLIC' | :guest | false | true | 'process nuget download versions request' | :success
'PUBLIC' | :developer | false | false | 'process nuget download versions request' | :success
'PUBLIC' | :guest | false | false | 'process nuget download versions request' | :success
'PUBLIC' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
'PUBLIC' | :anonymous | false | true | 'process nuget download versions request' | :success
'PRIVATE' | :developer | true | true | 'process nuget download versions request' | :success
'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
@ -100,12 +100,12 @@ RSpec.describe API::NugetProjectPackages do
where(:visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
'PUBLIC' | :developer | true | true | 'process nuget download content request' | :success
'PUBLIC' | :guest | true | true | 'process nuget download content request' | :success
'PUBLIC' | :developer | true | false | 'process nuget download content request' | :success
'PUBLIC' | :guest | true | false | 'process nuget download content request' | :success
'PUBLIC' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
'PUBLIC' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
'PUBLIC' | :developer | false | true | 'process nuget download content request' | :success
'PUBLIC' | :guest | false | true | 'process nuget download content request' | :success
'PUBLIC' | :developer | false | false | 'process nuget download content request' | :success
'PUBLIC' | :guest | false | false | 'process nuget download content request' | :success
'PUBLIC' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
'PUBLIC' | :anonymous | false | true | 'process nuget download content request' | :success
'PRIVATE' | :developer | true | true | 'process nuget download content request' | :success
'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden

View File

@ -10,12 +10,12 @@ RSpec.shared_examples 'handling nuget service requests' do |anonymous_requests_e
where(:visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
'PUBLIC' | :developer | true | true | 'process nuget service index request' | :success
'PUBLIC' | :guest | true | true | 'process nuget service index request' | :success
'PUBLIC' | :developer | true | false | anonymous_requests_example_name | anonymous_requests_status
'PUBLIC' | :guest | true | false | anonymous_requests_example_name | anonymous_requests_status
'PUBLIC' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
'PUBLIC' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
'PUBLIC' | :developer | false | true | 'process nuget service index request' | :success
'PUBLIC' | :guest | false | true | 'process nuget service index request' | :success
'PUBLIC' | :developer | false | false | anonymous_requests_example_name | anonymous_requests_status
'PUBLIC' | :guest | false | false | anonymous_requests_example_name | anonymous_requests_status
'PUBLIC' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
'PUBLIC' | :anonymous | false | true | anonymous_requests_example_name | anonymous_requests_status
'PRIVATE' | :developer | true | true | 'process nuget service index request' | :success
'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
@ -109,12 +109,12 @@ RSpec.shared_examples 'handling nuget metadata requests with package name' do |a
where(:visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
'PUBLIC' | :developer | true | true | 'process nuget metadata request at package name level' | :success
'PUBLIC' | :guest | true | true | 'process nuget metadata request at package name level' | :success
'PUBLIC' | :developer | true | false | anonymous_requests_example_name | anonymous_requests_status
'PUBLIC' | :guest | true | false | anonymous_requests_example_name | anonymous_requests_status
'PUBLIC' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
'PUBLIC' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
'PUBLIC' | :developer | false | true | 'process nuget metadata request at package name level' | :success
'PUBLIC' | :guest | false | true | 'process nuget metadata request at package name level' | :success
'PUBLIC' | :developer | false | false | anonymous_requests_example_name | anonymous_requests_status
'PUBLIC' | :guest | false | false | anonymous_requests_example_name | anonymous_requests_status
'PUBLIC' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
'PUBLIC' | :anonymous | false | true | anonymous_requests_example_name | anonymous_requests_status
'PRIVATE' | :developer | true | true | 'process nuget metadata request at package name level' | :success
'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
@ -171,12 +171,12 @@ RSpec.shared_examples 'handling nuget metadata requests with package name and pa
where(:visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
'PUBLIC' | :developer | true | true | 'process nuget metadata request at package name and package version level' | :success
'PUBLIC' | :guest | true | true | 'process nuget metadata request at package name and package version level' | :success
'PUBLIC' | :developer | true | false | anonymous_requests_example_name | anonymous_requests_status
'PUBLIC' | :guest | true | false | anonymous_requests_example_name | anonymous_requests_status
'PUBLIC' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
'PUBLIC' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
'PUBLIC' | :developer | false | true | 'process nuget metadata request at package name and package version level' | :success
'PUBLIC' | :guest | false | true | 'process nuget metadata request at package name and package version level' | :success
'PUBLIC' | :developer | false | false | anonymous_requests_example_name | anonymous_requests_status
'PUBLIC' | :guest | false | false | anonymous_requests_example_name | anonymous_requests_status
'PUBLIC' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
'PUBLIC' | :anonymous | false | true | anonymous_requests_example_name | anonymous_requests_status
'PRIVATE' | :developer | true | true | 'process nuget metadata request at package name and package version level' | :success
'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
@ -235,12 +235,12 @@ RSpec.shared_examples 'handling nuget search requests' do |anonymous_requests_ex
where(:visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
'PUBLIC' | :developer | true | true | 'process nuget search request' | :success
'PUBLIC' | :guest | true | true | 'process nuget search request' | :success
'PUBLIC' | :developer | true | false | anonymous_requests_example_name | anonymous_requests_status
'PUBLIC' | :guest | true | false | anonymous_requests_example_name | anonymous_requests_status
'PUBLIC' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
'PUBLIC' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
'PUBLIC' | :developer | false | true | 'process nuget search request' | :success
'PUBLIC' | :guest | false | true | 'process nuget search request' | :success
'PUBLIC' | :developer | false | false | anonymous_requests_example_name | anonymous_requests_status
'PUBLIC' | :guest | false | false | anonymous_requests_example_name | anonymous_requests_status
'PUBLIC' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
'PUBLIC' | :anonymous | false | true | anonymous_requests_example_name | anonymous_requests_status
'PRIVATE' | :developer | true | true | 'process nuget search request' | :success
'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden