Commit 6f5bf6b8 authored by Sean McGivern's avatar Sean McGivern

Merge branch 'vij-public-snippet-access' into 'master'

Allow unauthenticated public snippet API calls

See merge request gitlab-org/gitlab!44135
parents eb18a2c9 d982bbf4
---
title: Allow unauthenticated users access to public Personal Snippets via the REST API
merge_request: 44135
author:
type: fixed
...@@ -5,8 +5,6 @@ module API ...@@ -5,8 +5,6 @@ module API
class Snippets < Grape::API::Instance class Snippets < Grape::API::Instance
include PaginationParams include PaginationParams
before { authenticate! }
resource :snippets do resource :snippets do
helpers Helpers::SnippetsHelpers helpers Helpers::SnippetsHelpers
helpers do helpers do
...@@ -23,7 +21,7 @@ module API ...@@ -23,7 +21,7 @@ module API
end end
end end
desc 'Get a snippets list for authenticated user' do desc 'Get a snippets list for an authenticated user' do
detail 'This feature was introduced in GitLab 8.15.' detail 'This feature was introduced in GitLab 8.15.'
success Entities::Snippet success Entities::Snippet
end end
...@@ -31,6 +29,8 @@ module API ...@@ -31,6 +29,8 @@ module API
use :pagination use :pagination
end end
get do get do
authenticate!
present paginate(snippets_for_current_user), with: Entities::Snippet, current_user: current_user present paginate(snippets_for_current_user), with: Entities::Snippet, current_user: current_user
end end
...@@ -42,6 +42,8 @@ module API ...@@ -42,6 +42,8 @@ module API
use :pagination use :pagination
end end
get 'public' do get 'public' do
authenticate!
present paginate(public_snippets), with: Entities::PersonalSnippet, current_user: current_user present paginate(public_snippets), with: Entities::PersonalSnippet, current_user: current_user
end end
...@@ -74,6 +76,8 @@ module API ...@@ -74,6 +76,8 @@ module API
use :create_file_params use :create_file_params
end end
post do post do
authenticate!
authorize! :create_snippet authorize! :create_snippet
attrs = process_create_params(declared_params(include_missing: false)) attrs = process_create_params(declared_params(include_missing: false))
...@@ -109,6 +113,8 @@ module API ...@@ -109,6 +113,8 @@ module API
use :minimum_update_params use :minimum_update_params
end end
put ':id' do put ':id' do
authenticate!
snippet = snippets_for_current_user.find_by_id(params.delete(:id)) snippet = snippets_for_current_user.find_by_id(params.delete(:id))
break not_found!('Snippet') unless snippet break not_found!('Snippet') unless snippet
...@@ -139,6 +145,8 @@ module API ...@@ -139,6 +145,8 @@ module API
requires :id, type: Integer, desc: 'The ID of a snippet' requires :id, type: Integer, desc: 'The ID of a snippet'
end end
delete ':id' do delete ':id' do
authenticate!
snippet = snippets_for_current_user.find_by_id(params.delete(:id)) snippet = snippets_for_current_user.find_by_id(params.delete(:id))
break not_found!('Snippet') unless snippet break not_found!('Snippet') unless snippet
......
...@@ -432,6 +432,14 @@ RSpec.describe API::ProjectSnippets do ...@@ -432,6 +432,14 @@ RSpec.describe API::ProjectSnippets do
describe 'GET /projects/:project_id/snippets/:id/files/:ref/:file_path/raw' do describe 'GET /projects/:project_id/snippets/:id/files/:ref/:file_path/raw' do
let_it_be(:snippet) { create(:project_snippet, :repository, author: admin, project: project) } let_it_be(:snippet) { create(:project_snippet, :repository, author: admin, project: project) }
context 'with no user' do
it 'requires authentication' do
get api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/files/master/%2Egitattributes/raw", nil)
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
it_behaves_like 'raw snippet files' do it_behaves_like 'raw snippet files' do
let(:api_path) { "/projects/#{snippet.project.id}/snippets/#{snippet_id}/files/#{ref}/#{file_path}/raw" } let(:api_path) { "/projects/#{snippet.project.id}/snippets/#{snippet_id}/files/#{ref}/#{file_path}/raw" }
end end
......
...@@ -5,14 +5,15 @@ require 'spec_helper' ...@@ -5,14 +5,15 @@ require 'spec_helper'
RSpec.describe API::Snippets do RSpec.describe API::Snippets do
include SnippetHelpers include SnippetHelpers
let_it_be(:user) { create(:user) } let_it_be(:admin) { create(:user, :admin) }
let_it_be(:user) { create(:user) }
let_it_be(:other_user) { create(:user) }
let_it_be(:public_snippet) { create(:personal_snippet, :repository, :public, author: user) }
let_it_be(:private_snippet) { create(:personal_snippet, :repository, :private, author: user) }
let_it_be(:internal_snippet) { create(:personal_snippet, :repository, :internal, author: user) }
describe 'GET /snippets/' do describe 'GET /snippets/' do
it 'returns snippets available' do it 'returns snippets available for user' do
public_snippet = create(:personal_snippet, :repository, :public, author: user)
private_snippet = create(:personal_snippet, :repository, :private, author: user)
internal_snippet = create(:personal_snippet, :repository, :internal, author: user)
get api("/snippets/", user) get api("/snippets/", user)
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
...@@ -29,9 +30,7 @@ RSpec.describe API::Snippets do ...@@ -29,9 +30,7 @@ RSpec.describe API::Snippets do
end end
it 'hides private snippets from regular user' do it 'hides private snippets from regular user' do
create(:personal_snippet, :private) get api("/snippets/", other_user)
get api("/snippets/", user)
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers expect(response).to include_pagination_headers
...@@ -39,9 +38,7 @@ RSpec.describe API::Snippets do ...@@ -39,9 +38,7 @@ RSpec.describe API::Snippets do
expect(json_response.size).to eq(0) expect(json_response.size).to eq(0)
end end
it 'returns 404 for non-authenticated' do it 'returns 401 for non-authenticated' do
create(:personal_snippet, :internal)
get api("/snippets/") get api("/snippets/")
expect(response).to have_gitlab_http_status(:unauthorized) expect(response).to have_gitlab_http_status(:unauthorized)
...@@ -62,10 +59,6 @@ RSpec.describe API::Snippets do ...@@ -62,10 +59,6 @@ RSpec.describe API::Snippets do
end end
describe 'GET /snippets/public' do describe 'GET /snippets/public' do
let_it_be(:other_user) { create(:user) }
let_it_be(:public_snippet) { create(:personal_snippet, :repository, :public, author: user) }
let_it_be(:private_snippet) { create(:personal_snippet, :repository, :private, author: user) }
let_it_be(:internal_snippet) { create(:personal_snippet, :repository, :internal, author: user) }
let_it_be(:public_snippet_other) { create(:personal_snippet, :repository, :public, author: other_user) } let_it_be(:public_snippet_other) { create(:personal_snippet, :repository, :public, author: other_user) }
let_it_be(:private_snippet_other) { create(:personal_snippet, :repository, :private, author: other_user) } let_it_be(:private_snippet_other) { create(:personal_snippet, :repository, :private, author: other_user) }
let_it_be(:internal_snippet_other) { create(:personal_snippet, :repository, :internal, author: other_user) } let_it_be(:internal_snippet_other) { create(:personal_snippet, :repository, :internal, author: other_user) }
...@@ -73,8 +66,10 @@ RSpec.describe API::Snippets do ...@@ -73,8 +66,10 @@ RSpec.describe API::Snippets do
let_it_be(:private_snippet_project) { create(:project_snippet, :repository, :private, author: user) } let_it_be(:private_snippet_project) { create(:project_snippet, :repository, :private, author: user) }
let_it_be(:internal_snippet_project) { create(:project_snippet, :repository, :internal, author: user) } let_it_be(:internal_snippet_project) { create(:project_snippet, :repository, :internal, author: user) }
it 'returns all snippets with public visibility from all users' do let(:path) { "/snippets/public" }
get api("/snippets/public", user)
it 'returns only public snippets from all users when authenticated' do
get api(path, user)
aggregate_failures do aggregate_failures do
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
...@@ -90,20 +85,23 @@ RSpec.describe API::Snippets do ...@@ -90,20 +85,23 @@ RSpec.describe API::Snippets do
expect(json_response[1]['files'].first).to eq snippet_blob_file(public_snippet.blobs.first) expect(json_response[1]['files'].first).to eq snippet_blob_file(public_snippet.blobs.first)
end end
end end
end
describe 'GET /snippets/:id/raw' do
let_it_be(:author) { create(:user) }
let_it_be(:snippet) { create(:personal_snippet, :repository, :private, author: author) }
it 'requires authentication' do it 'requires authentication' do
get api("/snippets/#{snippet.id}", nil) get api(path, nil)
expect(response).to have_gitlab_http_status(:unauthorized) expect(response).to have_gitlab_http_status(:unauthorized)
end end
end
describe 'GET /snippets/:id/raw' do
let(:snippet) { private_snippet }
it_behaves_like 'snippet access with different users' do
let(:path) { "/snippets/#{snippet.id}/raw" }
end
it 'returns raw text' do it 'returns raw text' do
get api("/snippets/#{snippet.id}/raw", author) get api("/snippets/#{snippet.id}/raw", user)
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(response.media_type).to eq 'text/plain' expect(response.media_type).to eq 'text/plain'
...@@ -113,28 +111,14 @@ RSpec.describe API::Snippets do ...@@ -113,28 +111,14 @@ RSpec.describe API::Snippets do
it 'returns 404 for invalid snippet id' do it 'returns 404 for invalid snippet id' do
snippet.destroy! snippet.destroy!
get api("/snippets/#{snippet.id}/raw", author)
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['message']).to eq('404 Snippet Not Found')
end
it 'hides private snippets from ordinary users' do
get api("/snippets/#{snippet.id}/raw", user) get api("/snippets/#{snippet.id}/raw", user)
expect(response).to have_gitlab_http_status(:not_found) expect(response).to have_gitlab_http_status(:not_found)
end expect(json_response['message']).to eq('404 Snippet Not Found')
it 'shows internal snippets to ordinary users' do
internal_snippet = create(:personal_snippet, :internal, author: author)
get api("/snippets/#{internal_snippet.id}/raw", user)
expect(response).to have_gitlab_http_status(:ok)
end end
it_behaves_like 'snippet blob content' do it_behaves_like 'snippet blob content' do
let_it_be(:snippet_with_empty_repo) { create(:personal_snippet, :empty_repo, :private, author: author) } let_it_be(:snippet_with_empty_repo) { create(:personal_snippet, :empty_repo, :private, author: user) }
subject { get api("/snippets/#{snippet.id}/raw", snippet.author) } subject { get api("/snippets/#{snippet.id}/raw", snippet.author) }
end end
...@@ -149,33 +133,11 @@ RSpec.describe API::Snippets do ...@@ -149,33 +133,11 @@ RSpec.describe API::Snippets do
end end
describe 'GET /snippets/:id' do describe 'GET /snippets/:id' do
let_it_be(:admin) { create(:user, :admin) } let(:snippet_id) { private_snippet.id }
let_it_be(:author) { create(:user) }
let_it_be(:private_snippet) { create(:personal_snippet, :repository, :private, author: author) }
let_it_be(:internal_snippet) { create(:personal_snippet, :repository, :internal, author: author) }
let(:snippet) { private_snippet }
subject { get api("/snippets/#{snippet.id}", user) }
it 'hides private snippets from an ordinary user' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
context 'without a user' do subject { get api("/snippets/#{snippet_id}", user) }
let(:user) { nil }
it 'requires authentication' do
subject
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
context 'with the author' do context 'with the author' do
let(:user) { author }
it 'returns snippet json' do it 'returns snippet json' do
subject subject
...@@ -191,18 +153,10 @@ RSpec.describe API::Snippets do ...@@ -191,18 +153,10 @@ RSpec.describe API::Snippets do
end end
end end
context 'with an admin' do context 'with a non-existent snippet ID' do
let(:user) { admin } let(:snippet_id) { 0 }
it 'shows private snippets to an admin' do
subject
expect(response).to have_gitlab_http_status(:ok)
end
it 'returns 404 for invalid snippet id' do
private_snippet.destroy!
it 'returns 404' do
subject subject
expect(response).to have_gitlab_http_status(:not_found) expect(response).to have_gitlab_http_status(:not_found)
...@@ -210,19 +164,11 @@ RSpec.describe API::Snippets do ...@@ -210,19 +164,11 @@ RSpec.describe API::Snippets do
end end
end end
context 'with an internal snippet' do it_behaves_like 'snippet access with different users' do
let(:snippet) { internal_snippet } let(:path) { "/snippets/#{snippet.id}" }
it 'shows internal snippets to an ordinary user' do
subject
expect(response).to have_gitlab_http_status(:ok)
end
end end
it_behaves_like 'snippet_multiple_files feature disabled' do it_behaves_like 'snippet_multiple_files feature disabled'
let(:user) { author }
end
end end
describe 'POST /snippets/' do describe 'POST /snippets/' do
......
...@@ -8,7 +8,7 @@ module SnippetHelpers ...@@ -8,7 +8,7 @@ module SnippetHelpers
def snippet_blob_file(blob) def snippet_blob_file(blob)
{ {
"path" => blob.path, "path" => blob.path,
"raw_url" => gitlab_raw_snippet_blob_url(blob.container, blob.path) "raw_url" => gitlab_raw_snippet_blob_url(blob.container, blob.path, host: 'localhost')
} }
end end
end end
...@@ -7,14 +7,6 @@ RSpec.shared_examples 'raw snippet files' do ...@@ -7,14 +7,6 @@ RSpec.shared_examples 'raw snippet files' do
let(:file_path) { '%2Egitattributes' } let(:file_path) { '%2Egitattributes' }
let(:ref) { 'master' } let(:ref) { 'master' }
context 'with no user' do
it 'requires authentication' do
get api(api_path)
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
shared_examples 'not found' do shared_examples 'not found' do
it 'returns 404' do it 'returns 404' do
get api(api_path, user) get api(api_path, user)
...@@ -216,3 +208,58 @@ RSpec.shared_examples 'invalid snippet updates' do ...@@ -216,3 +208,58 @@ RSpec.shared_examples 'invalid snippet updates' do
expect(json_response['error']).to eq 'title is empty' expect(json_response['error']).to eq 'title is empty'
end end
end end
RSpec.shared_examples 'snippet access with different users' do
using RSpec::Parameterized::TableSyntax
where(:requester, :visibility, :status) do
:admin | :public | :ok
:admin | :private | :ok
:admin | :internal | :ok
:author | :public | :ok
:author | :private | :ok
:author | :internal | :ok
:other | :public | :ok
:other | :private | :not_found
:other | :internal | :ok
nil | :public | :ok
nil | :private | :not_found
nil | :internal | :not_found
end
with_them do
let(:snippet) { snippet_for(visibility) }
it 'returns the correct response' do
request_user = user_for(requester)
get api(path, request_user)
expect(response).to have_gitlab_http_status(status)
end
end
def user_for(user_type)
case user_type
when :author
user
when :other
other_user
when :admin
admin
else
nil
end
end
def snippet_for(snippet_type)
case snippet_type
when :private
private_snippet
when :internal
internal_snippet
when :public
public_snippet
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