Commit 71ba28e4 authored by Douwe Maan's avatar Douwe Maan

Merge branch '4269-public-api' into 'master'

Allow public access to some Project API endpoints

## What does this MR do?

This opens up a few endpoints in the Project API:

- `GET /projects/visible` (returns public projects only)
- `GET /projects/search/:query` (returns results only for public projects)
- `GET /projects/:id` (only if the project is public)
- `GET /projects/:id/events` (only if the project is public)
- `GET /projects/:id/users` (only if the project is public)

## Are there points in the code the reviewer needs to double check?

I've chosen to explicitly add `authenticate!` to GET methods that still need a current user.

## Does this MR meet the acceptance criteria?

- [x] [Changelog entry](https://docs.gitlab.com/ce/development/changelog.html) added
- [ ] [Documentation created/updated](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/development/doc_styleguide.md)
- [x] API support added
- Tests
  - [x] Added for this feature/bug
  - [ ] All builds are passing

Part of #4269

See merge request !7843
parents 99453027 d7572472
---
title: Allow public access to some Project API endpoints
merge_request: 7843
author:
...@@ -141,6 +141,10 @@ module API ...@@ -141,6 +141,10 @@ module API
unauthorized! unless current_user unauthorized! unless current_user
end end
def authenticate_non_get!
authenticate! unless %w[GET HEAD].include?(route.route_method)
end
def authenticate_by_gitlab_shell_token! def authenticate_by_gitlab_shell_token!
input = params['secret_token'].try(:chomp) input = params['secret_token'].try(:chomp)
unless Devise.secure_compare(secret_token, input) unless Devise.secure_compare(secret_token, input)
...@@ -149,6 +153,7 @@ module API ...@@ -149,6 +153,7 @@ module API
end end
def authenticated_as_admin! def authenticated_as_admin!
authenticate!
forbidden! unless current_user.is_admin? forbidden! unless current_user.is_admin?
end end
......
...@@ -3,7 +3,7 @@ module API ...@@ -3,7 +3,7 @@ module API
class Projects < Grape::API class Projects < Grape::API
include PaginationParams include PaginationParams
before { authenticate! } before { authenticate_non_get! }
helpers do helpers do
params :optional_params do params :optional_params do
...@@ -61,7 +61,7 @@ module API ...@@ -61,7 +61,7 @@ module API
end end
end end
desc 'Get a projects list for authenticated user' do desc 'Get a list of visible projects for authenticated user' do
success Entities::BasicProjectDetails success Entities::BasicProjectDetails
end end
params do params do
...@@ -70,15 +70,15 @@ module API ...@@ -70,15 +70,15 @@ module API
use :filter_params use :filter_params
use :pagination use :pagination
end end
get do get '/visible' do
projects = current_user.authorized_projects projects = ProjectsFinder.new.execute(current_user)
projects = filter_projects(projects) projects = filter_projects(projects)
entity = params[:simple] ? Entities::BasicProjectDetails : Entities::ProjectWithAccess entity = params[:simple] || !current_user ? Entities::BasicProjectDetails : Entities::ProjectWithAccess
present paginate(projects), with: entity, user: current_user present paginate(projects), with: entity, user: current_user
end end
desc 'Get a list of visible projects for authenticated user' do desc 'Get a projects list for authenticated user' do
success Entities::BasicProjectDetails success Entities::BasicProjectDetails
end end
params do params do
...@@ -87,8 +87,10 @@ module API ...@@ -87,8 +87,10 @@ module API
use :filter_params use :filter_params
use :pagination use :pagination
end end
get '/visible' do get do
projects = ProjectsFinder.new.execute(current_user) authenticate!
projects = current_user.authorized_projects
projects = filter_projects(projects) projects = filter_projects(projects)
entity = params[:simple] ? Entities::BasicProjectDetails : Entities::ProjectWithAccess entity = params[:simple] ? Entities::BasicProjectDetails : Entities::ProjectWithAccess
...@@ -103,6 +105,8 @@ module API ...@@ -103,6 +105,8 @@ module API
use :pagination use :pagination
end end
get '/owned' do get '/owned' do
authenticate!
projects = current_user.owned_projects projects = current_user.owned_projects
projects = filter_projects(projects) projects = filter_projects(projects)
...@@ -117,6 +121,8 @@ module API ...@@ -117,6 +121,8 @@ module API
use :pagination use :pagination
end end
get '/starred' do get '/starred' do
authenticate!
projects = current_user.viewable_starred_projects projects = current_user.viewable_starred_projects
projects = filter_projects(projects) projects = filter_projects(projects)
...@@ -132,6 +138,7 @@ module API ...@@ -132,6 +138,7 @@ module API
end end
get '/all' do get '/all' do
authenticated_as_admin! authenticated_as_admin!
projects = Project.all projects = Project.all
projects = filter_projects(projects) projects = filter_projects(projects)
...@@ -213,7 +220,8 @@ module API ...@@ -213,7 +220,8 @@ module API
success Entities::ProjectWithAccess success Entities::ProjectWithAccess
end end
get ":id" do get ":id" do
present user_project, with: Entities::ProjectWithAccess, user: current_user, entity = current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails
present user_project, with: entity, user: current_user,
user_can_admin_project: can?(current_user, :admin_project, user_project) user_can_admin_project: can?(current_user, :admin_project, user_project)
end end
...@@ -433,7 +441,7 @@ module API ...@@ -433,7 +441,7 @@ module API
use :pagination use :pagination
end end
get ':id/users' do get ':id/users' do
users = User.where(id: user_project.team.users.map(&:id)) users = user_project.team.users
users = users.search(params[:search]) if params[:search].present? users = users.search(params[:search]) if params[:search].present?
present paginate(users), with: Entities::UserBasic present paginate(users), with: Entities::UserBasic
......
...@@ -47,7 +47,7 @@ describe API::Helpers, api: true do ...@@ -47,7 +47,7 @@ describe API::Helpers, api: true do
end end
def error!(message, status) def error!(message, status)
raise Exception raise Exception.new("#{status} - #{message}")
end end
describe ".current_user" do describe ".current_user" do
...@@ -290,4 +290,56 @@ describe API::Helpers, api: true do ...@@ -290,4 +290,56 @@ describe API::Helpers, api: true do
handle_api_exception(exception) handle_api_exception(exception)
end end
end end
describe '.authenticate_non_get!' do
%w[HEAD GET].each do |method_name|
context "method is #{method_name}" do
before do
expect_any_instance_of(self.class).to receive(:route).and_return(double(route_method: method_name))
end
it 'does not raise an error' do
expect_any_instance_of(self.class).not_to receive(:authenticate!)
expect { authenticate_non_get! }.not_to raise_error
end
end
end
%w[POST PUT PATCH DELETE].each do |method_name|
context "method is #{method_name}" do
before do
expect_any_instance_of(self.class).to receive(:route).and_return(double(route_method: method_name))
end
it 'calls authenticate!' do
expect_any_instance_of(self.class).to receive(:authenticate!)
authenticate_non_get!
end
end
end
end
describe '.authenticate!' do
context 'current_user is nil' do
before do
expect_any_instance_of(self.class).to receive(:current_user).and_return(nil)
end
it 'returns a 401 response' do
expect { authenticate! }.to raise_error '401 - {"message"=>"401 Unauthorized"}'
end
end
context 'current_user is present' do
before do
expect_any_instance_of(self.class).to receive(:current_user).and_return(true)
end
it 'does not raise an error' do
expect { authenticate! }.not_to raise_error
end
end
end
end end
This diff is collapsed.
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