Welcome to mirror list, hosted at ThFree Co, Russian Federation.

lfs_storage_controller.rb « repositories « controllers « app - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 22f1a81b95b273299d7d955082ae3c07c0388021 (plain)
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
133
134
135
# 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)
      unless lfs_object && lfs_object.file.exists?
        render_lfs_not_found
        return
      end

      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