diff --git a/config/initializers/0_glitterconfig.rb b/config/initializers/0_glitterconfig.rb index 46f6e0d2..2e91e903 100644 --- a/config/initializers/0_glitterconfig.rb +++ b/config/initializers/0_glitterconfig.rb @@ -3,6 +3,10 @@ # ImageMagick geometry to use for thumbnail generation # defaults to 100 px width # http://www.imagemagick.org/script/command-line-processing.php#geometry + +# used for overriding the grack::auth module of grack gem +require Rails.root.join("lib", "rack", "grack_auth") + Glitter::Application.config.thumbnail_geometry=[50,50] Glitter::Application.config.inspire_geometry=[230,130] Glitter::Application.config.mobile_inspire_geometry=[600,340] @@ -39,6 +43,7 @@ Glitter::Application.config.repo_path = "#{Rails.root}/public/data/repos" elsif Rails.env.test? Glitter::Application.config.repo_dir="public/testdata" + Glitter::Application.config.repo_path = "#{Rails.root}/public/testdata/repos" elsif Rails.env.production? Glitter::Application.config.repo_dir="public/data" Glitter::Application.config.repo_path = "#{ENV["OPENSHIFT_DATA_DIR"]}/repos" diff --git a/lib/rack/git_http.rb b/lib/rack/git_http.rb deleted file mode 100644 index 564e5a4a..00000000 --- a/lib/rack/git_http.rb +++ /dev/null @@ -1,313 +0,0 @@ -require 'zlib' -require 'rack/request' -require 'rack/response' -require 'rack/utils' -require 'time' - -class GitHttp - class App - - SERVICES = [ - ["POST", 'service_rpc', "(.*?)/git-upload-pack$", 'upload-pack'], - ["POST", 'service_rpc', "(.*?)/git-receive-pack$", 'receive-pack'], - - ["GET", 'get_info_refs', "(.*?)/info/refs$"], - ["GET", 'get_text_file', "(.*?)/HEAD$"], - ["GET", 'get_text_file', "(.*?)/objects/info/alternates$"], - ["GET", 'get_text_file', "(.*?)/objects/info/http-alternates$"], - ["GET", 'get_info_packs', "(.*?)/objects/info/packs$"], - ["GET", 'get_text_file', "(.*?)/objects/info/[^/]*$"], - ["GET", 'get_loose_object', "(.*?)/objects/[0-9a-f]{2}/[0-9a-f]{38}$"], - ["GET", 'get_pack_file', "(.*?)/objects/pack/pack-[0-9a-f]{40}\\.pack$"], - ["GET", 'get_idx_file', "(.*?)/objects/pack/pack-[0-9a-f]{40}\\.idx$"], - ] - - def debug(msg) - File.open("#{ENV['OPENSHIFT_DATA_DIR']}/debug.txt", "a+") { |f| f.write("#{msg}\n") } - end - - def initialize(config = false) - set_config(config) - end - - def set_config(config) - @config = config || {} - end - - def set_config_setting(key, value) - @config[key] = value - end - - def call(env) - @env = env - @req = Rack::Request.new(env) - - cmd, path, @reqfile, @rpc = match_routing - - return render_method_not_allowed if cmd == 'not_allowed' - return render_not_found if !cmd - - @dir = get_git_dir(path) - debug "Path: #{@dir}" - return render_not_found if !@dir - - Dir.chdir(@dir) do - self.method(cmd).call() - end - end - - # --------------------------------- - # actual command handling functions - # --------------------------------- - - def service_rpc - return render_no_access if !has_access(@rpc, true) - input = read_body - - @res = Rack::Response.new - @res.status = 200 - @res["Content-Type"] = "application/x-git-%s-result" % @rpc - @res.finish do - command = git_command("#{@rpc} --stateless-rpc #{@dir}") - debug(command) - IO.popen(command, File::RDWR) do |pipe| - pipe.write(input) - while !pipe.eof? - block = pipe.read(8192) # 8M at a time - @res.write block # steam it to the client - end - end - end - end - - def get_info_refs - service_name = get_service_type - - if has_access(service_name) - cmd = git_command("#{service_name} --stateless-rpc --advertise-refs .") - debug(cmd) - refs = `#{cmd}` - - @res = Rack::Response.new - @res.status = 200 - @res["Content-Type"] = "application/x-git-%s-advertisement" % service_name - hdr_nocache - @res.write(pkt_write("# service=git-#{service_name}\n")) - @res.write(pkt_flush) - @res.write(refs) - @res.finish - else - dumb_info_refs - end - end - - def dumb_info_refs - update_server_info - send_file(@reqfile, "text/plain; charset=utf-8") do - hdr_nocache - end - end - - def get_info_packs - # objects/info/packs - send_file(@reqfile, "text/plain; charset=utf-8") do - hdr_nocache - end - end - - def get_loose_object - send_file(@reqfile, "application/x-git-loose-object") do - hdr_cache_forever - end - end - - def get_pack_file - send_file(@reqfile, "application/x-git-packed-objects") do - hdr_cache_forever - end - end - - def get_idx_file - send_file(@reqfile, "application/x-git-packed-objects-toc") do - hdr_cache_forever - end - end - - def get_text_file - send_file(@reqfile, "text/plain") do - hdr_nocache - end - end - - # ------------------------ - # logic helping functions - # ------------------------ - - F = ::File - - # some of this borrowed from the Rack::File implementation - def send_file(reqfile, content_type) - debug "File: #{reqfile}" - reqfile = File.join(@dir, reqfile) - return render_not_found if !F.exists?(reqfile) - - @res = Rack::Response.new - @res.status = 200 - @res["Content-Type"] = content_type - @res["Last-Modified"] = F.mtime(reqfile).httpdate - - yield - - if size = F.size?(reqfile) - @res["Content-Length"] = size.to_s - @res.finish do - F.open(reqfile, "rb") do |file| - while part = file.read(8192) - @res.write part - end - end - end - else - body = [F.read(reqfile)] - size = Rack::Utils.bytesize(body.first) - @res["Content-Length"] = size - @res.write body - @res.finish - end - end - - def get_git_dir(path) - root = @config[:project_root] || `pwd` - path = File.join(root, path) - if File.exists?(path) # TODO: check is a valid git directory - return path - end - false - end - - def get_service_type - service_type = @req.params['service'] - return false if !service_type - return false if service_type[0, 4] != 'git-' - service_type.gsub('git-', '') - end - - def match_routing - cmd = nil - path = nil - SERVICES.each do |method, handler, match, rpc| - if m = Regexp.new(match).match(@req.path_info) - return ['not_allowed'] if method != @req.request_method - cmd = handler - path = m[1] - file = @req.path_info.sub(path + '/', '') - debug "Service files: #{file}" - return [cmd, path, file, rpc] - end - end - return nil - end - - def has_access(rpc, check_content_type = false) - if check_content_type - return false if @req.content_type != "application/x-git-%s-request" % rpc - end - return false if !['upload-pack', 'receive-pack'].include? rpc - if rpc == 'receive-pack' - return @config[:receive_pack] if @config.include? :receive_pack - end - if rpc == 'upload-pack' - return @config[:upload_pack] if @config.include? :upload_pack - end - return get_config_setting(rpc) - end - - def get_config_setting(service_name) - service_name = service_name.gsub('-', '') - setting = get_git_config("http.#{service_name}") - if service_name == 'uploadpack' - return setting != 'false' - else - return setting == 'true' - end - end - - def get_git_config(config_name) - cmd = git_command("config #{config_name}") - `#{cmd}`.chomp - end - - def read_body - if @env["HTTP_CONTENT_ENCODING"] =~ /gzip/ - input = Zlib::GzipReader.new(@req.body).read - else - input = @req.body.read - end - end - - def update_server_info - cmd = git_command("update-server-info") - `#{cmd}` - end - - def git_command(command) - git_bin = @config[:git_path] || 'git' - command = "#{git_bin} #{command}" - command - end - - # -------------------------------------- - # HTTP error response handling functions - # -------------------------------------- - - PLAIN_TYPE = {"Content-Type" => "text/plain"} - - def render_method_not_allowed - if @env['SERVER_PROTOCOL'] == "HTTP/1.1" - [405, PLAIN_TYPE, ["Method Not Allowed"]] - else - [400, PLAIN_TYPE, ["Bad Request"]] - end - end - - def render_not_found - [404, PLAIN_TYPE, ["Not Found"]] - end - - def render_no_access - [403, PLAIN_TYPE, ["Forbidden"]] - end - - - # ------------------------------ - # packet-line handling functions - # ------------------------------ - - def pkt_flush - '0000' - end - - def pkt_write(str) - (str.size + 4).to_s(base=16).rjust(4, '0') + str - end - - - # ------------------------ - # header writing functions - # ------------------------ - - def hdr_nocache - @res["Expires"] = "Fri, 01 Jan 1980 00:00:00 GMT" - @res["Pragma"] = "no-cache" - @res["Cache-Control"] = "no-cache, max-age=0, must-revalidate" - end - - def hdr_cache_forever - now = Time.now().to_i - @res["Date"] = now.to_s - @res["Expires"] = (now + 31536000).to_s; - @res["Cache-Control"] = "public, max-age=31536000"; - end - - end -end diff --git a/lib/rack/grack_auth.rb b/lib/rack/grack_auth.rb new file mode 100644 index 00000000..eafdcb14 --- /dev/null +++ b/lib/rack/grack_auth.rb @@ -0,0 +1,99 @@ +module Grack + class Auth < Rack::Auth::Basic + + attr_accessor :user, :project, :env + + def call(env) + @env = env + @request = Rack::Request.new(env) + @auth = Request.new(env) + + auth! + if project && authorized_request? + @app.call(env) + elsif @user.nil? + 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 + @user = authenticate_user(login, password) + end + + # return nil if user is not found else return + # the user object + def authenticate_user(login, password) + user = User.find_by(username: login.to_s.downcase) + if user.nil? + return nil + else + user if user.valid_password?(password) + 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[0] = '' if path_with_namespace.start_with?('/') + return nil unless path_with_namespace.include?('/') + + # seperate username and project name + id = path_with_namespace.split('/') + @project_owner = User.find_by(username: id.first.to_s.downcase) + Project.with_deleted.find_by user_id: @project_owner.id, + name: id.last.to_s.downcase + end + end + + def authorized_request? + case git_cmd + when *%w{ git-upload-pack git-upload-archive } + unless project.private + # Allow clone/fetch for public projects + true + else + false + end + when *%w{ git-receive-pack } + if user + # Skip user authorization on upload request. + # It will be done by the pre-receive hook in the repository. + true + else + false + end + else + false + end + end + + def git_cmd + if @request.get? + @request.params['service'] + elsif @request.post? + File.basename(@request.path) + else + nil + end + end + + def render_not_found + [404, { "Content-Type" => "text/plain" }, ["Not Found"]] + end + end +end