# frozen_string_literal: true module Repositories class LfsApiController < Repositories::GitHttpClientController include LfsRequest include Gitlab::Utils::StrongMemoize LFS_TRANSFER_CONTENT_TYPE = 'application/octet-stream' skip_before_action :lfs_check_access!, only: [:deprecated] before_action :lfs_check_batch_operation!, only: [:batch] # 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, [:batch] def batch unless objects.present? render_lfs_not_found return end if download_request? render json: { objects: download_objects! }, content_type: LfsRequest::CONTENT_TYPE elsif upload_request? render json: { objects: upload_objects! }, content_type: LfsRequest::CONTENT_TYPE else raise "Never reached" end end def deprecated render( json: { 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" }, content_type: LfsRequest::CONTENT_TYPE, status: :not_implemented ) end private def download_request? params[:operation] == 'download' end def upload_request? params[:operation] == 'upload' end def download_objects! existing_oids = project.lfs_objects_oids(oids: objects_oids) objects.each do |object| if existing_oids.include?(object[:oid]) object[:actions] = download_actions(object) if Guest.can?(:download_code, project) object[:authenticated] = true end 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 end def upload_objects! existing_oids = project.lfs_objects_oids(oids: objects_oids) objects.each do |object| next if existing_oids.include?(object[:oid]) next if should_auto_link? && oids_from_fork.include?(object[:oid]) && link_to_project!(object) object[:actions] = upload_actions(object) end objects end def download_actions(object) { download: { href: "#{project.http_url_to_repo}/gitlab-lfs/objects/#{object[:oid]}", header: { Authorization: authorization_header }.compact } } end def upload_actions(object) { upload: { href: "#{upload_http_url_to_repo}/gitlab-lfs/objects/#{object[:oid]}/#{object[:size]}", header: upload_headers } } end # Overridden in EE def upload_http_url_to_repo project.http_url_to_repo end def upload_headers { Authorization: authorization_header, # git-lfs v2.5.0 sets the Content-Type based on the uploaded file. This # ensures that Workhorse can intercept the request. 'Content-Type': LFS_TRANSFER_CONTENT_TYPE, 'Transfer-Encoding': 'chunked' } end def lfs_check_batch_operation! if batch_operation_disallowed? render( json: { message: lfs_read_only_message }, content_type: LfsRequest::CONTENT_TYPE, status: :forbidden ) end end # Overridden in EE def batch_operation_disallowed? upload_request? && Gitlab::Database.read_only? end # Overridden in EE def lfs_read_only_message _('You cannot write to this read-only GitLab instance.') end def authorization_header strong_memoize(:authorization_header) do lfs_auth_header || request.headers['Authorization'] end end def lfs_auth_header return unless user Gitlab::LfsToken.new(user).basic_encoding end def should_auto_link? return false unless Feature.enabled?(:lfs_auto_link_fork_source, project, default_enabled: :yaml) return false unless project.forked? # Sanity check in case for some reason the user doesn't have access to the parent can?(user, :download_code, project.fork_source) end def oids_from_fork @oids_from_fork ||= project.lfs_objects_oids_from_fork_source(oids: objects_oids) end def link_to_project!(object) lfs_object = LfsObject.for_oid_and_size(object[:oid], object[:size]) return unless lfs_object LfsObjectsProject.link_to_project!(lfs_object, project) Gitlab::AppJsonLogger.info(message: "LFS object auto-linked to forked project", lfs_object_oid: lfs_object.oid, lfs_object_size: lfs_object.size, source_project_id: project.fork_source.id, source_project_path: project.fork_source.full_path, target_project_id: project.project_id, target_project_path: project.full_path) end end end Repositories::LfsApiController.prepend_mod_with('Repositories::LfsApiController')