2018-09-25 23:45:43 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2016-07-20 12:41:26 -04:00
|
|
|
class Projects::LfsStorageController < Projects::GitHttpClientController
|
2016-11-21 10:31:51 -05:00
|
|
|
include LfsRequest
|
|
|
|
include WorkhorseRequest
|
2017-09-07 17:27:04 -04:00
|
|
|
include SendFileUpload
|
2016-07-20 12:41:26 -04:00
|
|
|
|
Verify that LFS upload requests are genuine
LFS uploads are handled in concert by workhorse and rails. In normal
use, workhorse:
* Authorizes the request with rails (upload_authorize)
* Handles the upload of the file to a tempfile - disk or object storage
* Validates the file size and contents
* Hands off to rails to complete the upload (upload_finalize)
In `upload_finalize`, the LFS object is linked to the project. As LFS
objects are deduplicated across all projects, it may already exist. If
not, the temporary file is copied to the correct place, and will be
used by all future LFS objects with the same OID.
Workhorse uses the Content-Type of the request to decide to follow this
routine, as the URLs are ambiguous. If the Content-Type is anything but
"application/octet-stream", the request is proxied directly to rails,
on the assumption that this is a normal file edit request. If it's an
actual LFS request with a different content-type, however, it is routed
to the Rails `upload_finalize` action, which treats it as an LFS upload
just as it would a workhorse-modified request.
The outcome is that users can upload LFS objects that don't match the
declared size or OID. They can also create links to LFS objects they
don't really own, allowing them to read the contents of files if they
know just the size or OID.
We can close this hole by requiring requests to `upload_finalize` to be
sourced from Workhorse. The mechanism to do this already exists.
2019-01-08 12:57:58 -05:00
|
|
|
skip_before_action :verify_workhorse_api!, only: :download
|
2016-07-20 12:41:26 -04:00
|
|
|
|
|
|
|
def download
|
|
|
|
lfs_object = LfsObject.find_by_oid(oid)
|
|
|
|
unless lfs_object && lfs_object.file.exists?
|
|
|
|
render_lfs_not_found
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2017-09-07 17:27:04 -04:00
|
|
|
send_upload(lfs_object.file, send_params: { content_type: "application/octet-stream" })
|
2016-07-20 12:41:26 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def upload_authorize
|
2016-08-19 13:10:41 -04:00
|
|
|
set_workhorse_internal_api_content_type
|
2018-03-23 06:07:22 -04:00
|
|
|
|
2018-05-09 11:27:38 -04:00
|
|
|
authorized = LfsObjectUploader.workhorse_authorize(has_length: true)
|
2018-03-23 06:07:22 -04:00
|
|
|
authorized.merge!(LfsOid: oid, LfsSize: size)
|
|
|
|
|
|
|
|
render json: authorized
|
2016-07-20 12:41:26 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def upload_finalize
|
2018-03-23 06:07:22 -04:00
|
|
|
if store_file!(oid, size)
|
2016-07-20 12:41:26 -04:00
|
|
|
head 200
|
|
|
|
else
|
2018-07-02 06:43:06 -04:00
|
|
|
render plain: 'Unprocessable entity', status: :unprocessable_entity
|
2016-07-20 12:41:26 -04:00
|
|
|
end
|
2018-03-23 06:07:22 -04:00
|
|
|
rescue ActiveRecord::RecordInvalid
|
2018-04-03 12:47:33 -04:00
|
|
|
render_lfs_forbidden
|
|
|
|
rescue UploadedFile::InvalidPathError
|
|
|
|
render_lfs_forbidden
|
2018-03-23 06:07:22 -04:00
|
|
|
rescue ObjectStorage::RemoteStoreError
|
|
|
|
render_lfs_forbidden
|
2016-07-20 12:41:26 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def download_request?
|
|
|
|
action_name == 'download'
|
|
|
|
end
|
|
|
|
|
|
|
|
def upload_request?
|
|
|
|
%w[upload_authorize upload_finalize].include? action_name
|
|
|
|
end
|
|
|
|
|
|
|
|
def oid
|
|
|
|
params[:oid].to_s
|
|
|
|
end
|
|
|
|
|
|
|
|
def size
|
|
|
|
params[:size].to_i
|
|
|
|
end
|
|
|
|
|
2018-08-27 11:31:01 -04:00
|
|
|
# rubocop: disable CodeReuse/ActiveRecord
|
2018-03-23 06:07:22 -04:00
|
|
|
def store_file!(oid, size)
|
|
|
|
object = LfsObject.find_by(oid: oid, size: size)
|
|
|
|
unless object&.file&.exists?
|
|
|
|
object = create_file!(oid, size)
|
|
|
|
end
|
2016-07-20 12:41:26 -04:00
|
|
|
|
2018-03-23 06:07:22 -04:00
|
|
|
return unless object
|
2016-07-20 12:41:26 -04:00
|
|
|
|
2018-03-23 06:07:22 -04:00
|
|
|
link_to_project!(object)
|
2016-07-20 12:41:26 -04:00
|
|
|
end
|
2018-08-27 11:31:01 -04:00
|
|
|
# rubocop: enable CodeReuse/ActiveRecord
|
2016-07-20 12:41:26 -04:00
|
|
|
|
2018-03-23 06:07:22 -04:00
|
|
|
def create_file!(oid, size)
|
2018-04-03 12:47:33 -04:00
|
|
|
uploaded_file = UploadedFile.from_params(
|
|
|
|
params, :file, LfsObjectUploader.workhorse_local_upload_path)
|
|
|
|
return unless uploaded_file
|
|
|
|
|
|
|
|
LfsObject.create!(oid: oid, size: size, file: uploaded_file)
|
2016-07-20 12:41:26 -04:00
|
|
|
end
|
|
|
|
|
2018-08-27 11:31:01 -04:00
|
|
|
# rubocop: disable CodeReuse/ActiveRecord
|
2018-03-23 06:07:22 -04:00
|
|
|
def link_to_project!(object)
|
2016-07-20 12:41:26 -04:00
|
|
|
if object && !object.projects.exists?(storage_project.id)
|
2018-05-01 16:27:54 -04:00
|
|
|
object.lfs_objects_projects.create!(project: storage_project)
|
2016-07-20 12:41:26 -04:00
|
|
|
end
|
|
|
|
end
|
2018-08-27 11:31:01 -04:00
|
|
|
# rubocop: enable CodeReuse/ActiveRecord
|
2016-07-20 12:41:26 -04:00
|
|
|
end
|