Commit d4103419 authored by Peter Leitzen's avatar Peter Leitzen

Merge branch 'vij-raw-snippet-blobs' into 'master'

Add Snippets BlobController for raw data

See merge request gitlab-org/gitlab!33938
parents b39e3e74 666fb7bf
# frozen_string_literal: true
module Snippets::BlobsActions
extend ActiveSupport::Concern
include Gitlab::Utils::StrongMemoize
include ExtractsRef
include Snippets::SendBlob
included do
before_action :authorize_read_snippet!, only: [:raw]
before_action :ensure_repository
before_action :ensure_blob
end
def raw
send_snippet_blob(snippet, blob)
end
private
def repository_container
snippet
end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def blob
strong_memoize(:blob) do
assign_ref_vars
next unless @commit
@repo.blob_at(@commit.id, @path)
end
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
def ensure_blob
render_404 unless blob
end
def ensure_repository
unless snippet.repo_exists?
Gitlab::AppLogger.error(message: "Snippet raw blob attempt with no repo", snippet: snippet.id)
respond_422
end
end
def snippet_id
params[:snippet_id]
end
end
# frozen_string_literal: true
module Snippets::SendBlob
include SendsBlob
def send_snippet_blob(snippet, blob)
workhorse_set_content_type!
send_blob(
snippet.repository,
blob,
inline: content_disposition == 'inline',
allow_caching: snippet.public?
)
end
private
def content_disposition
@disposition ||= params[:inline] == 'false' ? 'attachment' : 'inline'
end
end
......@@ -2,11 +2,12 @@
module SnippetsActions
extend ActiveSupport::Concern
include SendsBlob
include RendersNotes
include RendersBlob
include PaginatedCollection
include Gitlab::NoteableMetadata
include Snippets::SendBlob
included do
skip_before_action :verify_authenticity_token,
......@@ -25,6 +26,10 @@ module SnippetsActions
render 'edit'
end
# This endpoint is being replaced by Snippets::BlobController#raw
# Support for old raw links will be maintainted via this action but
# it will only return the first blob found,
# see: https://gitlab.com/gitlab-org/gitlab/-/issues/217775
def raw
workhorse_set_content_type!
......@@ -39,12 +44,7 @@ module SnippetsActions
filename: Snippet.sanitized_file_name(blob.name)
)
else
send_blob(
snippet.repository,
blob,
inline: content_disposition == 'inline',
allow_caching: snippet.public?
)
send_snippet_blob(snippet, blob)
end
end
......@@ -106,10 +106,6 @@ module SnippetsActions
private
def content_disposition
@disposition ||= params[:inline] == 'false' ? 'attachment' : 'inline'
end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def blob
return unless snippet
......
# frozen_string_literal: true
class Projects::Snippets::BlobsController < Projects::Snippets::ApplicationController
include Snippets::BlobsActions
end
......@@ -15,7 +15,7 @@ class Projects::SnippetsController < Projects::Snippets::ApplicationController
before_action :authorize_admin_snippet!, only: [:destroy]
def index
@snippet_counts = Snippets::CountService
@snippet_counts = ::Snippets::CountService
.new(current_user, project: @project)
.execute
......@@ -35,7 +35,7 @@ class Projects::SnippetsController < Projects::Snippets::ApplicationController
def create
create_params = snippet_params.merge(spammable_params)
service_response = Snippets::CreateService.new(project, current_user, create_params).execute
service_response = ::Snippets::CreateService.new(project, current_user, create_params).execute
@snippet = service_response.payload[:snippet]
handle_repository_error(:new)
......
# frozen_string_literal: true
class Snippets::BlobsController < Snippets::ApplicationController
include Snippets::BlobsActions
skip_before_action :authenticate_user!, only: [:raw]
end
---
title: Add new raw snippet blob endpoint
merge_request: 33938
author:
type: added
......@@ -313,6 +313,12 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
get '/snippets/:snippet_id/raw/:ref/*path',
to: 'snippets/blobs#raw',
format: false,
as: :snippet_blob_raw,
constraints: { snippet_id: /\d+/ }
draw :issues
draw :merge_requests
draw :pipelines
......
......@@ -17,5 +17,14 @@ resources :snippets, concerns: :awardable do
end
end
# Use this /-/ scope for all new snippet routes.
scope path: '-' do
get '/snippets/:snippet_id/raw/:ref/*path',
to: 'snippets/blobs#raw',
as: :snippet_raw,
format: false,
constraints: { snippet_id: /\d+/ }
end
get '/s/:username', to: redirect('users/%{username}/snippets'),
constraints: { username: /[a-zA-Z.0-9_\-]+(?<!\.atom)/ }
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::Snippets::BlobsController do
using RSpec::Parameterized::TableSyntax
include SnippetHelpers
let_it_be(:author) { create(:user) }
let_it_be(:developer) { create(:user) }
let_it_be(:other_user) { create(:user) }
let(:visibility) { :public }
let(:project_visibility) { :public }
let(:project) { create(:project, project_visibility) }
let(:snippet) { create(:project_snippet, visibility, :repository, project: project, author: author) }
before do
project.add_maintainer(author)
project.add_developer(developer)
end
describe 'GET #raw' do
let(:filepath) { 'file1' }
let(:ref) { TestEnv::BRANCH_SHA['snippet/single-file'] }
let(:inline) { nil }
subject do
get(:raw,
params: {
namespace_id: project.namespace,
project_id: project,
snippet_id: snippet,
path: filepath,
ref: ref,
inline: inline
})
end
context 'with a snippet without a repository' do
let(:snippet) { create(:project_snippet, visibility, project: project, author: author) }
it_behaves_like 'raw snippet without repository', :not_found
end
where(:project_visibility_level, :snippet_visibility_level, :user, :status) do
:public | :public | :author | :ok
:public | :public | :developer | :ok
:public | :public | :other_user | :ok
:public | :public | nil | :ok
:public | :private | :author | :ok
:public | :private | :developer | :ok
:public | :private | :other_user | :not_found
:public | :private | nil | :not_found
:private | :public | :author | :ok
:private | :public | :developer | :ok
:private | :public | :other_user | :not_found
:private | :public | nil | :redirect
:private | :private | :author | :ok
:private | :private | :developer | :ok
:private | :private | :other_user | :not_found
:private | :private | nil | :redirect
end
with_them do
let(:visibility) { snippet_visibility_level }
let(:project_visibility) { project_visibility_level }
before do
sign_in_as(user)
subject
end
it 'responds with correct status' do
expect(response).to have_gitlab_http_status(status)
end
end
it_behaves_like 'raw snippet blob'
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Snippets::BlobsController do
using RSpec::Parameterized::TableSyntax
include SnippetHelpers
describe 'GET #raw' do
let_it_be(:author) { create(:user) }
let_it_be(:other_user) { create(:user) }
let(:visibility) { :public }
let(:snippet) { create(:personal_snippet, visibility, :repository, author: author) }
let(:filepath) { 'file1' }
let(:ref) { TestEnv::BRANCH_SHA['snippet/single-file'] }
let(:inline) { nil }
subject do
get(:raw,
params: {
snippet_id: snippet,
path: filepath,
ref: ref,
inline: inline
})
end
where(:snippet_visibility_level, :user, :status) do
:public | :author | :ok
:public | :other_user | :ok
:public | nil | :ok
:private | :author | :ok
:private | :other_user | :not_found
:private | nil | :redirect
end
with_them do
let(:visibility) { snippet_visibility_level }
before do
sign_in_as(user)
subject
end
it 'responds with correct status' do
expect(response).to have_gitlab_http_status(status)
end
end
it_behaves_like 'raw snippet blob'
context 'with a snippet without a repository' do
let(:snippet) { create(:personal_snippet, visibility, author: author) }
it_behaves_like 'raw snippet without repository', :redirect
end
end
end
......@@ -878,4 +878,12 @@ describe 'project routing' do
expect(get('/gitlab/gitlabhq/-/design_management/designs/1/c6f00aa50b80887ada30a6fe517670be9f8f9ece/resized_image/small')).to route_to('application#route_not_found', unmatched_route: 'gitlab/gitlabhq/-/design_management/designs/1/c6f00aa50b80887ada30a6fe517670be9f8f9ece/resized_image/small')
end
end
describe Projects::Snippets::BlobsController, "routing" do
it "to #raw" do
expect(get('/gitlab/gitlabhq/-/snippets/1/raw/master/lib/version.rb'))
.to route_to('projects/snippets/blobs#raw', namespace_id: 'gitlab',
project_id: 'gitlabhq', snippet_id: '1', ref: 'master', path: 'lib/version.rb')
end
end
end
......@@ -368,3 +368,10 @@ describe AutocompleteController, 'routing' do
expect(get("/autocomplete/award_emojis")).to route_to('autocomplete#award_emojis')
end
end
describe Snippets::BlobsController, "routing" do
it "to #raw" do
expect(get('/-/snippets/1/raw/master/lib/version.rb'))
.to route_to('snippets/blobs#raw', snippet_id: '1', ref: 'master', path: 'lib/version.rb')
end
end
# frozen_string_literal: true
module SnippetHelpers
def sign_in_as(user)
sign_in(public_send(user)) if user
end
end
# frozen_string_literal: true
RSpec.shared_examples 'raw snippet blob' do
context 'with valid params' do
before do
subject
end
it 'delivers file with correct Workhorse headers' do
expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8')
expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to eq 'true'
expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:')
end
it 'responds with status 200' do
expect(response).to have_gitlab_http_status(:ok)
end
end
context 'with invalid file path' do
let(:filepath) { 'doesnotexist' }
it_behaves_like 'returning response status', :not_found
end
context 'with invalid ref' do
let(:ref) { 'doesnotexist' }
it_behaves_like 'returning response status', :not_found
end
it_behaves_like 'content disposition headers'
end
RSpec.shared_examples 'raw snippet without repository' do |unauthorized_status|
context 'when authorized' do
it 'returns a 422' do
subject
expect(response).to have_gitlab_http_status(:unprocessable_entity)
end
end
context 'when unauthorized' do
let(:visibility) { :private }
it_behaves_like 'returning response status', unauthorized_status
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