diff --git a/Gemfile b/Gemfile index 9fd324d18..b68040c2a 100644 --- a/Gemfile +++ b/Gemfile @@ -4,7 +4,7 @@ gem 'gitolite-rugged', git: 'https://github.com/jbox-web/gitolite-rugged.git', tag: '1.4.0' # Ruby/Rack Git Smart-HTTP Server Handler -gem 'gitlab-grack', '~> 2.0.0', git: 'https://github.com/jbox-web/grack.git', require: 'grack', branch: 'fix_gemfile' +gem 'gitlab-grack', '~> 2.0.0', git: 'https://github.com/trobol/grack.git', require: 'grack', branch: 'git-lfs' # Memcached client for GitCache gem 'dalli' diff --git a/config/routes.rb b/config/routes.rb index 182149b39..b3d0f1ab9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -44,7 +44,7 @@ mount Grack::Bundle.new({}), at: RedmineGitHosting::Config.http_server_subdir, constraints: ->(request) { %r{[-/\w.]+\.git/}.match request.path_info }, - via: %i[get post] + via: %i[get post put] # Post Receive Hooks mount Hrack::Bundle.new({}), at: 'githooks/post-receive/:type/:projectid', via: [:post] diff --git a/lib/redmine_git_hosting/patches/grack_auth_patch.rb b/lib/redmine_git_hosting/patches/grack_auth_patch.rb index f437aaa63..4d2332a8d 100644 --- a/lib/redmine_git_hosting/patches/grack_auth_patch.rb +++ b/lib/redmine_git_hosting/patches/grack_auth_patch.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'grack/auth' +require 'grack/server' module RedmineGitHosting module Patches @@ -19,15 +20,26 @@ def call(env) # else # @env['PATH_INFO'] = @request.path # end - - if repository - auth! - else - render_not_found - end + + path = @request.path_info + return [400] unless m = /((?:[^\/]+\/)*?[^\/]+\.git)(\/.*)$/.match(path).to_a + @repo_path = m[1] + @req_route = m[2] + + return render_not_found("Repository not found") unless repository + return render_not_found("invalid route") unless has_route + + auth! end private + def has_route + Grack::Server::SERVICES.each do |method, _, match| + next unless m = Regexp.new(match).match(@req_route) + return method.include? @request.request_method + end + false + end def auth! if @auth.provided? @@ -53,15 +65,15 @@ def authenticate_user(login, password) end def authorized_request? - case git_cmd - when *RedmineGitHosting::GitAccess::DOWNLOAD_COMMANDS + case git_method + when :pull if @user RedmineGitHosting::GitAccess.new.download_access_check(@user, repository, ssl: is_ssl?).allowed? else # Allow clone/fetch for public projects repository.public_project? || repository.public_repo? end - when *RedmineGitHosting::GitAccess::PUSH_COMMANDS + when :push # Push requires valid SSL if !is_ssl? logger.error 'SmartHttp : your are trying to push data without SSL!, exiting !' @@ -75,24 +87,54 @@ def authorized_request? false end end - + def git_cmd if @request.get? @request.params['service'] elsif @request.post? File.basename @request.path + else + nil end end - - def repository - @repository ||= repository_by_path @request.path_info + + def git_method + arr = @req_route.split('/', 5)[1...] # first item is empty string + return git_lfs_cmd(arr) if arr[0] == "info" && arr[1] == "lfs" + + case git_cmd + when *RedmineGitHosting::GitAccess::DOWNLOAD_COMMANDS + return :pull + when *RedmineGitHosting::GitAccess::PUSH_COMMANDS + return :push + end + + return nil end - def repository_by_path(path) - if (m = %r{([^/]+/)*?[^/]+\.git}.match(path).to_a) - repo_path = m.first - Repository::Xitolite.find_by_path repo_path, loose: true + def git_lfs_cmd(arr) + case arr[2] + when "objects" + case @request.request_method + when "POST" # batch request + return :pull if arr[3] == "batch" + when "GET" + return :pull # file download + when "PUT" + return :push # file upload + end + when "locks" + if @request.get? + return :pull + elsif @request.post? + return :push if arr[3] == nil || arr[3] == "verify" || arr[4] == "unlock" + end end + return nil + end + + def repository + @repository ||= Repository::Xitolite.find_by_path @repo_path, loose: true end def is_ssl? @@ -111,8 +153,8 @@ def x_forwarded_ssl_headers? @request.env['HTTP_X_FORWARDED_SSL'].to_s == 'on' end - def render_not_found - [404, { 'Content-Type' => 'text/plain' }, ['Not Found']] + def render_not_found(msg = 'Not Found') + [404, { 'Content-Type' => 'text/plain' }, [msg]] end def logger