Commit 656d4ebf authored by Jan Provaznik's avatar Jan Provaznik

Add workhorse authorize method for project/group uploads

This method can be used by workhorse to get presigned
URLs used for direct upload of files.
parent 937c1b5b
...@@ -39,6 +39,16 @@ module UploadsActions ...@@ -39,6 +39,16 @@ module UploadsActions
send_upload(uploader, attachment: uploader.filename, disposition: disposition) send_upload(uploader, attachment: uploader.filename, disposition: disposition)
end end
def authorize
set_workhorse_internal_api_content_type
authorized = uploader_class.workhorse_authorize(
has_length: false,
maximum_size: Gitlab::CurrentSettings.max_attachment_size.megabytes.to_i)
render json: authorized
end
private private
def uploader_class def uploader_class
......
class Groups::UploadsController < Groups::ApplicationController class Groups::UploadsController < Groups::ApplicationController
include UploadsActions include UploadsActions
include WorkhorseRequest
skip_before_action :group, if: -> { action_name == 'show' && image_or_video? } skip_before_action :group, if: -> { action_name == 'show' && image_or_video? }
before_action :authorize_upload_file!, only: [:create] before_action :authorize_upload_file!, only: [:create, :authorize]
before_action :verify_workhorse_api!, only: [:authorize]
private private
......
class Projects::UploadsController < Projects::ApplicationController class Projects::UploadsController < Projects::ApplicationController
include UploadsActions include UploadsActions
include WorkhorseRequest
# These will kick you out if you don't have access. # These will kick you out if you don't have access.
skip_before_action :project, :repository, skip_before_action :project, :repository,
if: -> { action_name == 'show' && image_or_video? } if: -> { action_name == 'show' && image_or_video? }
before_action :authorize_upload_file!, only: [:create] before_action :authorize_upload_file!, only: [:create, :authorize]
before_action :verify_workhorse_api!, only: [:authorize]
private private
......
---
title: Support direct_upload for generic uploads
merge_request:
author:
type: added
...@@ -55,6 +55,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do ...@@ -55,6 +55,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
resources :uploads, only: [:create] do resources :uploads, only: [:create] do
collection do collection do
get ":secret/:filename", action: :show, as: :show, constraints: { filename: %r{[^/]+} } get ":secret/:filename", action: :show, as: :show, constraints: { filename: %r{[^/]+} }
post :authorize
end end
end end
......
...@@ -406,6 +406,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do ...@@ -406,6 +406,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
resources :uploads, only: [:create] do resources :uploads, only: [:create] do
collection do collection do
get ":secret/:filename", action: :show, as: :show, constraints: { filename: %r{[^/]+} } get ":secret/:filename", action: :show, as: :show, constraints: { filename: %r{[^/]+} }
post :authorize
end end
end end
......
...@@ -52,6 +52,7 @@ _The uploads are stored by default in ...@@ -52,6 +52,7 @@ _The uploads are stored by default in
>**Notes:** >**Notes:**
- [Introduced][ee-3867] in [GitLab Enterprise Edition Premium][eep] 10.5. - [Introduced][ee-3867] in [GitLab Enterprise Edition Premium][eep] 10.5.
- Since version 11.1, we support direct_upload to S3.
If you don't want to use the local disk where GitLab is installed to store the If you don't want to use the local disk where GitLab is installed to store the
uploads, you can use an object storage provider like AWS S3 instead. uploads, you can use an object storage provider like AWS S3 instead.
...@@ -65,7 +66,7 @@ For source installations the following settings are nested under `uploads:` and ...@@ -65,7 +66,7 @@ For source installations the following settings are nested under `uploads:` and
|---------|-------------|---------| |---------|-------------|---------|
| `enabled` | Enable/disable object storage | `false` | | `enabled` | Enable/disable object storage | `false` |
| `remote_directory` | The bucket name where Uploads will be stored| | | `remote_directory` | The bucket name where Uploads will be stored| |
| `direct_upload` | Set to true to enable direct upload of Uploads without the need of local shared storage. Option may be removed once we decide to support only single storage for all files. This is beta option as it uses inefficient way of uploading data (via Unicorn). The accelerated uploads gonna be implemented in future releases | `false` | | `direct_upload` | Set to true to enable direct upload of Uploads without the need of local shared storage. Option may be removed once we decide to support only single storage for all files. If enabled Workhorse uploads files directly to the object storage | `false` |
| `background_upload` | Set to false to disable automatic upload. Option may be removed once upload is direct to S3 | `true` | | `background_upload` | Set to false to disable automatic upload. Option may be removed once upload is direct to S3 | `true` |
| `proxy_download` | Set to true to enable proxying all files served. Option allows to reduce egress traffic as this allows clients to download directly from remote storage instead of proxying all data | `false` | | `proxy_download` | Set to true to enable proxying all files served. Option allows to reduce egress traffic as this allows clients to download directly from remote storage instead of proxying all data | `false` |
| `connection` | Various connection options described below | | | `connection` | Various connection options described below | |
......
require 'spec_helper' require 'spec_helper'
describe Groups::UploadsController do describe Groups::UploadsController do
include WorkhorseHelpers
let(:model) { create(:group, :public) } let(:model) { create(:group, :public) }
let(:params) do let(:params) do
{ group_id: model } { group_id: model }
...@@ -9,4 +11,10 @@ describe Groups::UploadsController do ...@@ -9,4 +11,10 @@ describe Groups::UploadsController do
it_behaves_like 'handle uploads' do it_behaves_like 'handle uploads' do
let(:uploader_class) { NamespaceFileUploader } let(:uploader_class) { NamespaceFileUploader }
end end
def post_authorize(verified: true)
request.headers.merge!(workhorse_internal_api_request_header) if verified
post :authorize, group_id: model.full_path, format: :json
end
end end
require 'spec_helper' require 'spec_helper'
describe Projects::UploadsController do describe Projects::UploadsController do
include WorkhorseHelpers
let(:model) { create(:project, :public) } let(:model) { create(:project, :public) }
let(:params) do let(:params) do
{ namespace_id: model.namespace.to_param, project_id: model } { namespace_id: model.namespace.to_param, project_id: model }
...@@ -15,4 +17,10 @@ describe Projects::UploadsController do ...@@ -15,4 +17,10 @@ describe Projects::UploadsController do
expect(response).to redirect_to(new_user_session_path) expect(response).to redirect_to(new_user_session_path)
end end
end end
def post_authorize(verified: true)
request.headers.merge!(workhorse_internal_api_request_header) if verified
post :authorize, namespace_id: model.namespace, project_id: model.path, format: :json
end
end end
...@@ -260,4 +260,83 @@ shared_examples 'handle uploads' do ...@@ -260,4 +260,83 @@ shared_examples 'handle uploads' do
end end
end end
end end
describe "POST #authorize" do
context 'when a user is not authorized to upload a file' do
it 'returns 404 status' do
post_authorize
expect(response.status).to eq(404)
end
end
context 'when a user can upload a file' do
before do
sign_in(user)
model.add_developer(user)
end
context 'and the request bypassed workhorse' do
it 'raises an exception' do
expect { post_authorize(verified: false) }.to raise_error JWT::DecodeError
end
end
context 'and request is sent by gitlab-workhorse to authorize the request' do
shared_examples 'a valid response' do
before do
post_authorize
end
it 'responds with status 200' do
expect(response).to have_gitlab_http_status(200)
end
it 'uses the gitlab-workhorse content type' do
expect(response.headers["Content-Type"]).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
end
end
shared_examples 'a local file' do
it_behaves_like 'a valid response' do
it 'responds with status 200, location of uploads store and object details' do
expect(json_response['TempPath']).to eq(uploader_class.workhorse_local_upload_path)
expect(json_response['RemoteObject']).to be_nil
end
end
end
context 'when using local storage' do
it_behaves_like 'a local file'
end
context 'when using remote storage' do
context 'when direct upload is enabled' do
before do
stub_uploads_object_storage(uploader_class, direct_upload: true)
end
it_behaves_like 'a valid response' do
it 'responds with status 200, location of uploads remote store and object details' do
expect(json_response['TempPath']).to eq(uploader_class.workhorse_local_upload_path)
expect(json_response['RemoteObject']).to have_key('ID')
expect(json_response['RemoteObject']).to have_key('GetURL')
expect(json_response['RemoteObject']).to have_key('StoreURL')
expect(json_response['RemoteObject']).to have_key('DeleteURL')
expect(json_response['RemoteObject']).to have_key('MultipartUpload')
end
end
end
context 'when direct upload is disabled' do
before do
stub_uploads_object_storage(uploader_class, direct_upload: false)
end
it_behaves_like 'a local file'
end
end
end
end
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