1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
|
# frozen_string_literal: true
module Repositories
class LfsStorageController < Repositories::GitHttpClientController
include LfsRequest
include WorkhorseRequest
include SendFileUpload
InvalidUploadedFile = Class.new(StandardError)
skip_before_action :verify_workhorse_api!, only: :download
# added here as a part of the refactor, will be removed
# https://gitlab.com/gitlab-org/gitlab/-/issues/328692
delegate :deploy_token, :user, to: :authentication_result, allow_nil: true
urgency :medium, [:download, :upload_authorize]
urgency :low, [:upload_finalize]
def download
lfs_object = LfsObject.find_by_oid(oid)
return render_lfs_not_found unless lfs_object&.file&.exists?
send_upload(lfs_object.file, send_params: { content_type: "application/octet-stream" })
end
def upload_authorize
set_workhorse_internal_api_content_type
# We don't actually know whether Workhorse received an LFS upload
# request with a Content-Length header or `Transfer-Encoding:
# chunked`. Since we don't know, we need to be pessimistic and
# set `has_length` to `false` so that multipart uploads will be
# used for AWS. Otherwise, AWS will respond with `501 NOT IMPLEMENTED`
# error because a PutObject request with `Transfer-Encoding: chunked`
# is not supported.
#
# This is only an issue with object storage-specific settings, not
# with consolidated object storage settings.
authorized = LfsObjectUploader.workhorse_authorize(has_length: false, maximum_size: size)
authorized.merge!(LfsOid: oid, LfsSize: size)
render json: authorized
end
def upload_finalize
validate_uploaded_file!
if store_file!(oid, size)
head :ok, content_type: LfsRequest::CONTENT_TYPE
else
render plain: 'Unprocessable entity', status: :unprocessable_entity
end
rescue ActiveRecord::RecordInvalid
render_lfs_forbidden
rescue UploadedFile::InvalidPathError
render_lfs_forbidden
rescue ObjectStorage::RemoteStoreError
render_lfs_forbidden
rescue InvalidUploadedFile
render plain: 'SHA256 or size mismatch', status: :bad_request
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
def uploaded_file
params[:file]
end
# rubocop: disable CodeReuse/ActiveRecord
def store_file!(oid, size)
object = LfsObject.find_by(oid: oid, size: size)
if object
replace_file!(object) unless object.file&.exists?
else
object = create_file!(oid, size)
end
return unless object
link_to_project!(object)
end
# rubocop: enable CodeReuse/ActiveRecord
def create_file!(oid, size)
return unless uploaded_file.is_a?(UploadedFile)
LfsObject.create!(oid: oid, size: size, file: uploaded_file)
end
def replace_file!(lfs_object)
raise UploadedFile::InvalidPathError unless uploaded_file.is_a?(UploadedFile)
Gitlab::AppJsonLogger.info(message: "LFS file replaced because it did not exist", oid: oid, size: size)
lfs_object.file = uploaded_file
lfs_object.save!
end
def link_to_project!(object)
return unless object
LfsObjectsProject.safe_find_or_create_by!(
project: project,
lfs_object: object
)
end
def validate_uploaded_file!
return unless uploaded_file
if size != uploaded_file.size || oid != uploaded_file.sha256
raise InvalidUploadedFile
end
end
end
end
|