Commit fdbb8612 authored by Giorgenes Gelatti's avatar Giorgenes Gelatti

Adds PHP Composer API skeleton

- Adds basic entry points for composer package installation
- Adds basic group authorization
- Adds authorization specs
parent 45b8f11d
# frozen_string_literal: true
# PHP composer support (https://getcomposer.org/)
module API
class ComposerPackages < Grape::API
helpers ::API::Helpers::PackagesManagerClientsHelpers
helpers ::API::Helpers::RelatedResourcesHelpers
helpers ::API::Helpers::Packages::BasicAuthHelpers
include ::API::Helpers::Packages::BasicAuthHelpers::Constants
content_type :json, 'application/json'
default_format :json
COMPOSER_ENDPOINT_REQUIREMENTS = {
package_name: API::NO_SLASH_URL_PART_REGEX
}.freeze
default_format :json
rescue_from ArgumentError do |e|
render_api_error!(e.message, 400)
end
rescue_from ActiveRecord::RecordInvalid do |e|
render_api_error!(e.message, 400)
end
before do
require_packages_enabled!
end
params do
requires :id, type: String, desc: 'The ID of a group'
end
resource :group, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
before do
unless ::Feature.enabled?(:composer_packages, user_group)
not_found!
end
authorize_packages_feature!(user_group)
end
desc 'Composer packages endpoint at group level'
route_setting :authentication, job_token_allowed: true
get ':id/-/packages/composer/packages' do
end
desc 'Composer packages endpoint at group level for packages list'
params do
requires :sha, type: String, desc: 'Shasum of current json'
end
route_setting :authentication, job_token_allowed: true
get ':id/-/packages/composer/p/:sha' do
end
desc 'Composer packages endpoint at group level for package versions metadata'
route_setting :authentication, job_token_allowed: true
get ':id/-/packages/composer/*package_name', requirements: COMPOSER_ENDPOINT_REQUIREMENTS, file_path: true do
end
end
params do
requires :id, type: Integer, desc: 'The ID of a project'
end
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
before do
unless ::Feature.enabled?(:composer_packages, authorized_user_project)
not_found!
end
authorize_packages_feature!(authorized_user_project)
end
desc 'Composer packages endpoint for registering packages'
params do
optional :branch, type: String, desc: 'The name of the branch'
optional :tag, type: String, desc: 'The name of the tag'
end
namespace ':id/packages/composer' do
post do
authorize_create_package!(authorized_user_project)
created!
end
end
end
end
end
......@@ -34,6 +34,7 @@ module EE
mount ::API::ProjectPushRule
mount ::API::NugetPackages
mount ::API::PypiPackages
mount ::API::ComposerPackages
mount ::API::ConanPackages
mount ::API::MavenPackages
mount ::API::NpmPackages
......
# frozen_string_literal: true
require 'spec_helper'
describe API::ComposerPackages do
include EE::PackagesManagerApiSpecHelpers
let_it_be(:user) { create(:user) }
let_it_be(:group, reload: true) { create(:group, :public) }
let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
let_it_be(:project, reload: true) { create(:project, :public) }
describe 'GET /api/v4/group/:id/-/packages/composer/packages' do
let(:url) { "/group/#{group.id}/-/packages/composer/packages.json" }
subject { get api(url) }
context 'with packages features enabled' do
before do
stub_licensed_features(packages: true)
end
context 'with valid project' do
using RSpec::Parameterized::TableSyntax
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
'PUBLIC' | :developer | true | true | 'process Composer api request' | :success
'PUBLIC' | :guest | true | true | 'process Composer api request' | :success
'PUBLIC' | :developer | true | false | 'process Composer api request' | :success
'PUBLIC' | :guest | true | false | 'process Composer api request' | :success
'PUBLIC' | :developer | false | true | 'process Composer api request' | :success
'PUBLIC' | :guest | false | true | 'process Composer api request' | :success
'PUBLIC' | :developer | false | false | 'process Composer api request' | :success
'PUBLIC' | :guest | false | false | 'process Composer api request' | :success
'PUBLIC' | :anonymous | false | true | 'process Composer api request' | :success
'PRIVATE' | :developer | true | true | 'process Composer api request' | :success
'PRIVATE' | :guest | true | true | 'process Composer api request' | :success
'PRIVATE' | :developer | true | false | 'process Composer api request' | :not_found
'PRIVATE' | :guest | true | false | 'process Composer api request' | :not_found
'PRIVATE' | :developer | false | true | 'process Composer api request' | :not_found
'PRIVATE' | :guest | false | true | 'process Composer api request' | :not_found
'PRIVATE' | :developer | false | false | 'process Composer api request' | :not_found
'PRIVATE' | :guest | false | false | 'process Composer api request' | :not_found
'PRIVATE' | :anonymous | false | true | 'process Composer api request' | :not_found
end
with_them do
let(:token) { user_token ? personal_access_token.token : 'wrong' }
let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
subject { get api(url), headers: headers }
before do
group.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
end
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
end
end
it_behaves_like 'rejects Composer access with unknown group id'
end
it_behaves_like 'rejects Composer packages access with packages features disabled'
end
describe 'GET /api/v4/group/:id/-/packages/composer/p/:sha.json' do
let(:sha) { '123' }
let(:url) { "/group/#{group.id}/-/packages/composer/p/#{sha}.json" }
subject { get api(url) }
context 'with packages features enabled' do
before do
stub_licensed_features(packages: true)
end
context 'with valid project' do
using RSpec::Parameterized::TableSyntax
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
'PUBLIC' | :developer | true | true | 'process Composer api request' | :success
'PUBLIC' | :guest | true | true | 'process Composer api request' | :success
'PUBLIC' | :developer | true | false | 'process Composer api request' | :success
'PUBLIC' | :guest | true | false | 'process Composer api request' | :success
'PUBLIC' | :developer | false | true | 'process Composer api request' | :success
'PUBLIC' | :guest | false | true | 'process Composer api request' | :success
'PUBLIC' | :developer | false | false | 'process Composer api request' | :success
'PUBLIC' | :guest | false | false | 'process Composer api request' | :success
'PUBLIC' | :anonymous | false | true | 'process Composer api request' | :success
'PRIVATE' | :developer | true | true | 'process Composer api request' | :success
'PRIVATE' | :guest | true | true | 'process Composer api request' | :success
'PRIVATE' | :developer | true | false | 'process Composer api request' | :not_found
'PRIVATE' | :guest | true | false | 'process Composer api request' | :not_found
'PRIVATE' | :developer | false | true | 'process Composer api request' | :not_found
'PRIVATE' | :guest | false | true | 'process Composer api request' | :not_found
'PRIVATE' | :developer | false | false | 'process Composer api request' | :not_found
'PRIVATE' | :guest | false | false | 'process Composer api request' | :not_found
'PRIVATE' | :anonymous | false | true | 'process Composer api request' | :not_found
end
with_them do
let(:token) { user_token ? personal_access_token.token : 'wrong' }
let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
subject { get api(url), headers: headers }
before do
group.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
end
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
end
end
it_behaves_like 'rejects Composer access with unknown group id'
end
it_behaves_like 'rejects Composer packages access with packages features disabled'
end
describe 'GET /api/v4/group/:id/-/packages/composer/*package_name.json' do
let(:package_name) { 'foobar' }
let(:url) { "/group/#{group.id}/-/packages/composer/#{package_name}.json" }
subject { get api(url) }
context 'with packages features enabled' do
before do
stub_licensed_features(packages: true)
end
context 'with valid project' do
using RSpec::Parameterized::TableSyntax
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
'PUBLIC' | :developer | true | true | 'process Composer api request' | :success
'PUBLIC' | :guest | true | true | 'process Composer api request' | :success
'PUBLIC' | :developer | true | false | 'process Composer api request' | :success
'PUBLIC' | :guest | true | false | 'process Composer api request' | :success
'PUBLIC' | :developer | false | true | 'process Composer api request' | :success
'PUBLIC' | :guest | false | true | 'process Composer api request' | :success
'PUBLIC' | :developer | false | false | 'process Composer api request' | :success
'PUBLIC' | :guest | false | false | 'process Composer api request' | :success
'PUBLIC' | :anonymous | false | true | 'process Composer api request' | :success
'PRIVATE' | :developer | true | true | 'process Composer api request' | :success
'PRIVATE' | :guest | true | true | 'process Composer api request' | :success
'PRIVATE' | :developer | true | false | 'process Composer api request' | :not_found
'PRIVATE' | :guest | true | false | 'process Composer api request' | :not_found
'PRIVATE' | :developer | false | true | 'process Composer api request' | :not_found
'PRIVATE' | :guest | false | true | 'process Composer api request' | :not_found
'PRIVATE' | :developer | false | false | 'process Composer api request' | :not_found
'PRIVATE' | :guest | false | false | 'process Composer api request' | :not_found
'PRIVATE' | :anonymous | false | true | 'process Composer api request' | :not_found
end
with_them do
let(:token) { user_token ? personal_access_token.token : 'wrong' }
let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
subject { get api(url), headers: headers }
before do
group.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
end
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
end
end
it_behaves_like 'rejects Composer access with unknown group id'
end
it_behaves_like 'rejects Composer packages access with packages features disabled'
end
describe 'POST /api/v4/projects/:id/packages/composer' do
let(:url) { "/projects/#{project.id}/packages/composer" }
let(:params) { { branch: 'foobar' } }
subject { post api(url), headers: headers }
context 'with packages features enabled' do
before do
stub_licensed_features(packages: true)
end
context 'with valid project' do
using RSpec::Parameterized::TableSyntax
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
'PUBLIC' | :developer | true | true | 'process Composer api request' | :created
'PUBLIC' | :guest | true | true | 'process Composer api request' | :forbidden
'PUBLIC' | :developer | true | false | 'process Composer api request' | :unauthorized
'PUBLIC' | :guest | true | false | 'process Composer api request' | :unauthorized
'PUBLIC' | :developer | false | true | 'process Composer api request' | :forbidden
'PUBLIC' | :guest | false | true | 'process Composer api request' | :forbidden
'PUBLIC' | :developer | false | false | 'process Composer api request' | :unauthorized
'PUBLIC' | :guest | false | false | 'process Composer api request' | :unauthorized
'PUBLIC' | :anonymous | false | true | 'process Composer api request' | :unauthorized
'PRIVATE' | :developer | true | true | 'process Composer api request' | :created
'PRIVATE' | :guest | true | true | 'process Composer api request' | :forbidden
'PRIVATE' | :developer | true | false | 'process Composer api request' | :unauthorized
'PRIVATE' | :guest | true | false | 'process Composer api request' | :unauthorized
'PRIVATE' | :developer | false | true | 'process Composer api request' | :not_found
'PRIVATE' | :guest | false | true | 'process Composer api request' | :not_found
'PRIVATE' | :developer | false | false | 'process Composer api request' | :unauthorized
'PRIVATE' | :guest | false | false | 'process Composer api request' | :unauthorized
'PRIVATE' | :anonymous | false | true | 'process Composer api request' | :unauthorized
end
with_them do
let(:token) { user_token ? personal_access_token.token : 'wrong' }
let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
before do
project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
end
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
end
end
it_behaves_like 'rejects Composer access with unknown project id'
end
it_behaves_like 'rejects Composer packages access with packages features disabled'
end
end
# frozen_string_literal: true
RSpec.shared_examples 'process Composer api request' do |user_type, status, add_member = true|
context "for user type #{user_type}" do
before do
group.send("add_#{user_type}", user) if add_member && user_type != :anonymous
project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
end
it_behaves_like 'returning response status', status
end
end
RSpec.shared_examples 'rejects Composer access with unknown group id' do
context 'with an unknown group' do
let(:group) { double(id: non_existing_record_id) }
context 'as anonymous' do
it_behaves_like 'process Composer api request', :anonymous, :not_found
end
context 'as authenticated user' do
subject { get api(url), headers: build_basic_auth_header(user.username, personal_access_token.token) }
it_behaves_like 'process Composer api request', :anonymous, :not_found
end
end
end
RSpec.shared_examples 'rejects Composer packages access with packages features disabled' do
context 'with packages features disabled' do
before do
stub_licensed_features(packages: false)
end
it_behaves_like 'process Composer api request', :anonymous, :forbidden
end
end
RSpec.shared_examples 'rejects Composer access with unknown project id' do
context 'with an unknown project' do
let(:project) { double(id: non_existing_record_id) }
context 'as anonymous' do
it_behaves_like 'process PyPi api request', :anonymous, :unauthorized
end
context 'as authenticated user' do
subject { get api(url), headers: build_basic_auth_header(user.username, personal_access_token.token) }
it_behaves_like 'process PyPi api request', :anonymous, :not_found
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