Commit 26578c4c authored by Grzegorz Bizon's avatar Grzegorz Bizon

Merge branch '13345-conan-check_credentials-endpoint' into 'master'

Conan registry check_credentials endpoint

See merge request gitlab-org/gitlab!16215
parents 7e2969b7 2d5571f9
---
title: Add Conan check_credentials API endpoint
merge_request: 16215
author:
type: added
......@@ -16,13 +16,21 @@ module API
namespace 'packages/conan/v1/users/' do
format :txt
desc 'Authenticate user' do
desc 'Authenticate user against conan CLI' do
detail 'This feature was introduced in GitLab 12.2'
end
get 'authenticate' do
token = ::Gitlab::ConanToken.from_personal_access_token(access_token)
token.to_jwt
end
desc 'Check for valid user credentials per conan CLI' do
detail 'This feature was introduced in GitLab 12.3'
end
get 'check_credentials' do
authenticate!
:ok
end
end
namespace 'packages/conan/v1/' do
......@@ -34,7 +42,91 @@ module API
end
end
namespace 'packages/conan/v1/conans/*url_recipe' do
before do
render_api_error!("Invalid recipe", 400) unless valid_url_recipe?(params[:url_recipe])
end
params do
requires :url_recipe, type: String, desc: 'Package recipe'
end
# Get the recipe manifest
# returns the download urls for the existing recipe in the registry
#
# the manifest is a hash of { filename: url }
# where the url is the download url for the file
desc 'Package Digest' do
detail 'This feature was introduced in GitLab 12.3'
end
get 'packages/:package_id/digest' do
render_api_error!("No recipe manifest found", 404)
end
desc 'Recipe Digest' do
detail 'This feature was introduced in GitLab 12.3'
end
get 'digest' do
render_api_error!("No recipe manifest found", 404)
end
# Get the upload urls
#
# request body contains { filename: filesize } where the filename is the
# name of the file the conan client is requesting to upload
#
# returns { filename: url }
# where the url is the upload url for the file that the conan client will use
desc 'Package Upload Urls' do
detail 'This feature was introduced in GitLab 12.3'
end
params do
requires :package_id, type: String, desc: 'Conan package ID'
end
post 'packages/:package_id/upload_urls' do
status 200
{
'conaninfo.txt': "#{base_file_url}/#{params[:url_recipe]}/-/0/package/#{params[:package_id]}/0/conaninfo.txt",
'conanmanifest.txt': "#{base_file_url}/#{params[:url_recipe]}/-/0/package/#{params[:package_id]}/0/conanmanifest.txt",
'conan_package.tgz': "#{base_file_url}/#{params[:url_recipe]}/-/0/package/#{params[:package_id]}/0/conan_package.tgz"
}
end
desc 'Recipe Upload Urls' do
detail 'This feature was introduced in GitLab 12.3'
end
post 'upload_urls' do
status 200
{
'conanfile.py': "#{base_file_url}/#{params[:url_recipe]}/-/0/export/conanfile.py",
'conanmanifest.txt': "#{base_file_url}/#{params[:url_recipe]}/-/0/export/conanmanifest.txt"
}
end
# Get the recipe snapshot
#
# the snapshot is a hash of { filename: md5 hash }
# md5 hash is the has of that file. This hash is used to diff the files existing on the client
# to determine which client files need to be uploaded if no recipe exists the snapshot is empty
desc 'Recipe Snapshot' do
detail 'This feature was introduced in GitLab 12.3'
end
get '/' do
{}
end
desc 'Package Snapshot' do
detail 'This feature was introduced in GitLab 12.3'
end
get 'packages/:package_id' do
{}
end
end
helpers do
def base_file_url
"#{::Settings.gitlab.base_url}/api/v4/packages/conan/v1/files"
end
def find_personal_access_token
personal_access_token = find_personal_access_token_from_conan_jwt ||
find_personal_access_token_from_conan_http_basic_auth
......@@ -64,6 +156,10 @@ module API
PersonalAccessToken.find_by_token(token)
end
def valid_url_recipe?(recipe_url)
recipe_url =~ %r{\A(([\w](\.|\+|-)?)*(\/?)){4}\z}
end
end
end
end
......@@ -12,6 +12,11 @@ describe API::ConanPackages do
)
end
let(:personal_access_token) { create(:personal_access_token) }
let(:headers) do
{ 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials('foo', personal_access_token.token) }
end
before do
stub_licensed_features(packages: true)
allow(Settings).to receive(:attr_encrypted_db_key_base).and_return(base_secret)
......@@ -24,6 +29,10 @@ describe API::ConanPackages do
end
end
def build_auth_headers(token)
{ 'HTTP_AUTHORIZATION' => "Bearer #{token}" }
end
describe 'GET /api/v4/packages/conan/v1/ping' do
context 'feature flag disabled' do
before do
......@@ -45,46 +54,36 @@ describe API::ConanPackages do
end
it 'responds with 200 OK when valid token is provided' do
personal_access_token = create(:personal_access_token)
jwt = build_jwt(personal_access_token)
headers = { 'HTTP_AUTHORIZATION' => "Bearer #{jwt.encoded}" }
get api('/packages/conan/v1/ping'), headers: headers
get api('/packages/conan/v1/ping'), headers: build_auth_headers(jwt.encoded)
expect(response).to have_gitlab_http_status(200)
expect(response.headers['X-Conan-Server-Capabilities']).to eq("")
end
it 'responds with 401 Unauthorized when invalid access token ID is provided' do
personal_access_token = create(:personal_access_token)
jwt = build_jwt(double(id: 12345), user_id: personal_access_token.user_id)
headers = { 'HTTP_AUTHORIZATION' => "Bearer #{jwt.encoded}" }
get api('/packages/conan/v1/ping'), headers: headers
get api('/packages/conan/v1/ping'), headers: build_auth_headers(jwt.encoded)
expect(response).to have_gitlab_http_status(401)
end
it 'responds with 401 Unauthorized when invalid user is provided' do
personal_access_token = create(:personal_access_token)
jwt = build_jwt(personal_access_token, user_id: 12345)
headers = { 'HTTP_AUTHORIZATION' => "Bearer #{jwt.encoded}" }
get api('/packages/conan/v1/ping'), headers: headers
get api('/packages/conan/v1/ping'), headers: build_auth_headers(jwt.encoded)
expect(response).to have_gitlab_http_status(401)
end
it 'responds with 401 Unauthorized when the provided JWT is signed with different secret' do
personal_access_token = create(:personal_access_token)
jwt = build_jwt(personal_access_token, secret: SecureRandom.base64(32))
headers = { 'HTTP_AUTHORIZATION' => "Bearer #{jwt.encoded}" }
get api('/packages/conan/v1/ping'), headers: headers
get api('/packages/conan/v1/ping'), headers: build_auth_headers(jwt.encoded)
expect(response).to have_gitlab_http_status(401)
end
it 'responds with 401 Unauthorized when invalid JWT is provided' do
headers = { 'HTTP_AUTHORIZATION' => "Bearer invalid-jwt" }
get api('/packages/conan/v1/ping'), headers: headers
get api('/packages/conan/v1/ping'), headers: build_auth_headers('invalid-jwt')
expect(response).to have_gitlab_http_status(401)
end
......@@ -109,8 +108,6 @@ describe API::ConanPackages do
end
it 'responds with 200 OK and JWT when valid access token is provided' do
personal_access_token = create(:personal_access_token)
headers = { 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials('foo', personal_access_token.token) }
get api('/packages/conan/v1/users/authenticate'), headers: headers
expect(response).to have_gitlab_http_status(200)
......@@ -123,4 +120,129 @@ describe API::ConanPackages do
expect(duration).to eq(1.hour)
end
end
describe 'GET /api/v4/packages/conan/v1/users/check_credentials' do
it 'responds with a 200 OK' do
get api('/packages/conan/v1/users/check_credentials'), headers: headers
expect(response).to have_gitlab_http_status(200)
end
it 'responds with a 401 Unauthorized when an invalid token is used' do
get api('/packages/conan/v1/users/check_credentials'), headers: build_auth_headers('invalid-token')
expect(response).to have_gitlab_http_status(401)
end
end
shared_examples 'rejected invalid recipe' do
context 'with invalid recipe url' do
let(:recipe) { '../../foo++../..' }
it 'returns 400' do
subject
expect(response).to have_gitlab_http_status(400)
end
end
end
context 'recipe endpoints' do
let(:jwt) { build_jwt(personal_access_token) }
let(:headers) { build_auth_headers(jwt.encoded) }
let(:recipe) { 'my-package-name/1.0/username/channel' }
describe 'GET /api/v4/packages/conan/v1/conans/*recipe' do
subject { get api("/packages/conan/v1/conans/#{recipe}"), headers: headers }
it_behaves_like 'rejected invalid recipe'
it 'responds with an empty response' do
subject
expect(response.body).to be {}
end
end
describe 'GET /api/v4/packages/conan/v1/conans/*recipe/digest' do
subject { get api("/packages/conan/v1/conans/#{recipe}/digest"), headers: headers }
it_behaves_like 'rejected invalid recipe'
it 'responds with a 404' do
subject
expect(response).to have_gitlab_http_status(404)
end
end
describe 'GET /api/v4/packages/conan/v1/conans/*recipe/upload_urls' do
let(:params) do
{ "conanfile.py": 24,
"conanmanifext.txt": 123 }
end
subject { post api("/packages/conan/v1/conans/#{recipe}/upload_urls"), params: params, headers: headers }
it_behaves_like 'rejected invalid recipe'
it 'returns a set of upload urls for the files requested' do
subject
expected_response = {
'conanfile.py': "#{Settings.gitlab.base_url}/api/v4/packages/conan/v1/files/#{recipe}/-/0/export/conanfile.py",
'conanmanifest.txt': "#{Settings.gitlab.base_url}/api/v4/packages/conan/v1/files/#{recipe}/-/0/export/conanmanifest.txt"
}
expect(response.body).to eq expected_response.to_json
end
end
describe 'GET /api/v4/packages/conan/v1/conans/*recipe/packages/:package_id' do
subject { get api("/packages/conan/v1/conans/#{recipe}/packages/123456789"), headers: headers }
it_behaves_like 'rejected invalid recipe'
it 'responds with an empty response' do
subject
expect(response.body).to be {}
end
end
describe 'GET /api/v4/packages/conan/v1/conans/*recipe/packages/:package_id/digest' do
subject { get api("/packages/conan/v1/conans/#{recipe}/packages/123456789/digest"), headers: headers }
it_behaves_like 'rejected invalid recipe'
it 'responds with a 404' do
subject
expect(response).to have_gitlab_http_status(404)
end
end
describe 'GET /api/v4/packages/conan/v1/conans/*recipe/packages/:package_id/upload_urls' do
let(:params) do
{ "conaninfo.txt": 24,
"conanmanifext.txt": 123,
"conan_package.tgz": 523 }
end
context 'valid recipe' do
subject { post api("/packages/conan/v1/conans/#{recipe}/packages/123456789/upload_urls"), params: params, headers: headers }
it_behaves_like 'rejected invalid recipe'
it 'returns a set of upload urls for the files requested' do
expected_response = {
'conaninfo.txt': "#{Settings.gitlab.base_url}/api/v4/packages/conan/v1/files/#{recipe}/-/0/package/123456789/0/conaninfo.txt",
'conanmanifest.txt': "#{Settings.gitlab.base_url}/api/v4/packages/conan/v1/files/#{recipe}/-/0/package/123456789/0/conanmanifest.txt",
'conan_package.tgz': "#{Settings.gitlab.base_url}/api/v4/packages/conan/v1/files/#{recipe}/-/0/package/123456789/0/conan_package.tgz"
}
subject
expect(response.body).to eq expected_response.to_json
end
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