Commit f7dc91a5 authored by Steve Mokris's avatar Steve Mokris Committed by Mark Chao

Allow anonymous access to public Conan packages

- Allow access to ping endpoint without a token
- Allow access to download endpoints without a token, if the project
  is public
- Instead of throwing UnauthorizedError when no token is provided,
  continue with anonymous privileges
- Update tests to reflect the above changes
- Add test for downloading without a token
parent 31b2c00c
...@@ -44,7 +44,7 @@ module Packages ...@@ -44,7 +44,7 @@ module Packages
name, version, username, _ = query.split(/[@\/]/) name, version, username, _ = query.split(/[@\/]/)
full_path = Packages::Conan::Metadatum.full_path_from(package_username: username) full_path = Packages::Conan::Metadatum.full_path_from(package_username: username)
project = Project.find_by_full_path(full_path) project = Project.find_by_full_path(full_path)
return unless current_user.can?(:read_package, project) return unless Ability.allowed?(current_user, :read_package, project)
result = project.packages.with_name(name).with_version(version).order_created.last result = project.packages.with_name(name).with_version(version).order_created.last
[result&.conan_recipe].compact [result&.conan_recipe].compact
......
---
title: Allow anonymous access to public Conan packages
merge_request: 54047
author: Steve Mokris @smokris
type: changed
...@@ -171,6 +171,10 @@ convention. ...@@ -171,6 +171,10 @@ convention.
## Authenticate to the Package Registry ## Authenticate to the Package Registry
GitLab requires authentication to upload packages, and to install packages
from private and internal projects. (You can, however, install packages
from public projects without authentication.)
To authenticate to the Package Registry, you need one of the following: To authenticate to the Package Registry, you need one of the following:
- A [personal access token](../../../user/profile/personal_access_tokens.md) - A [personal access token](../../../user/profile/personal_access_tokens.md)
...@@ -302,8 +306,9 @@ file. ...@@ -302,8 +306,9 @@ file.
Prerequisites: Prerequisites:
- The Conan remote [must be configured](#add-the-package-registry-as-a-conan-remote). - The Conan remote [must be configured](#add-the-package-registry-as-a-conan-remote).
- [Authentication](#authenticate-to-the-package-registry) with the - For private and internal projects, you must configure
Package Registry must be configured. [Authentication](#authenticate-to-the-package-registry)
with the Package Registry.
1. In the project where you want to install the package as a dependency, open 1. In the project where you want to install the package as a dependency, open
`conanfile.txt`. Or, in the root of your project, create a file called `conanfile.txt`. Or, in the root of your project, create a file called
......
...@@ -42,7 +42,7 @@ module API ...@@ -42,7 +42,7 @@ module API
# Personal access token will be extracted from Bearer or Basic authorization # Personal access token will be extracted from Bearer or Basic authorization
# in the overridden find_personal_access_token or find_user_from_job_token helpers # in the overridden find_personal_access_token or find_user_from_job_token helpers
authenticate! authenticate_non_get!
end end
desc 'Ping the Conan API' do desc 'Ping the Conan API' do
...@@ -71,6 +71,10 @@ module API ...@@ -71,6 +71,10 @@ module API
end end
namespace 'users' do namespace 'users' do
before do
authenticate!
end
format :txt format :txt
content_type :txt, 'text/plain' content_type :txt, 'text/plain'
......
...@@ -221,7 +221,7 @@ module API ...@@ -221,7 +221,7 @@ module API
def find_user_from_job_token def find_user_from_job_token
return unless route_authentication_setting[:job_token_allowed] return unless route_authentication_setting[:job_token_allowed]
job = find_job_from_token || raise(::Gitlab::Auth::UnauthorizedError) job = find_job_from_token || return
@current_authenticated_job = job # rubocop:disable Gitlab/ModuleWithInstanceVariables @current_authenticated_job = job # rubocop:disable Gitlab/ModuleWithInstanceVariables
job.user job.user
......
# frozen_string_literal: true # frozen_string_literal: true
RSpec.shared_examples 'conan ping endpoint' do RSpec.shared_examples 'conan ping endpoint' do
it 'responds with 401 Unauthorized when no token provided' do it 'responds with 200 OK when no token provided' do
get api(url) get api(url)
expect(response).to have_gitlab_http_status(:unauthorized)
end
it 'responds with 200 OK when valid token is provided' do
jwt = build_jwt(personal_access_token)
get api(url), headers: build_token_auth_header(jwt.encoded)
expect(response).to have_gitlab_http_status(:ok)
expect(response.headers['X-Conan-Server-Capabilities']).to eq("")
end
it 'responds with 200 OK when valid job token is provided' do
jwt = build_jwt_from_job(job)
get api(url), headers: build_token_auth_header(jwt.encoded)
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(response.headers['X-Conan-Server-Capabilities']).to eq("") expect(response.headers['X-Conan-Server-Capabilities']).to eq("")
end end
it 'responds with 200 OK when valid deploy token is provided' do
jwt = build_jwt_from_deploy_token(deploy_token)
get api(url), headers: build_token_auth_header(jwt.encoded)
expect(response).to have_gitlab_http_status(:ok)
expect(response.headers['X-Conan-Server-Capabilities']).to eq("")
end
it 'responds with 401 Unauthorized when invalid access token ID is provided' do
jwt = build_jwt(double(id: 12345), user_id: personal_access_token.user_id)
get api(url), headers: build_token_auth_header(jwt.encoded)
expect(response).to have_gitlab_http_status(:unauthorized)
end
it 'responds with 401 Unauthorized when invalid user is provided' do
jwt = build_jwt(personal_access_token, user_id: 12345)
get api(url), headers: build_token_auth_header(jwt.encoded)
expect(response).to have_gitlab_http_status(:unauthorized)
end
it 'responds with 401 Unauthorized when the provided JWT is signed with different secret' do
jwt = build_jwt(personal_access_token, secret: SecureRandom.base64(32))
get api(url), headers: build_token_auth_header(jwt.encoded)
expect(response).to have_gitlab_http_status(:unauthorized)
end
it 'responds with 401 Unauthorized when invalid JWT is provided' do
get api(url), headers: build_token_auth_header('invalid-jwt')
expect(response).to have_gitlab_http_status(:unauthorized)
end
context 'packages feature disabled' do context 'packages feature disabled' do
it 'responds with 404 Not Found' do it 'responds with 404 Not Found' do
stub_packages_setting(enabled: false) stub_packages_setting(enabled: false)
...@@ -72,7 +22,10 @@ RSpec.shared_examples 'conan search endpoint' do ...@@ -72,7 +22,10 @@ RSpec.shared_examples 'conan search endpoint' do
before do before do
project.update_column(:visibility_level, Gitlab::VisibilityLevel::PUBLIC) project.update_column(:visibility_level, Gitlab::VisibilityLevel::PUBLIC)
get api(url), headers: headers, params: params # Do not pass the HTTP_AUTHORIZATION header,
# in order to test that this public project's packages
# are visible to anonymous search.
get api(url), params: params
end end
subject { json_response['results'] } subject { json_response['results'] }
...@@ -109,6 +62,33 @@ RSpec.shared_examples 'conan authenticate endpoint' do ...@@ -109,6 +62,33 @@ RSpec.shared_examples 'conan authenticate endpoint' do
end end
end end
it 'responds with 401 Unauthorized when an invalid access token ID is provided' do
jwt = build_jwt(double(id: 12345), user_id: personal_access_token.user_id)
get api(url), headers: build_token_auth_header(jwt.encoded)
expect(response).to have_gitlab_http_status(:unauthorized)
end
it 'responds with 401 Unauthorized when invalid user is provided' do
jwt = build_jwt(personal_access_token, user_id: 12345)
get api(url), headers: build_token_auth_header(jwt.encoded)
expect(response).to have_gitlab_http_status(:unauthorized)
end
it 'responds with 401 Unauthorized when the provided JWT is signed with different secret' do
jwt = build_jwt(personal_access_token, secret: SecureRandom.base64(32))
get api(url), headers: build_token_auth_header(jwt.encoded)
expect(response).to have_gitlab_http_status(:unauthorized)
end
it 'responds with 401 UnauthorizedOK when invalid JWT is provided' do
get api(url), headers: build_token_auth_header('invalid-jwt')
expect(response).to have_gitlab_http_status(:unauthorized)
end
context 'when valid JWT access token is provided' do context 'when valid JWT access token is provided' do
it 'responds with 200' do it 'responds with 200' do
subject subject
...@@ -507,19 +487,37 @@ RSpec.shared_examples 'delete package endpoint' do ...@@ -507,19 +487,37 @@ RSpec.shared_examples 'delete package endpoint' do
end end
end end
RSpec.shared_examples 'allows download with no token' do
context 'with no private token' do
let(:headers) { {} }
it 'returns 200' do
subject
expect(response).to have_gitlab_http_status(:ok)
end
end
end
RSpec.shared_examples 'denies download with no token' do RSpec.shared_examples 'denies download with no token' do
context 'with no private token' do context 'with no private token' do
let(:headers) { {} } let(:headers) { {} }
it 'returns 400' do it 'returns 404' do
subject subject
expect(response).to have_gitlab_http_status(:unauthorized) expect(response).to have_gitlab_http_status(:not_found)
end end
end end
end end
RSpec.shared_examples 'a public project with packages' do RSpec.shared_examples 'a public project with packages' do
before do
project.update_column(:visibility_level, Gitlab::VisibilityLevel::PUBLIC)
end
it_behaves_like 'allows download with no token'
it 'returns the file' do it 'returns the file' do
subject subject
......
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