gitlab-org--gitlab-foss/lib/api/projects.rb
Toon Claes 7c9983c721 Allow project owners to set up forking relation through API
Before this change only GitLab admins where allowed to set up forking
relation through the API. This changes that so project owners can
do this too.

Closes gitlab-org/gitlab-ce#40550.
2018-08-20 16:25:02 +02:00

509 lines
17 KiB
Ruby

require_dependency 'declarative_policy'
module API
class Projects < Grape::API
include PaginationParams
include Helpers::CustomAttributes
include Helpers::ProjectsHelpers
before { authenticate_non_get! }
helpers do
params :optional_filter_params_ee do
# EE::API::Projects would override this helper
end
params :optional_update_params_ee do
# EE::API::Projects would override this helper
end
# EE::API::Projects would override this method
def apply_filters(projects)
projects = projects.with_issues_available_for_user(current_user) if params[:with_issues_enabled]
projects = projects.with_merge_requests_enabled if params[:with_merge_requests_enabled]
projects = projects.with_statistics if params[:statistics]
projects
end
def verify_update_project_attrs!(project, attrs)
end
end
def self.update_params_at_least_one_of
[
:jobs_enabled,
:resolve_outdated_diff_discussions,
:ci_config_path,
:container_registry_enabled,
:default_branch,
:description,
:issues_enabled,
:lfs_enabled,
:merge_requests_enabled,
:merge_method,
:name,
:only_allow_merge_if_all_discussions_are_resolved,
:only_allow_merge_if_pipeline_succeeds,
:path,
:printing_merge_request_link_enabled,
:public_builds,
:request_access_enabled,
:shared_runners_enabled,
:snippets_enabled,
:tag_list,
:visibility,
:wiki_enabled,
:avatar
]
end
helpers do
params :statistics_params do
optional :statistics, type: Boolean, default: false, desc: 'Include project statistics'
end
params :collection_params do
use :sort_params
use :filter_params
use :pagination
optional :simple, type: Boolean, default: false,
desc: 'Return only the ID, URL, name, and path of each project'
end
params :sort_params do
optional :order_by, type: String, values: %w[id name path created_at updated_at last_activity_at],
default: 'created_at', desc: 'Return projects ordered by field'
optional :sort, type: String, values: %w[asc desc], default: 'desc',
desc: 'Return projects sorted in ascending and descending order'
end
params :filter_params do
optional :archived, type: Boolean, desc: 'Limit by archived status'
optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values,
desc: 'Limit by visibility'
optional :search, type: String, desc: 'Return list of projects matching the search criteria'
optional :owned, type: Boolean, default: false, desc: 'Limit by owned by authenticated user'
optional :starred, type: Boolean, default: false, desc: 'Limit by starred status'
optional :membership, type: Boolean, default: false, desc: 'Limit by projects that the current user is a member of'
optional :with_issues_enabled, type: Boolean, default: false, desc: 'Limit by enabled issues feature'
optional :with_merge_requests_enabled, type: Boolean, default: false, desc: 'Limit by enabled merge requests feature'
optional :min_access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'Limit by minimum access level of authenticated user'
use :optional_filter_params_ee
end
params :create_params do
optional :namespace_id, type: Integer, desc: 'Namespace ID for the new project. Default to the user namespace.'
optional :import_url, type: String, desc: 'URL from which the project is imported'
end
def load_projects
ProjectsFinder.new(current_user: current_user, params: project_finder_params).execute
end
def present_projects(projects, options = {})
projects = reorder_projects(projects)
projects = apply_filters(projects)
projects = paginate(projects)
projects, options = with_custom_attributes(projects, options)
options = options.reverse_merge(
with: current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails,
statistics: params[:statistics],
current_user: current_user
)
options[:with] = Entities::BasicProjectDetails if params[:simple]
present options[:with].prepare_relation(projects, options), options
end
def translate_params_for_compatibility(params)
params[:builds_enabled] = params.delete(:jobs_enabled) if params.key?(:jobs_enabled)
params
end
end
resource :users, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
desc 'Get a user projects' do
success Entities::BasicProjectDetails
end
params do
requires :user_id, type: String, desc: 'The ID or username of the user'
use :collection_params
use :statistics_params
use :with_custom_attributes
end
get ":user_id/projects" do
user = find_user(params[:user_id])
not_found!('User') unless user
params[:user] = user
present_projects load_projects
end
end
resource :projects do
include CustomAttributesEndpoints
desc 'Get a list of visible projects for authenticated user' do
success Entities::BasicProjectDetails
end
params do
use :collection_params
use :statistics_params
use :with_custom_attributes
end
get do
present_projects load_projects
end
desc 'Create new project' do
success Entities::Project
end
params do
optional :name, type: String, desc: 'The name of the project'
optional :path, type: String, desc: 'The path of the repository'
at_least_one_of :name, :path
use :optional_project_params
use :create_params
end
post do
attrs = declared_params(include_missing: false)
attrs = translate_params_for_compatibility(attrs)
project = ::Projects::CreateService.new(current_user, attrs).execute
if project.saved?
present project, with: Entities::Project,
user_can_admin_project: can?(current_user, :admin_project, project)
else
if project.errors[:limit_reached].present?
error!(project.errors[:limit_reached], 403)
end
render_validation_error!(project)
end
end
desc 'Create new project for a specified user. Only available to admin users.' do
success Entities::Project
end
params do
requires :name, type: String, desc: 'The name of the project'
requires :user_id, type: Integer, desc: 'The ID of a user'
optional :path, type: String, desc: 'The path of the repository'
optional :default_branch, type: String, desc: 'The default branch of the project'
use :optional_project_params
use :create_params
end
post "user/:user_id" do
authenticated_as_admin!
user = User.find_by(id: params.delete(:user_id))
not_found!('User') unless user
attrs = declared_params(include_missing: false)
attrs = translate_params_for_compatibility(attrs)
project = ::Projects::CreateService.new(user, attrs).execute
if project.saved?
present project, with: Entities::Project,
user_can_admin_project: can?(current_user, :admin_project, project)
else
render_validation_error!(project)
end
end
end
params do
requires :id, type: String, desc: 'The ID of a project'
end
resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
desc 'Get a single project' do
success Entities::ProjectWithAccess
end
params do
use :statistics_params
use :with_custom_attributes
end
get ":id" do
options = {
with: current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails,
current_user: current_user,
user_can_admin_project: can?(current_user, :admin_project, user_project),
statistics: params[:statistics]
}
project, options = with_custom_attributes(user_project, options)
present project, options
end
desc 'Fork new project for the current user or provided namespace.' do
success Entities::Project
end
params do
optional :namespace, type: String, desc: 'The ID or name of the namespace that the project will be forked into'
end
post ':id/fork' do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42284')
fork_params = declared_params(include_missing: false)
namespace_id = fork_params[:namespace]
if namespace_id.present?
fork_params[:namespace] = find_namespace(namespace_id)
unless fork_params[:namespace] && can?(current_user, :create_projects, fork_params[:namespace])
not_found!('Target Namespace')
end
end
forked_project = ::Projects::ForkService.new(user_project, current_user, fork_params).execute
if forked_project.errors.any?
conflict!(forked_project.errors.messages)
else
present forked_project, with: Entities::Project,
user_can_admin_project: can?(current_user, :admin_project, forked_project)
end
end
desc 'List forks of this project' do
success Entities::Project
end
params do
use :collection_params
use :with_custom_attributes
end
get ':id/forks' do
forks = ForkProjectsFinder.new(user_project, params: project_finder_params, current_user: current_user).execute
present_projects forks
end
desc 'Update an existing project' do
success Entities::Project
end
params do
optional :name, type: String, desc: 'The name of the project'
optional :default_branch, type: String, desc: 'The default branch of the project'
optional :path, type: String, desc: 'The path of the repository'
use :optional_project_params
at_least_one_of(*::API::Projects.update_params_at_least_one_of)
end
put ':id' do
authorize_admin_project
attrs = declared_params(include_missing: false)
authorize! :rename_project, user_project if attrs[:name].present?
authorize! :change_visibility_level, user_project if attrs[:visibility].present?
attrs = translate_params_for_compatibility(attrs)
verify_update_project_attrs!(user_project, attrs)
result = ::Projects::UpdateService.new(user_project, current_user, attrs).execute
if result[:status] == :success
present user_project, with: Entities::Project,
user_can_admin_project: can?(current_user, :admin_project, user_project)
else
render_validation_error!(user_project)
end
end
desc 'Archive a project' do
success Entities::Project
end
post ':id/archive' do
authorize!(:archive_project, user_project)
::Projects::UpdateService.new(user_project, current_user, archived: true).execute
present user_project, with: Entities::Project
end
desc 'Unarchive a project' do
success Entities::Project
end
post ':id/unarchive' do
authorize!(:archive_project, user_project)
::Projects::UpdateService.new(@project, current_user, archived: false).execute
present user_project, with: Entities::Project
end
desc 'Star a project' do
success Entities::Project
end
post ':id/star' do
if current_user.starred?(user_project)
not_modified!
else
current_user.toggle_star(user_project)
user_project.reload
present user_project, with: Entities::Project
end
end
desc 'Unstar a project' do
success Entities::Project
end
post ':id/unstar' do
if current_user.starred?(user_project)
current_user.toggle_star(user_project)
user_project.reload
present user_project, with: Entities::Project
else
not_modified!
end
end
desc 'Get languages in project repository'
get ':id/languages' do
user_project.repository.languages.map { |language| language.values_at(:label, :value) }.to_h
end
desc 'Remove a project'
delete ":id" do
authorize! :remove_project, user_project
destroy_conditionally!(user_project) do
::Projects::DestroyService.new(user_project, current_user, {}).async_execute
end
accepted!
end
desc 'Mark this project as forked from another'
params do
requires :forked_from_id, type: String, desc: 'The ID of the project it was forked from'
end
post ":id/fork/:forked_from_id" do
authorize! :admin_project, user_project
fork_from_project = find_project!(params[:forked_from_id])
not_found!("Source Project") unless fork_from_project
result = ::Projects::ForkService.new(fork_from_project, current_user).execute(user_project)
if result
present user_project.reload, with: Entities::Project
else
render_api_error!("Project already forked", 409) if user_project.forked?
end
end
desc 'Remove a forked_from relationship'
delete ":id/fork" do
authorize! :remove_fork_project, user_project
result = destroy_conditionally!(user_project) do
::Projects::UnlinkForkService.new(user_project, current_user).execute
end
result ? status(204) : not_modified!
end
desc 'Share the project with a group' do
success Entities::ProjectGroupLink
end
params do
requires :group_id, type: Integer, desc: 'The ID of a group'
requires :group_access, type: Integer, values: Gitlab::Access.values, desc: 'The group access level'
optional :expires_at, type: Date, desc: 'Share expiration date'
end
post ":id/share" do
authorize! :admin_project, user_project
group = Group.find_by_id(params[:group_id])
unless group && can?(current_user, :read_group, group)
not_found!('Group')
end
unless user_project.allowed_to_share_with_group?
break render_api_error!("The project sharing with group is disabled", 400)
end
link = user_project.project_group_links.new(declared_params(include_missing: false))
if link.save
present link, with: Entities::ProjectGroupLink
else
render_api_error!(link.errors.full_messages.first, 409)
end
end
params do
requires :group_id, type: Integer, desc: 'The ID of the group'
end
delete ":id/share/:group_id" do
authorize! :admin_project, user_project
link = user_project.project_group_links.find_by(group_id: params[:group_id])
not_found!('Group Link') unless link
destroy_conditionally!(link)
end
desc 'Upload a file'
params do
requires :file, type: File, desc: 'The file to be uploaded'
end
post ":id/uploads" do
UploadService.new(user_project, params[:file]).execute
end
desc 'Get the users list of a project' do
success Entities::UserBasic
end
params do
optional :search, type: String, desc: 'Return list of users matching the search criteria'
use :pagination
end
get ':id/users' do
users = DeclarativePolicy.subject_scope { user_project.team.users }
users = users.search(params[:search]) if params[:search].present?
present paginate(users), with: Entities::UserBasic
end
desc 'Start the housekeeping task for a project' do
detail 'This feature was introduced in GitLab 9.0.'
end
post ':id/housekeeping' do
authorize_admin_project
begin
::Projects::HousekeepingService.new(user_project).execute
rescue ::Projects::HousekeepingService::LeaseTaken => error
conflict!(error.message)
end
end
desc 'Transfer a project to a new namespace'
params do
requires :namespace, type: String, desc: 'The ID or path of the new namespace'
end
put ":id/transfer" do
authorize! :change_namespace, user_project
namespace = find_namespace!(params[:namespace])
result = ::Projects::TransferService.new(user_project, current_user).execute(namespace)
if result
present user_project, with: Entities::Project
else
render_api_error!("Failed to transfer project #{user_project.errors.messages}", 400)
end
end
end
end
end