gitlab-org--gitlab-foss/lib/api/projects.rb
Sean McGivern 97424ea544 Restrict starred projects to viewable ones
`User#starred_projects` doesn't perform any visibility checks. This has
a couple of problems:

1. It assumes a user can always view all of their starred projects in
   perpetuity (project not changed to private, access revoked, etc.).
2. It assumes that we'll only ever allow a user to star a project they
   can view. This is currently the case, but bugs happen.

Add `User#viewable_starred_projects` to filter the starred projects by
those the user either has explicit access to, or are public or
internal. Then use that in all places where we list the user's starred
projects.
2016-05-10 18:13:52 +01:00

429 lines
15 KiB
Ruby

module API
# Projects API
class Projects < Grape::API
before { authenticate! }
resource :projects, requirements: { id: /[^\/]+/ } do
helpers do
def map_public_to_visibility_level(attrs)
publik = attrs.delete(:public)
if publik.present? && !attrs[:visibility_level].present?
publik = parse_boolean(publik)
# Since setting the public attribute to private could mean either
# private or internal, use the more conservative option, private.
attrs[:visibility_level] = (publik == true) ? Gitlab::VisibilityLevel::PUBLIC : Gitlab::VisibilityLevel::PRIVATE
end
attrs
end
end
# Get a projects list for authenticated user
#
# Example Request:
# GET /projects
get do
@projects = current_user.authorized_projects
@projects = filter_projects(@projects)
@projects = paginate @projects
present @projects, with: Entities::ProjectWithAccess, user: current_user
end
# Get an owned projects list for authenticated user
#
# Example Request:
# GET /projects/owned
get '/owned' do
@projects = current_user.owned_projects
@projects = filter_projects(@projects)
@projects = paginate @projects
present @projects, with: Entities::ProjectWithAccess, user: current_user
end
# Gets starred project for the authenticated user
#
# Example Request:
# GET /projects/starred
get '/starred' do
@projects = current_user.viewable_starred_projects
@projects = filter_projects(@projects)
@projects = paginate @projects
present @projects, with: Entities::Project
end
# Get all projects for admin user
#
# Example Request:
# GET /projects/all
get '/all' do
authenticated_as_admin!
@projects = Project.all
@projects = filter_projects(@projects)
@projects = paginate @projects
present @projects, with: Entities::ProjectWithAccess, user: current_user
end
# Get a single project
#
# Parameters:
# id (required) - The ID of a project
# Example Request:
# GET /projects/:id
get ":id" do
present user_project, with: Entities::ProjectWithAccess, user: current_user,
user_can_admin_project: can?(current_user, :admin_project, user_project)
end
# Get events for a single project
#
# Parameters:
# id (required) - The ID of a project
# Example Request:
# GET /projects/:id/events
get ":id/events" do
events = paginate user_project.events.recent
present events, with: Entities::Event
end
# Create new project
#
# Parameters:
# name (required) - name for new project
# description (optional) - short project description
# issues_enabled (optional)
# merge_requests_enabled (optional)
# builds_enabled (optional)
# wiki_enabled (optional)
# snippets_enabled (optional)
# shared_runners_enabled (optional)
# namespace_id (optional) - defaults to user namespace
# public (optional) - if true same as setting visibility_level = 20
# visibility_level (optional) - 0 by default
# import_url (optional)
# public_builds (optional)
# Example Request
# POST /projects
post do
required_attributes! [:name]
attrs = attributes_for_keys [:name,
:path,
:description,
:issues_enabled,
:merge_requests_enabled,
:builds_enabled,
:wiki_enabled,
:snippets_enabled,
:shared_runners_enabled,
:namespace_id,
:public,
:visibility_level,
:import_url,
:public_builds]
attrs = map_public_to_visibility_level(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
# Create new project for a specified user. Only available to admin users.
#
# Parameters:
# user_id (required) - The ID of a user
# name (required) - name for new project
# description (optional) - short project description
# default_branch (optional) - 'master' by default
# issues_enabled (optional)
# merge_requests_enabled (optional)
# builds_enabled (optional)
# wiki_enabled (optional)
# snippets_enabled (optional)
# shared_runners_enabled (optional)
# public (optional) - if true same as setting visibility_level = 20
# visibility_level (optional)
# import_url (optional)
# public_builds (optional)
# Example Request
# POST /projects/user/:user_id
post "user/:user_id" do
authenticated_as_admin!
user = User.find(params[:user_id])
attrs = attributes_for_keys [:name,
:description,
:default_branch,
:issues_enabled,
:merge_requests_enabled,
:builds_enabled,
:wiki_enabled,
:snippets_enabled,
:shared_runners_enabled,
:public,
:visibility_level,
:import_url,
:public_builds]
attrs = map_public_to_visibility_level(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
# Fork new project for the current user.
#
# Parameters:
# id (required) - The ID of a project
# Example Request
# POST /projects/fork/:id
post 'fork/:id' do
@forked_project =
::Projects::ForkService.new(user_project,
current_user).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
# Update an existing project
#
# Parameters:
# id (required) - the id of a project
# name (optional) - name of a project
# path (optional) - path of a project
# description (optional) - short project description
# issues_enabled (optional)
# merge_requests_enabled (optional)
# builds_enabled (optional)
# wiki_enabled (optional)
# snippets_enabled (optional)
# shared_runners_enabled (optional)
# public (optional) - if true same as setting visibility_level = 20
# visibility_level (optional) - visibility level of a project
# public_builds (optional)
# Example Request
# PUT /projects/:id
put ':id' do
attrs = attributes_for_keys [:name,
:path,
:description,
:default_branch,
:issues_enabled,
:merge_requests_enabled,
:builds_enabled,
:wiki_enabled,
:snippets_enabled,
:shared_runners_enabled,
:public,
:visibility_level,
:public_builds]
attrs = map_public_to_visibility_level(attrs)
authorize_admin_project
authorize! :rename_project, user_project if attrs[:name].present?
if attrs[:visibility_level].present?
authorize! :change_visibility_level, user_project
end
::Projects::UpdateService.new(user_project,
current_user, attrs).execute
if user_project.errors.any?
render_validation_error!(user_project)
else
present user_project, with: Entities::Project,
user_can_admin_project: can?(current_user, :admin_project, user_project)
end
end
# Archive project
#
# Parameters:
# id (required) - The ID of a project
# Example Request:
# PUT /projects/:id/archive
post ':id/archive' do
authorize!(:archive_project, user_project)
user_project.archive!
present user_project, with: Entities::Project
end
# Unarchive project
#
# Parameters:
# id (required) - The ID of a project
# Example Request:
# PUT /projects/:id/unarchive
post ':id/unarchive' do
authorize!(:archive_project, user_project)
user_project.unarchive!
present user_project, with: Entities::Project
end
# Star project
#
# Parameters:
# id (required) - The ID of a project
# Example Request:
# POST /projects/:id/star
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
# Unstar project
#
# Parameters:
# id (required) - The ID of a project
# Example Request:
# DELETE /projects/:id/star
delete ':id/star' 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
# Remove project
#
# Parameters:
# id (required) - The ID of a project
# Example Request:
# DELETE /projects/:id
delete ":id" do
authorize! :remove_project, user_project
::Projects::DestroyService.new(user_project, current_user, {}).pending_delete!
end
# Mark this project as forked from another
#
# Parameters:
# id: (required) - The ID of the project being marked as a fork
# forked_from_id: (required) - The ID of the project it was forked from
# Example Request:
# POST /projects/:id/fork/:forked_from_id
post ":id/fork/:forked_from_id" do
authenticated_as_admin!
forked_from_project = find_project(params[:forked_from_id])
unless forked_from_project.nil?
if user_project.forked_from_project.nil?
user_project.create_forked_project_link(forked_to_project_id: user_project.id, forked_from_project_id: forked_from_project.id)
else
render_api_error!("Project already forked", 409)
end
else
not_found!("Source Project")
end
end
# Remove a forked_from relationship
#
# Parameters:
# id: (required) - The ID of the project being marked as a fork
# Example Request:
# DELETE /projects/:id/fork
delete ":id/fork" do
authorize! :remove_fork_project, user_project
if user_project.forked?
user_project.forked_project_link.destroy
end
end
# Share project with group
#
# Parameters:
# id (required) - The ID of a project
# group_id (required) - The ID of a group
# group_access (required) - Level of permissions for sharing
#
# Example Request:
# POST /projects/:id/share
post ":id/share" do
authorize! :admin_project, user_project
required_attributes! [:group_id, :group_access]
unless user_project.allowed_to_share_with_group?
return render_api_error!("The project sharing with group is disabled", 400)
end
link = user_project.project_group_links.new
link.group_id = params[:group_id]
link.group_access = params[:group_access]
if link.save
present link, with: Entities::ProjectGroupLink
else
render_api_error!(link.errors.full_messages.first, 409)
end
end
# Upload a file
#
# Parameters:
# id: (required) - The ID of the project
# file: (required) - The file to be uploaded
post ":id/uploads" do
::Projects::UploadService.new(user_project, params[:file]).execute
end
# search for projects current_user has access to
#
# Parameters:
# query (required) - A string contained in the project name
# per_page (optional) - number of projects to return per page
# page (optional) - the page to retrieve
# Example Request:
# GET /projects/search/:query
get "/search/:query" do
ids = current_user.authorized_projects.map(&:id)
visibility_levels = [ Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PUBLIC ]
projects = Project.where("(id in (?) OR visibility_level in (?)) AND (name LIKE (?))", ids, visibility_levels, "%#{params[:query]}%")
sort = params[:sort] == 'desc' ? 'desc' : 'asc'
projects = case params["order_by"]
when 'id' then projects.order("id #{sort}")
when 'name' then projects.order("name #{sort}")
when 'created_at' then projects.order("created_at #{sort}")
when 'last_activity_at' then projects.order("last_activity_at #{sort}")
else projects
end
present paginate(projects), with: Entities::Project
end
# Get a users list
#
# Example Request:
# GET /users
get ':id/users' do
@users = User.where(id: user_project.team.users.map(&:id))
@users = @users.search(params[:search]) if params[:search].present?
@users = paginate @users
present @users, with: Entities::UserBasic
end
end
end
end