Remove obsolete code
This commit is contained in:
parent
0d9752446d
commit
df3ca41e62
5 changed files with 1 additions and 594 deletions
|
@ -1,6 +1,3 @@
|
|||
# GIT over HTTP
|
||||
require_dependency Rails.root.join('lib/gitlab/backend/grack_auth')
|
||||
|
||||
# GIT over SSH
|
||||
require_dependency Rails.root.join('lib/gitlab/backend/shell')
|
||||
|
||||
|
|
|
@ -1,163 +0,0 @@
|
|||
module Grack
|
||||
class AuthSpawner
|
||||
def self.call(env)
|
||||
# Avoid issues with instance variables in Grack::Auth persisting across
|
||||
# requests by creating a new instance for each request.
|
||||
Auth.new({}).call(env)
|
||||
end
|
||||
end
|
||||
|
||||
class Auth < Rack::Auth::Basic
|
||||
attr_accessor :user, :project, :env
|
||||
|
||||
def call(env)
|
||||
@env = env
|
||||
@request = Rack::Request.new(env)
|
||||
@auth = Request.new(env)
|
||||
|
||||
@ci = false
|
||||
|
||||
# Need this patch due to the rails mount
|
||||
# Need this if under RELATIVE_URL_ROOT
|
||||
unless Gitlab.config.gitlab.relative_url_root.empty?
|
||||
# If website is mounted using relative_url_root need to remove it first
|
||||
@env['PATH_INFO'] = @request.path.sub(Gitlab.config.gitlab.relative_url_root, '')
|
||||
else
|
||||
@env['PATH_INFO'] = @request.path
|
||||
end
|
||||
|
||||
@env['SCRIPT_NAME'] = ""
|
||||
|
||||
auth!
|
||||
|
||||
lfs_response = Gitlab::Lfs::Router.new(project, @user, @ci, @request).try_call
|
||||
return lfs_response unless lfs_response.nil?
|
||||
|
||||
if @user.nil? && !@ci
|
||||
unauthorized
|
||||
else
|
||||
render_not_found
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def auth!
|
||||
return unless @auth.provided?
|
||||
|
||||
return bad_request unless @auth.basic?
|
||||
|
||||
# Authentication with username and password
|
||||
login, password = @auth.credentials
|
||||
|
||||
# Allow authentication for GitLab CI service
|
||||
# if valid token passed
|
||||
if ci_request?(login, password)
|
||||
@ci = true
|
||||
return
|
||||
end
|
||||
|
||||
@user = authenticate_user(login, password)
|
||||
end
|
||||
|
||||
def ci_request?(login, password)
|
||||
matched_login = /(?<s>^[a-zA-Z]*-ci)-token$/.match(login)
|
||||
|
||||
if project && matched_login.present?
|
||||
underscored_service = matched_login['s'].underscore
|
||||
|
||||
if underscored_service == 'gitlab_ci'
|
||||
return project && project.valid_build_token?(password)
|
||||
elsif Service.available_services_names.include?(underscored_service)
|
||||
service_method = "#{underscored_service}_service"
|
||||
service = project.send(service_method)
|
||||
|
||||
return service && service.activated? && service.valid_token?(password)
|
||||
end
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
def oauth_access_token_check(login, password)
|
||||
if login == "oauth2" && git_cmd == 'git-upload-pack' && password.present?
|
||||
token = Doorkeeper::AccessToken.by_token(password)
|
||||
token && token.accessible? && User.find_by(id: token.resource_owner_id)
|
||||
end
|
||||
end
|
||||
|
||||
def authenticate_user(login, password)
|
||||
user = Gitlab::Auth.find_with_user_password(login, password)
|
||||
|
||||
unless user
|
||||
user = oauth_access_token_check(login, password)
|
||||
end
|
||||
|
||||
# If the user authenticated successfully, we reset the auth failure count
|
||||
# from Rack::Attack for that IP. A client may attempt to authenticate
|
||||
# with a username and blank password first, and only after it receives
|
||||
# a 401 error does it present a password. Resetting the count prevents
|
||||
# false positives from occurring.
|
||||
#
|
||||
# Otherwise, we let Rack::Attack know there was a failed authentication
|
||||
# attempt from this IP. This information is stored in the Rails cache
|
||||
# (Redis) and will be used by the Rack::Attack middleware to decide
|
||||
# whether to block requests from this IP.
|
||||
config = Gitlab.config.rack_attack.git_basic_auth
|
||||
|
||||
if config.enabled
|
||||
if user
|
||||
# A successful login will reset the auth failure count from this IP
|
||||
Rack::Attack::Allow2Ban.reset(@request.ip, config)
|
||||
else
|
||||
banned = Rack::Attack::Allow2Ban.filter(@request.ip, config) do
|
||||
# Unless the IP is whitelisted, return true so that Allow2Ban
|
||||
# increments the counter (stored in Rails.cache) for the IP
|
||||
if config.ip_whitelist.include?(@request.ip)
|
||||
false
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
if banned
|
||||
Rails.logger.info "IP #{@request.ip} failed to login " \
|
||||
"as #{login} but has been temporarily banned from Git auth"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
user
|
||||
end
|
||||
|
||||
def git_cmd
|
||||
if @request.get?
|
||||
@request.params['service']
|
||||
elsif @request.post?
|
||||
File.basename(@request.path)
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def project
|
||||
return @project if defined?(@project)
|
||||
|
||||
@project = project_by_path(@request.path_info)
|
||||
end
|
||||
|
||||
def project_by_path(path)
|
||||
if m = /^([\w\.\/-]+)\.git/.match(path).to_a
|
||||
path_with_namespace = m.last
|
||||
path_with_namespace.gsub!(/\.wiki$/, '')
|
||||
|
||||
path_with_namespace[0] = '' if path_with_namespace.start_with?('/')
|
||||
Project.find_with_namespace(path_with_namespace)
|
||||
end
|
||||
end
|
||||
|
||||
def render_not_found
|
||||
[404, { "Content-Type" => "text/plain" }, ["Not Found"]]
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,329 +0,0 @@
|
|||
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)
|
||||
return render_forbidden unless 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
|
|
@ -1,98 +0,0 @@
|
|||
module Gitlab
|
||||
module Lfs
|
||||
class Router
|
||||
attr_reader :project, :user, :ci, :request
|
||||
|
||||
def initialize(project, user, ci, request)
|
||||
@project = project
|
||||
@user = user
|
||||
@ci = ci
|
||||
@env = request.env
|
||||
@request = request
|
||||
end
|
||||
|
||||
def try_call
|
||||
return unless @request && @request.path.present?
|
||||
|
||||
case @request.request_method
|
||||
when 'GET'
|
||||
get_response
|
||||
when 'POST'
|
||||
post_response
|
||||
when 'PUT'
|
||||
put_response
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_response
|
||||
path_match = @request.path.match(/\/(info\/lfs|gitlab-lfs)\/objects\/([0-9a-f]{64})$/)
|
||||
return nil unless path_match
|
||||
|
||||
oid = path_match[2]
|
||||
return nil unless oid
|
||||
|
||||
case path_match[1]
|
||||
when "info/lfs"
|
||||
lfs.render_unsupported_deprecated_api
|
||||
when "gitlab-lfs"
|
||||
lfs.render_download_object_response(oid)
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def post_response
|
||||
post_path = @request.path.match(/\/info\/lfs\/objects(\/batch)?$/)
|
||||
return nil unless post_path
|
||||
|
||||
# Check for Batch API
|
||||
if post_path[0].ends_with?("/info/lfs/objects/batch")
|
||||
lfs.render_batch_operation_response
|
||||
elsif post_path[0].ends_with?("/info/lfs/objects")
|
||||
lfs.render_unsupported_deprecated_api
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def put_response
|
||||
object_match = @request.path.match(/\/gitlab-lfs\/objects\/([0-9a-f]{64})\/([0-9]+)(|\/authorize){1}$/)
|
||||
return nil if object_match.nil?
|
||||
|
||||
oid = object_match[1]
|
||||
size = object_match[2].try(:to_i)
|
||||
return nil if oid.nil? || size.nil?
|
||||
|
||||
# GitLab-workhorse requests
|
||||
# 1. Try to authorize the request
|
||||
# 2. send a request with a header containing the name of the temporary file
|
||||
if object_match[3] && object_match[3] == '/authorize'
|
||||
lfs.render_storage_upload_authorize_response(oid, size)
|
||||
else
|
||||
tmp_file_name = sanitize_tmp_filename(@request.env['HTTP_X_GITLAB_LFS_TMP'])
|
||||
lfs.render_storage_upload_store_response(oid, size, tmp_file_name)
|
||||
end
|
||||
end
|
||||
|
||||
def lfs
|
||||
return unless @project
|
||||
|
||||
Gitlab::Lfs::Response.new(@project, @user, @ci, @request)
|
||||
end
|
||||
|
||||
def sanitize_tmp_filename(name)
|
||||
if name.present?
|
||||
name.gsub!(/^.*(\\|\/)/, '')
|
||||
name = name.match(/[0-9a-f]{73}/)
|
||||
name[0] if name
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Lfs::Router do
|
||||
describe 'Git LFS API and storage' do
|
||||
let(:user) { create(:user) }
|
||||
let!(:lfs_object) { create(:lfs_object, :with_file) }
|
||||
|
||||
|
|
Loading…
Reference in a new issue