Commit d5e63a6d authored by Ethan Reesor's avatar Ethan Reesor

Clean up Go proxy implementation

- Various fixes and improvements
- Hide proxy behind a feature flag, due to performance issues: #218083
- Add a feature flag for testing to disable strict go.mod validation
- Document why case en/decoding is necessary
- Refactor pseudo-version processing
  - Move logic from VersionFinder to ModuleHelpers
  - Document reasoning for matching and validation
- Replace BasicAuthHelper and custom method with route setting
- Use serach_files_by_name instead of tree to improve performance
- Use correct content type for zip: closes #214876
parent 4b568ff0
...@@ -56,7 +56,6 @@ Follow [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/213770) for de ...@@ -56,7 +56,6 @@ Follow [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/213770) for de
### Fetch modules from private projects ### Fetch modules from private projects
NOTE: **Note:**
`go` does not support transmitting credentials over insecure connections. The `go` does not support transmitting credentials over insecure connections. The
steps below work only if GitLab is configured for HTTPS. steps below work only if GitLab is configured for HTTPS.
......
...@@ -5,29 +5,31 @@ module Packages ...@@ -5,29 +5,31 @@ module Packages
class ModuleFinder class ModuleFinder
include ::API::Helpers::Packages::Go::ModuleHelpers include ::API::Helpers::Packages::Go::ModuleHelpers
GITLAB_GO_URL = (Settings.build_gitlab_go_url + '/').freeze
attr_reader :project, :module_name attr_reader :project, :module_name
def initialize(project, module_name) def initialize(project, module_name)
module_name = CGI.unescape(module_name)
module_name = Pathname.new(module_name).cleanpath.to_s module_name = Pathname.new(module_name).cleanpath.to_s
@project = project @project = project
@module_name = module_name @module_name = module_name
end end
# rubocop: disable CodeReuse/ActiveRecord
def execute def execute
return if @module_name.blank? || !@module_name.start_with?(GITLAB_GO_URL) return if @module_name.blank? || !@module_name.start_with?(gitlab_go_url)
module_path = @module_name[GITLAB_GO_URL.length..].split('/') module_path = @module_name[gitlab_go_url.length..].split('/')
project_path = project.full_path.split('/') project_path = project.full_path.split('/')
return unless module_path.take(project_path.length) == project_path module_project_path = module_path.shift(project_path.length)
return unless module_project_path == project_path
Packages::GoModule.new(@project, @module_name, module_path.join('/'))
end
private
Packages::GoModule.new(@project, @module_name, module_path.drop(project_path.length).join('/')) def gitlab_go_url
@gitlab_go_url ||= Settings.build_gitlab_go_url + '/'
end end
# rubocop: enable CodeReuse/ActiveRecord
end end
end end
end end
# frozen_string_literal: true # frozen_string_literal: true
class Packages::GoModule class Packages::GoModule
include Gitlab::Utils::StrongMemoize
attr_reader :project, :name, :path attr_reader :project, :name, :path
def initialize(project, name, path) def initialize(project, name, path)
...@@ -10,7 +12,7 @@ class Packages::GoModule ...@@ -10,7 +12,7 @@ class Packages::GoModule
end end
def versions def versions
@versions ||= Packages::Go::VersionFinder.new(self).execute strong_memoize(:versions) { Packages::Go::VersionFinder.new(self).execute }
end end
def version_by(ref: nil, commit: nil) def version_by(ref: nil, commit: nil)
...@@ -40,6 +42,10 @@ class Packages::GoModule ...@@ -40,6 +42,10 @@ class Packages::GoModule
end end
def gomod_valid?(gomod) def gomod_valid?(gomod)
if Feature.enabled?(:go_proxy_disable_gomod_validation, @project)
return gomod&.start_with?("module ")
end
gomod&.split("\n", 2)&.first == "module #{@name}" gomod&.split("\n", 2)&.first == "module #{@name}"
end end
...@@ -47,8 +53,8 @@ class Packages::GoModule ...@@ -47,8 +53,8 @@ class Packages::GoModule
def version_by_name(name) def version_by_name(name)
# avoid a Gitaly call if possible # avoid a Gitaly call if possible
if defined?(@versions) if strong_memoized?(:versions)
v = @versions.find { |v| v.name == ref } v = versions.find { |v| v.name == ref }
return v if v return v if v
end end
...@@ -60,8 +66,8 @@ class Packages::GoModule ...@@ -60,8 +66,8 @@ class Packages::GoModule
def version_by_ref(ref) def version_by_ref(ref)
# reuse existing versions # reuse existing versions
if defined?(@versions) if strong_memoized?(:versions)
v = @versions.find { |v| v.ref == ref } v = versions.find { |v| v.ref == ref }
return v if v return v if v
end end
......
# frozen_string_literal: true # frozen_string_literal: true
class Packages::GoModuleVersion class Packages::GoModuleVersion
include Gitlab::Utils::StrongMemoize
include ::API::Helpers::Packages::Go::ModuleHelpers include ::API::Helpers::Packages::Go::ModuleHelpers
VALID_TYPES = %i[ref commit pseudo].freeze VALID_TYPES = %i[ref commit pseudo].freeze
...@@ -42,14 +43,15 @@ class Packages::GoModuleVersion ...@@ -42,14 +43,15 @@ class Packages::GoModuleVersion
end end
def gomod def gomod
@gomod ||= strong_memoize(:gomod) do
if defined?(@blobs) if strong_memoized?(:blobs)
blob_at(@mod.path + '/go.mod') blob_at(@mod.path + '/go.mod')
elsif @mod.path.empty? elsif @mod.path.empty?
@mod.project.repository.blob_at(@commit.sha, 'go.mod')&.data @mod.project.repository.blob_at(@commit.sha, 'go.mod')&.data
else else
@mod.project.repository.blob_at(@commit.sha, @mod.path + '/go.mod')&.data @mod.project.repository.blob_at(@commit.sha, @mod.path + '/go.mod')&.data
end end
end
end end
def archive def archive
...@@ -57,12 +59,26 @@ class Packages::GoModuleVersion ...@@ -57,12 +59,26 @@ class Packages::GoModuleVersion
Zip::OutputStream.write_buffer do |zip| Zip::OutputStream.write_buffer do |zip|
files.each do |file| files.each do |file|
zip.put_next_entry "#{full_name}/#{file.path[suffix_len...]}" zip.put_next_entry "#{full_name}/#{file[suffix_len...]}"
zip.write blob_at(file.path) zip.write blob_at(file)
end end
end end
end end
def files
strong_memoize(:files) do
ls_tree.filter { |e| !excluded.any? { |n| e.start_with? n } }
end
end
def excluded
strong_memoize(:excluded) do
ls_tree
.filter { |f| f.end_with?('/go.mod') && f != @mod.path + '/go.mod' }
.map { |f| f[0..-7] }
end
end
def valid? def valid?
@mod.path_valid?(major) && @mod.gomod_valid?(gomod) @mod.path_valid?(major) && @mod.gomod_valid?(gomod)
end end
...@@ -78,15 +94,19 @@ class Packages::GoModuleVersion ...@@ -78,15 +94,19 @@ class Packages::GoModuleVersion
end end
def blobs def blobs
@blobs ||= @mod.project.repository.batch_blobs(files.map { |x| [@commit.sha, x.path] }) strong_memoize(:blobs) { @mod.project.repository.batch_blobs(files.map { |x| [@commit.sha, x] }) }
end end
def files def ls_tree
return @files if defined?(@files) strong_memoize(:ls_tree) do
path =
if @mod.path.empty?
'.'
else
@mod.path
end
sha = @commit.sha @mod.project.repository.gitaly_repository_client.search_files_by_name(@commit.sha, path)
tree = @mod.project.repository.tree(sha, @mod.path, recursive: true).entries.filter { |e| e.file? } end
nested = tree.filter { |e| e.name == 'go.mod' && !(@mod.path == '' && e.path == 'go.mod' || e.path == @mod.path + '/go.mod') }.map { |e| e.path[0..-7] }
@files = tree.filter { |e| !nested.any? { |n| e.path.start_with? n } }
end end
end end
# frozen_string_literal: true # frozen_string_literal: true
class Packages::SemVer class Packages::SemVer
# basic semver, but bounded (^expr$)
PATTERN = /\A(v?)(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([-.a-z0-9]+))?(?:\+([-.a-z0-9]+))?\z/i.freeze
attr_accessor :major, :minor, :patch, :prerelease, :build attr_accessor :major, :minor, :patch, :prerelease, :build
def initialize(major = 0, minor = 0, patch = 0, prerelease = nil, build = nil, prefixed: false) def initialize(major = 0, minor = 0, patch = 0, prerelease = nil, build = nil, prefixed: false)
...@@ -37,11 +34,11 @@ class Packages::SemVer ...@@ -37,11 +34,11 @@ class Packages::SemVer
end end
def self.match(str, prefixed: false) def self.match(str, prefixed: false)
m = PATTERN.match(str) return unless str&.start_with?('v') == prefixed
return unless m
return if prefixed == m[1].empty? str = str[1..] if prefixed
m Gitlab::Regex.semver_regex.match(str)
end end
def self.match?(str, prefixed: false) def self.match?(str, prefixed: false)
...@@ -52,6 +49,6 @@ class Packages::SemVer ...@@ -52,6 +49,6 @@ class Packages::SemVer
m = match str, prefixed: prefixed m = match str, prefixed: prefixed
return unless m return unless m
new(m[2].to_i, m[3].to_i, m[4].to_i, m[5], m[6], prefixed: prefixed) new(m[1].to_i, m[2].to_i, m[3].to_i, m[4], m[5], prefixed: prefixed)
end end
end end
# frozen_string_literal: true # frozen_string_literal: true
module API module API
class GoProxy < Grape::API class GoProxy < Grape::API
helpers ::API::Helpers::PackagesManagerClientsHelpers helpers ::API::Helpers::PackagesHelpers
helpers ::API::Helpers::Packages::BasicAuthHelpers
helpers ::API::Helpers::Packages::Go::ModuleHelpers helpers ::API::Helpers::Packages::Go::ModuleHelpers
# basic semver, except case encoded (A => !a) # basic semver, except case encoded (A => !a)
...@@ -13,27 +12,27 @@ module API ...@@ -13,27 +12,27 @@ module API
before { require_packages_enabled! } before { require_packages_enabled! }
helpers do helpers do
# support personal access tokens for HTTP Basic in addition to the usual methods def find_project!(id)
def find_personal_access_token # based on API::Helpers::Packages::BasicAuthHelpers#authorized_project_find!
pa = find_personal_access_token_from_http_basic_auth
return pa if pa project = find_project(id)
# copied from Gitlab::Auth::AuthFinders return project if project && can?(current_user, :read_project, project)
token =
current_request.params[::Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_PARAM].presence || if current_user
current_request.env[::Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_HEADER].presence || not_found!('Project')
parsed_oauth_token else
return unless token unauthorized!
end
# Expiration, revocation and scopes are verified in `validate_access_token!`
PersonalAccessToken.find_by_token(token) || raise(::Gitlab::Auth::UnauthorizedError)
end end
def find_module def find_module
not_found! unless Feature.enabled?(:go_proxy, user_project)
module_name = case_decode params[:module_name] module_name = case_decode params[:module_name]
bad_request!('Module Name') if module_name.blank? bad_request!('Module Name') if module_name.blank?
mod = ::Packages::Go::ModuleFinder.new(authorized_user_project, module_name).execute mod = ::Packages::Go::ModuleFinder.new(user_project, module_name).execute
not_found! unless mod not_found! unless mod
...@@ -55,13 +54,13 @@ module API ...@@ -55,13 +54,13 @@ module API
params do params do
requires :id, type: String, desc: 'The ID of a project' requires :id, type: String, desc: 'The ID of a project'
requires :module_name, type: String, desc: 'Module name' requires :module_name, type: String, desc: 'Module name', coerce_with: ->(val) { CGI.unescape(val) }
end end
route_setting :authentication, job_token_allowed: true route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
before do before do
authorize_read_package!(authorized_user_project) authorize_read_package!
authorize_packages_feature!(authorized_user_project) authorize_packages_feature!
end end
namespace ':id/packages/go/*module_name/@v' do namespace ':id/packages/go/*module_name/@v' do
...@@ -110,10 +109,10 @@ module API ...@@ -110,10 +109,10 @@ module API
get ':module_version.zip', requirements: MODULE_VERSION_REQUIREMENTS do get ':module_version.zip', requirements: MODULE_VERSION_REQUIREMENTS do
ver = find_version ver = find_version
# TODO: Content-Type should be application/zip, see #214876 content_type 'application/zip'
env['api.format'] = :binary
header['Content-Disposition'] = ActionDispatch::Http::ContentDisposition.format(disposition: 'attachment', filename: ver.name + '.zip') header['Content-Disposition'] = ActionDispatch::Http::ContentDisposition.format(disposition: 'attachment', filename: ver.name + '.zip')
header['Content-Transfer-Encoding'] = 'binary' header['Content-Transfer-Encoding'] = 'binary'
content_type 'text/plain'
status :ok status :ok
body ver.archive.string body ver.archive.string
end end
......
...@@ -6,10 +6,24 @@ module API ...@@ -6,10 +6,24 @@ module API
module Go module Go
module ModuleHelpers module ModuleHelpers
def case_encode(str) def case_encode(str)
# Converts "github.com/Azure" to "github.com/!azure"
#
# From `go help goproxy`:
#
# > To avoid problems when serving from case-sensitive file systems,
# > the <module> and <version> elements are case-encoded, replacing
# > every uppercase letter with an exclamation mark followed by the
# > corresponding lower-case letter: github.com/Azure encodes as
# > github.com/!azure.
str.gsub(/A-Z/) { |s| "!#{s.downcase}"} str.gsub(/A-Z/) { |s| "!#{s.downcase}"}
end end
def case_decode(str) def case_decode(str)
# Converts "github.com/!azure" to "github.com/Azure"
#
# See #case_encode
str.gsub(/![[:alpha:]]/) { |s| s[1..].upcase } str.gsub(/![[:alpha:]]/) { |s| s[1..].upcase }
end end
...@@ -29,7 +43,7 @@ module API ...@@ -29,7 +43,7 @@ module API
pre = version.prerelease pre = version.prerelease
# valid pseudo-versions are # Valid pseudo-versions are:
# vX.0.0-yyyymmddhhmmss-sha1337beef0, when no earlier tagged commit exists for X # vX.0.0-yyyymmddhhmmss-sha1337beef0, when no earlier tagged commit exists for X
# vX.Y.Z-pre.0.yyyymmddhhmmss-sha1337beef0, when most recent prior tag is vX.Y.Z-pre # vX.Y.Z-pre.0.yyyymmddhhmmss-sha1337beef0, when most recent prior tag is vX.Y.Z-pre
# vX.Y.(Z+1)-0.yyyymmddhhmmss-sha1337beef0, when most recent prior tag is vX.Y.Z # vX.Y.(Z+1)-0.yyyymmddhhmmss-sha1337beef0, when most recent prior tag is vX.Y.Z
...@@ -41,7 +55,41 @@ module API ...@@ -41,7 +55,41 @@ module API
pre = pre[m[0].length..] pre = pre[m[0].length..]
end end
/\A\d{14}-[A-Za-z0-9]+\z/.freeze.match? pre # This pattern is intentionally more forgiving than the patterns
# above. Correctness is verified by #pseudo_version_commit.
/\A\d{14}-\h+\z/.freeze.match? pre
end
def pseudo_version_commit(project, semver)
# Per Go's implementation of pseudo-versions, a tag should be
# considered a pseudo-version if it matches one of the patterns
# listed in #pseudo_version?, regardless of the content of the
# timestamp or the length of the SHA fragment. However, an error
# should be returned if the timestamp is not correct or if the SHA
# fragment is not exactly 12 characters long. See also Go's
# implementation of:
#
# - [*codeRepo.validatePseudoVersion](https://github.com/golang/go/blob/daf70d6c1688a1ba1699c933b3c3f04d6f2f73d9/src/cmd/go/internal/modfetch/coderepo.go#L530)
# - [Pseudo-version parsing](https://github.com/golang/go/blob/master/src/cmd/go/internal/modfetch/pseudo.go)
# - [Pseudo-version request processing](https://github.com/golang/go/blob/master/src/cmd/go/internal/modfetch/coderepo.go)
# Go ignores anything before '.' or after the second '-', so we will do the same
timestamp, sha = semver.prerelease.split('-').last 2
timestamp = timestamp.split('.').last
commit = project.repository.commit_by(oid: sha)
# Error messages are based on the responses of proxy.golang.org
# Verify that the SHA fragment references a commit
raise ArgumentError.new 'invalid pseudo-version: unknown commit' unless commit
# Require the SHA fragment to be 12 characters long
raise ArgumentError.new 'invalid pseudo-version: revision is shorter than canonical' unless sha.length == 12
# Require the timestamp to match that of the commit
raise ArgumentError.new 'invalid pseudo-version: does not match version-control timestamp' unless commit.committed_date.strftime('%Y%m%d%H%M%S') == timestamp
commit
end end
def parse_semver(str) def parse_semver(str)
......
...@@ -75,21 +75,27 @@ FactoryBot.define do ...@@ -75,21 +75,27 @@ FactoryBot.define do
transient do transient do
name { nil } name { nil }
message { 'Add module' } message { 'Add module' }
end
service do url do
port = ::Gitlab.config.gitlab.port v = "#{::Gitlab.config.gitlab.host}/#{project.path_with_namespace}"
host = ::Gitlab.config.gitlab.host
domain = case port when 80, 443 then host else "#{host}:#{port}" end if name
v + '/' + name
url = "#{domain}/#{project.path_with_namespace}" else
if name.nil? v
path = '' end
else
url += '/' + name
path = name + '/'
end end
path do
if name
name + '/'
else
''
end
end
end
service do
Files::MultiService.new( Files::MultiService.new(
project, project,
project.owner, project.owner,
......
...@@ -58,13 +58,6 @@ describe Packages::Go::ModuleFinder do ...@@ -58,13 +58,6 @@ describe Packages::Go::ModuleFinder do
end end
end end
context 'with a URL encoded relative path component' do
it_behaves_like 'an invalid path' do
let(:module_name) { base_url(project) + '/%2E%2E%2Fxyz' }
let(:expected_name) { base_url(project.namespace) + '/xyz' }
end
end
context 'with many relative path components' do context 'with many relative path components' do
it_behaves_like 'an invalid path' do it_behaves_like 'an invalid path' do
let(:module_name) { base_url(project) + ('/..' * 10) + '/xyz' } let(:module_name) { base_url(project) + ('/..' * 10) + '/xyz' }
......
...@@ -13,11 +13,16 @@ describe Packages::Go::VersionFinder do ...@@ -13,11 +13,16 @@ describe Packages::Go::VersionFinder do
create :go_module_commit, :module, project: project, tag: 'v1.0.1' create :go_module_commit, :module, project: project, tag: 'v1.0.1'
create :go_module_commit, :package, project: project, tag: 'v1.0.2', path: 'pkg' create :go_module_commit, :package, project: project, tag: 'v1.0.2', path: 'pkg'
create :go_module_commit, :module, project: project, tag: 'v1.0.3', name: 'mod' create :go_module_commit, :module, project: project, tag: 'v1.0.3', name: 'mod'
create :go_module_commit, :module, project: project, tag: 'v1.0.4', name: 'bad-mod', url: 'example.com/go-lib'
create :go_module_commit, :files, project: project, tag: 'c1', files: { 'y.go' => "package a\n" } create :go_module_commit, :files, project: project, tag: 'c1', files: { 'y.go' => "package a\n" }
create :go_module_commit, :module, project: project, tag: 'c2', name: 'v2' create :go_module_commit, :module, project: project, tag: 'c2', name: 'v2'
create :go_module_commit, :files, project: project, tag: 'v2.0.0', files: { 'v2/x.go' => "package a\n" } create :go_module_commit, :files, project: project, tag: 'v2.0.0', files: { 'v2/x.go' => "package a\n" }
end end
before do
stub_feature_flags(go_proxy_disable_gomod_validation: false)
end
shared_examples '#execute' do |*expected| shared_examples '#execute' do |*expected|
it "returns #{expected.empty? ? 'nothing' : expected.join(', ')}" do it "returns #{expected.empty? ? 'nothing' : expected.join(', ')}" do
actual = finder.execute.map { |x| x.name } actual = finder.execute.map { |x| x.name }
...@@ -35,7 +40,7 @@ describe Packages::Go::VersionFinder do ...@@ -35,7 +40,7 @@ describe Packages::Go::VersionFinder do
context 'for the root module' do context 'for the root module' do
let(:mod) { create :go_module, project: project } let(:mod) { create :go_module, project: project }
it_behaves_like '#execute', 'v1.0.1', 'v1.0.2', 'v1.0.3' it_behaves_like '#execute', 'v1.0.1', 'v1.0.2', 'v1.0.3', 'v1.0.4'
end end
context 'for the package' do context 'for the package' do
...@@ -47,7 +52,7 @@ describe Packages::Go::VersionFinder do ...@@ -47,7 +52,7 @@ describe Packages::Go::VersionFinder do
context 'for the submodule' do context 'for the submodule' do
let(:mod) { create :go_module, project: project, path: 'mod' } let(:mod) { create :go_module, project: project, path: 'mod' }
it_behaves_like '#execute', 'v1.0.3' it_behaves_like '#execute', 'v1.0.3', 'v1.0.4'
end end
context 'for the root module v2' do context 'for the root module v2' do
...@@ -55,6 +60,22 @@ describe Packages::Go::VersionFinder do ...@@ -55,6 +60,22 @@ describe Packages::Go::VersionFinder do
it_behaves_like '#execute', 'v2.0.0' it_behaves_like '#execute', 'v2.0.0'
end end
context 'for the bad module' do
let(:mod) { create :go_module, project: project, path: 'bad-mod' }
context 'with gomod checking enabled' do
it_behaves_like '#execute'
end
context 'with gomod checking disabled' do
before do
stub_feature_flags(go_proxy_disable_gomod_validation: true)
end
it_behaves_like '#execute', 'v1.0.4'
end
end
end end
describe '#find' do describe '#find' do
......
...@@ -3,6 +3,10 @@ ...@@ -3,6 +3,10 @@
require 'spec_helper' require 'spec_helper'
describe Packages::GoModule, type: :model do describe Packages::GoModule, type: :model do
before do
stub_feature_flags(go_proxy_disable_gomod_validation: false)
end
describe '#path_valid?' do describe '#path_valid?' do
context 'with root path' do context 'with root path' do
let_it_be(:package) { create(:go_module) } let_it_be(:package) { create(:go_module) }
......
...@@ -19,7 +19,7 @@ describe Packages::GoModuleVersion, type: :model do ...@@ -19,7 +19,7 @@ describe Packages::GoModuleVersion, type: :model do
shared_examples '#files' do |desc, *entries| shared_examples '#files' do |desc, *entries|
it "returns #{desc}" do it "returns #{desc}" do
actual = version.files.map { |x| x.path }.to_set actual = version.files.map { |x| x }.to_set
expect(actual).to eq(entries.to_set) expect(actual).to eq(entries.to_set)
end end
end end
......
...@@ -29,7 +29,9 @@ describe API::GoProxy do ...@@ -29,7 +29,9 @@ describe API::GoProxy do
before do before do
project.add_developer(user) project.add_developer(user)
stub_licensed_features(packages: true) stub_licensed_features(packages: true)
stub_feature_flags(go_proxy_disable_gomod_validation: false)
modules modules
end end
...@@ -54,7 +56,7 @@ describe API::GoProxy do ...@@ -54,7 +56,7 @@ describe API::GoProxy do
end end
end end
shared_examples 'a missing module version list resource' do |*versions, path: ''| shared_examples 'a missing module version list resource' do |path: ''|
let(:module_name) { "#{base}#{path}" } let(:module_name) { "#{base}#{path}" }
let(:resource) { "list" } let(:resource) { "list" }
...@@ -168,6 +170,18 @@ describe API::GoProxy do ...@@ -168,6 +170,18 @@ describe API::GoProxy do
context 'for the root module v2' do context 'for the root module v2' do
it_behaves_like 'a module version list resource', 'v2.0.0', path: '/v2' it_behaves_like 'a module version list resource', 'v2.0.0', path: '/v2'
end end
context 'with a URL encoded relative path component' do
it_behaves_like 'a missing module version list resource', path: '/%2E%2E%2Fxyz'
end
context 'with the feature disabled' do
before do
stub_feature_flags(go_proxy: false)
end
it_behaves_like 'a missing module version list resource'
end
end end
describe 'GET /projects/:id/packages/go/*module_name/@v/:module_version.info' do describe 'GET /projects/:id/packages/go/*module_name/@v/:module_version.info' do
...@@ -356,26 +370,31 @@ describe API::GoProxy do ...@@ -356,26 +370,31 @@ describe API::GoProxy do
it 'returns ok with an oauth token' do it 'returns ok with an oauth token' do
get_resource(oauth_access_token: oauth) get_resource(oauth_access_token: oauth)
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
end end
it 'returns ok with a job token' do it 'returns ok with a job token' do
get_resource(oauth_access_token: job) get_resource(oauth_access_token: job)
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
end end
it 'returns ok with a personal access token' do it 'returns ok with a personal access token' do
get_resource(personal_access_token: pa_token) get_resource(personal_access_token: pa_token)
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
end end
it 'returns ok with a personal access token and basic authentication' do it 'returns ok with a personal access token and basic authentication' do
get_resource(headers: build_basic_auth_header(user.username, pa_token.token)) get_resource(headers: build_basic_auth_header(user.username, pa_token.token))
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
end end
it 'returns unauthorized with no authentication' do it 'returns unauthorized with no authentication' do
get_resource get_resource
expect(response).to have_gitlab_http_status(:unauthorized) expect(response).to have_gitlab_http_status(:unauthorized)
end end
end end
...@@ -393,6 +412,7 @@ describe API::GoProxy do ...@@ -393,6 +412,7 @@ describe API::GoProxy do
it 'returns ok with no authentication' do it 'returns ok with no authentication' do
get_resource get_resource
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
end end
end end
...@@ -406,26 +426,31 @@ describe API::GoProxy do ...@@ -406,26 +426,31 @@ describe API::GoProxy do
describe 'GET /projects/:id/packages/go/*module_name/@v/list' do describe 'GET /projects/:id/packages/go/*module_name/@v/list' do
it 'returns not found with a user' do it 'returns not found with a user' do
get_resource(user) get_resource(user)
expect(response).to have_gitlab_http_status(:not_found) expect(response).to have_gitlab_http_status(:not_found)
end end
it 'returns not found with an oauth token' do it 'returns not found with an oauth token' do
get_resource(oauth_access_token: oauth) get_resource(oauth_access_token: oauth)
expect(response).to have_gitlab_http_status(:not_found) expect(response).to have_gitlab_http_status(:not_found)
end end
it 'returns not found with a job token' do it 'returns not found with a job token' do
get_resource(oauth_access_token: job) get_resource(oauth_access_token: job)
expect(response).to have_gitlab_http_status(:not_found) expect(response).to have_gitlab_http_status(:not_found)
end end
it 'returns not found with a personal access token' do it 'returns not found with a personal access token' do
get_resource(personal_access_token: pa_token) get_resource(personal_access_token: pa_token)
expect(response).to have_gitlab_http_status(:not_found) expect(response).to have_gitlab_http_status(:not_found)
end end
it 'returns unauthorized with no authentication' do it 'returns unauthorized with no authentication' do
get_resource get_resource
expect(response).to have_gitlab_http_status(:unauthorized) expect(response).to have_gitlab_http_status(:unauthorized)
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment