gitlab-org--gitlab-foss/lib/gitlab/lfs/response.rb
2016-07-01 21:56:17 +02:00

327 lines
8.7 KiB
Ruby

module Gitlab
module Lfs
class Response
def initialize(project, user, ci, request)
@origin_project = project
@project = storage_project(project)
@user = user
@ci = ci
@env = request.env
@request = request
end
def render_download_object_response(oid)
render_response_to_download do
if check_download_sendfile_header?
render_lfs_sendfile(oid)
else
render_not_found
end
end
end
def render_batch_operation_response
request_body = JSON.parse(@request.body.read)
case request_body["operation"]
when "download"
render_batch_download(request_body)
when "upload"
render_batch_upload(request_body)
else
render_not_found
end
end
def render_storage_upload_authorize_response(oid, size)
render_response_to_push do
[
200,
{ "Content-Type" => "application/json; charset=utf-8" },
[JSON.dump({
'StoreLFSPath' => "#{Gitlab.config.lfs.storage_path}/tmp/upload",
'LfsOid' => oid,
'LfsSize' => size
})]
]
end
end
def render_storage_upload_store_response(oid, size, tmp_file_name)
render_response_to_push do
render_lfs_upload_ok(oid, size, tmp_file_name)
end
end
def render_unsupported_deprecated_api
[
501,
{ "Content-Type" => "application/json; charset=utf-8" },
[JSON.dump({
'message' => 'Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.',
'documentation_url' => "#{Gitlab.config.gitlab.url}/help",
})]
]
end
private
def render_not_enabled
[
501,
{
"Content-Type" => "application/json; charset=utf-8",
},
[JSON.dump({
'message' => 'Git LFS is not enabled on this GitLab server, contact your admin.',
'documentation_url' => "#{Gitlab.config.gitlab.url}/help",
})]
]
end
def render_unauthorized
[
401,
{
'Content-Type' => 'text/plain'
},
['Unauthorized']
]
end
def render_not_found
[
404,
{
"Content-Type" => "application/vnd.git-lfs+json"
},
[JSON.dump({
'message' => 'Not found.',
'documentation_url' => "#{Gitlab.config.gitlab.url}/help",
})]
]
end
def render_forbidden
[
403,
{
"Content-Type" => "application/vnd.git-lfs+json"
},
[JSON.dump({
'message' => 'Access forbidden. Check your access level.',
'documentation_url' => "#{Gitlab.config.gitlab.url}/help",
})]
]
end
def render_lfs_sendfile(oid)
return render_not_found unless oid.present?
lfs_object = object_for_download(oid)
if lfs_object && lfs_object.file.exists?
[
200,
{
# GitLab-workhorse will forward Content-Type header
"Content-Type" => "application/octet-stream",
"X-Sendfile" => lfs_object.file.path
},
[]
]
else
render_not_found
end
end
def render_batch_upload(body)
return render_not_found if body.empty? || body['objects'].nil?
render_response_to_push do
response = build_upload_batch_response(body['objects'])
[
200,
{
"Content-Type" => "application/json; charset=utf-8",
"Cache-Control" => "private",
},
[JSON.dump(response)]
]
end
end
def render_batch_download(body)
return render_not_found if body.empty? || body['objects'].nil?
render_response_to_download do
response = build_download_batch_response(body['objects'])
[
200,
{
"Content-Type" => "application/json; charset=utf-8",
"Cache-Control" => "private",
},
[JSON.dump(response)]
]
end
end
def render_lfs_upload_ok(oid, size, tmp_file)
if store_file(oid, size, tmp_file)
[
200,
{
'Content-Type' => 'text/plain',
'Content-Length' => 0
},
[]
]
else
[
422,
{ 'Content-Type' => 'text/plain' },
["Unprocessable entity"]
]
end
end
def render_response_to_download
return render_not_enabled unless Gitlab.config.lfs.enabled
unless @project.public?
return render_unauthorized unless @user || @ci
return render_forbidden unless user_can_fetch?
end
yield
end
def render_response_to_push
return render_not_enabled unless Gitlab.config.lfs.enabled
return render_unauthorized unless @user
return render_forbidden unless user_can_push?
yield
end
def check_download_sendfile_header?
@env['HTTP_X_SENDFILE_TYPE'].to_s == "X-Sendfile"
end
def user_can_fetch?
# Check user access against the project they used to initiate the pull
@ci || @user.can?(:download_code, @origin_project)
end
def user_can_push?
# Check user access against the project they used to initiate the push
@user.can?(:push_code, @origin_project)
end
def storage_project(project)
if project.forked?
storage_project(project.forked_from_project)
else
project
end
end
def store_file(oid, size, tmp_file)
tmp_file_path = File.join("#{Gitlab.config.lfs.storage_path}/tmp/upload", tmp_file)
object = LfsObject.find_or_create_by(oid: oid, size: size)
if object.file.exists?
success = true
else
success = move_tmp_file_to_storage(object, tmp_file_path)
end
if success
success = link_to_project(object)
end
success
ensure
# Ensure that the tmp file is removed
FileUtils.rm_f(tmp_file_path)
end
def object_for_download(oid)
@project.lfs_objects.find_by(oid: oid)
end
def move_tmp_file_to_storage(object, path)
File.open(path) do |f|
object.file = f
end
object.file.store!
object.save
end
def link_to_project(object)
if object && !object.projects.exists?(@project.id)
object.projects << @project
object.save
end
end
def select_existing_objects(objects)
objects_oids = objects.map { |o| o['oid'] }
@project.lfs_objects.where(oid: objects_oids).pluck(:oid).to_set
end
def build_upload_batch_response(objects)
selected_objects = select_existing_objects(objects)
upload_hypermedia_links(objects, selected_objects)
end
def build_download_batch_response(objects)
selected_objects = select_existing_objects(objects)
download_hypermedia_links(objects, selected_objects)
end
def download_hypermedia_links(all_objects, existing_objects)
all_objects.each do |object|
if existing_objects.include?(object['oid'])
object['actions'] = {
'download' => {
'href' => "#{@origin_project.http_url_to_repo}/gitlab-lfs/objects/#{object['oid']}",
'header' => {
'Authorization' => @env['HTTP_AUTHORIZATION']
}.compact
}
}
else
object['error'] = {
'code' => 404,
'message' => "Object does not exist on the server or you don't have permissions to access it",
}
end
end
{ 'objects' => all_objects }
end
def upload_hypermedia_links(all_objects, existing_objects)
all_objects.each do |object|
# generate actions only for non-existing objects
next if existing_objects.include?(object['oid'])
object['actions'] = {
'upload' => {
'href' => "#{@origin_project.http_url_to_repo}/gitlab-lfs/objects/#{object['oid']}/#{object['size']}",
'header' => {
'Authorization' => @env['HTTP_AUTHORIZATION']
}.compact
}
}
end
{ 'objects' => all_objects }
end
end
end
end