gitlab-grack 2.0.0.rc1 → 2.0.0.rc2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +3 -22
- data/.gitmodules +3 -0
- data/.travis.yml +14 -0
- data/CHANGELOG +2 -0
- data/Gemfile +7 -2
- data/Gemfile.lock +17 -8
- data/README.md +95 -292
- data/Rakefile +27 -0
- data/examples/dispatch.fcgi +9 -0
- data/grack.gemspec +20 -0
- data/install.txt +60 -0
- data/lib/grack.rb +5 -0
- data/lib/grack/auth.rb +37 -0
- data/lib/grack/bundle.rb +20 -0
- data/lib/grack/server.rb +346 -0
- data/lib/grack/version.rb +3 -0
- data/tests/main_test.rb +255 -0
- metadata +18 -22
- data/Makefile +0 -153
- data/Procfile.example +0 -3
- data/Vagrantfile +0 -41
- data/database.yml.example +0 -13
- data/gitlab-openldap/.gitignore +0 -1
- data/gitlab-openldap/Makefile +0 -40
- data/gitlab-openldap/README.md +0 -60
- data/gitlab-openldap/bootstrap.ldif +0 -36
- data/gitlab-openldap/frontend.alt.example.com.ldif +0 -109
- data/gitlab-openldap/frontend.example.com.ldif +0 -109
- data/gitlab-openldap/run-slapd +0 -3
- data/gitlab-openldap/run-slapd-alt +0 -3
- data/redis/redis.conf.example +0 -2
- data/redis/resque.yml.example +0 -2
- data/support/edit-gitlab.yml +0 -11
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
|
4
|
+
task :default => :test
|
5
|
+
|
6
|
+
desc "Run the tests."
|
7
|
+
task :test do
|
8
|
+
Dir.glob("tests/*_test.rb").each do |f|
|
9
|
+
system "ruby #{f}"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
desc "Run test coverage."
|
14
|
+
task :rcov do
|
15
|
+
system "rcov tests/*_test.rb -i lib/git_http.rb -x rack -x Library -x tests"
|
16
|
+
system "open coverage/index.html"
|
17
|
+
end
|
18
|
+
|
19
|
+
namespace :grack do
|
20
|
+
desc "Start Grack"
|
21
|
+
task :start do
|
22
|
+
system "rackup config.ru -p 8080"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
desc "Start everything."
|
27
|
+
multitask :start => [ 'grack:start' ]
|
@@ -0,0 +1,9 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/lib')
|
3
|
+
require 'lib/git_http'
|
4
|
+
config = {
|
5
|
+
:project_root => "/opt",
|
6
|
+
:upload_pack => true,
|
7
|
+
:receive_pack => false,
|
8
|
+
}
|
9
|
+
Rack::Handler::FastCGI.run(GitHttp::App.new(config))
|
data/grack.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/grack/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Scott Chacon"]
|
6
|
+
gem.email = ["schacon@gmail.com"]
|
7
|
+
gem.description = %q{Ruby/Rack Git Smart-HTTP Server Handler}
|
8
|
+
gem.summary = %q{Ruby/Rack Git Smart-HTTP Server Handler}
|
9
|
+
gem.homepage = "https://github.com/gitlabhq/grack"
|
10
|
+
|
11
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
12
|
+
gem.files = `git ls-files`.split("\n")
|
13
|
+
gem.test_files = `git ls-files -- tests/*`.split("\n")
|
14
|
+
gem.name = "gitlab-grack"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Grack::VERSION
|
17
|
+
|
18
|
+
gem.add_dependency("rack", "~> 1.5.1")
|
19
|
+
gem.add_development_dependency("mocha", "~> 0.11")
|
20
|
+
end
|
data/install.txt
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
Installation
|
2
|
+
========================
|
3
|
+
|
4
|
+
** This documentation is not finished yet. I haven't tested all of
|
5
|
+
these and it's obviously incomplete - these are currently just notes.
|
6
|
+
|
7
|
+
FastCGI
|
8
|
+
---------------------------------------
|
9
|
+
Here is an example config from lighttpd server:
|
10
|
+
----
|
11
|
+
# main fastcgi entry
|
12
|
+
$HTTP["url"] =~ "^/myapp/.+$" {
|
13
|
+
fastcgi.server = ( "/myapp" =>
|
14
|
+
( "localhost" =>
|
15
|
+
( "bin-path" => "/var/www/localhost/cgi-bin/dispatch.fcgi",
|
16
|
+
"docroot" => "/var/www/localhost/htdocs/myapp",
|
17
|
+
"host" => "127.0.0.1",
|
18
|
+
"port" => 1026,
|
19
|
+
"check-local" => "disable"
|
20
|
+
)
|
21
|
+
)
|
22
|
+
)
|
23
|
+
} # HTTP[url]
|
24
|
+
----
|
25
|
+
You can use the examples/dispatch.fcgi file as your dispatcher.
|
26
|
+
|
27
|
+
(Example Apache setup?)
|
28
|
+
|
29
|
+
Installing in a Java application server
|
30
|
+
---------------------------------------
|
31
|
+
# install Warbler
|
32
|
+
$ sudo gem install warbler
|
33
|
+
$ cd gitsmart
|
34
|
+
$ (edit config.ru)
|
35
|
+
$ warble
|
36
|
+
$ cp gitsmart.war /path/to/java/autodeploy/dir
|
37
|
+
|
38
|
+
Unicorn
|
39
|
+
---------------------------------------
|
40
|
+
With Unicorn (http://unicorn.bogomips.org/) you can just run 'unicorn'
|
41
|
+
in the directory with the config.ru file.
|
42
|
+
|
43
|
+
Thin
|
44
|
+
---------------------------------------
|
45
|
+
thin.yml
|
46
|
+
---
|
47
|
+
pid: /home/deploy/myapp/server/thin.pid
|
48
|
+
log: /home/deploy/myapp/logs/thin.log
|
49
|
+
timeout: 30
|
50
|
+
port: 7654
|
51
|
+
max_conns: 1024
|
52
|
+
chdir: /home/deploy/myapp/site_files
|
53
|
+
rackup: /home/deploy/myapp/server/config.ru
|
54
|
+
max_persistent_conns: 512
|
55
|
+
environment: production
|
56
|
+
address: 127.0.0.1
|
57
|
+
servers: 1
|
58
|
+
daemonize: true
|
59
|
+
|
60
|
+
|
data/lib/grack/auth.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'rack/auth/basic'
|
2
|
+
require 'rack/auth/abstract/handler'
|
3
|
+
require 'rack/auth/abstract/request'
|
4
|
+
|
5
|
+
module Grack
|
6
|
+
class Auth < Rack::Auth::Basic
|
7
|
+
def call(env)
|
8
|
+
@env = env
|
9
|
+
@request = Rack::Request.new(env)
|
10
|
+
@auth = Request.new(env)
|
11
|
+
|
12
|
+
if not @auth.provided?
|
13
|
+
unauthorized
|
14
|
+
elsif not @auth.basic?
|
15
|
+
bad_request
|
16
|
+
else
|
17
|
+
result = if (access = valid? and access == true)
|
18
|
+
@env['REMOTE_USER'] = @auth.username
|
19
|
+
@app.call(env)
|
20
|
+
else
|
21
|
+
if access == '404'
|
22
|
+
render_not_found
|
23
|
+
elsif access == '403'
|
24
|
+
render_no_access
|
25
|
+
else
|
26
|
+
unauthorized
|
27
|
+
end
|
28
|
+
end
|
29
|
+
result
|
30
|
+
end
|
31
|
+
end# method call
|
32
|
+
|
33
|
+
def valid?
|
34
|
+
false
|
35
|
+
end
|
36
|
+
end# class Auth
|
37
|
+
end# module Grack
|
data/lib/grack/bundle.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rack/builder'
|
2
|
+
require 'grack/auth'
|
3
|
+
require 'grack/server'
|
4
|
+
|
5
|
+
module Grack
|
6
|
+
module Bundle
|
7
|
+
extend self
|
8
|
+
|
9
|
+
def new(config)
|
10
|
+
Rack::Builder.new do
|
11
|
+
use Grack::Auth do |username, password|
|
12
|
+
false
|
13
|
+
end
|
14
|
+
|
15
|
+
run Grack::Server.new(config)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
data/lib/grack/server.rb
ADDED
@@ -0,0 +1,346 @@
|
|
1
|
+
require 'zlib'
|
2
|
+
require 'rack/request'
|
3
|
+
require 'rack/response'
|
4
|
+
require 'rack/utils'
|
5
|
+
require 'time'
|
6
|
+
|
7
|
+
module Grack
|
8
|
+
class Server
|
9
|
+
|
10
|
+
SERVICES = [
|
11
|
+
["POST", 'service_rpc', "(.*?)/git-upload-pack$", 'upload-pack'],
|
12
|
+
["POST", 'service_rpc', "(.*?)/git-receive-pack$", 'receive-pack'],
|
13
|
+
|
14
|
+
["GET", 'get_info_refs', "(.*?)/info/refs$"],
|
15
|
+
["GET", 'get_text_file', "(.*?)/HEAD$"],
|
16
|
+
["GET", 'get_text_file', "(.*?)/objects/info/alternates$"],
|
17
|
+
["GET", 'get_text_file', "(.*?)/objects/info/http-alternates$"],
|
18
|
+
["GET", 'get_info_packs', "(.*?)/objects/info/packs$"],
|
19
|
+
["GET", 'get_text_file', "(.*?)/objects/info/[^/]*$"],
|
20
|
+
["GET", 'get_loose_object', "(.*?)/objects/[0-9a-f]{2}/[0-9a-f]{38}$"],
|
21
|
+
["GET", 'get_pack_file', "(.*?)/objects/pack/pack-[0-9a-f]{40}\\.pack$"],
|
22
|
+
["GET", 'get_idx_file', "(.*?)/objects/pack/pack-[0-9a-f]{40}\\.idx$"],
|
23
|
+
]
|
24
|
+
|
25
|
+
def initialize(config = false)
|
26
|
+
set_config(config)
|
27
|
+
end
|
28
|
+
|
29
|
+
def set_config(config)
|
30
|
+
@config = config || {}
|
31
|
+
end
|
32
|
+
|
33
|
+
def set_config_setting(key, value)
|
34
|
+
@config[key] = value
|
35
|
+
end
|
36
|
+
|
37
|
+
def call(env)
|
38
|
+
dup._call(env)
|
39
|
+
end
|
40
|
+
|
41
|
+
def _call(env)
|
42
|
+
@env = env
|
43
|
+
@req = Rack::Request.new(env)
|
44
|
+
|
45
|
+
cmd, path, @reqfile, @rpc = match_routing
|
46
|
+
|
47
|
+
return render_method_not_allowed if cmd == 'not_allowed'
|
48
|
+
return render_not_found if !cmd
|
49
|
+
|
50
|
+
@dir = get_git_dir(path)
|
51
|
+
return render_not_found if !@dir
|
52
|
+
|
53
|
+
self.method(cmd).call()
|
54
|
+
end
|
55
|
+
|
56
|
+
# ---------------------------------
|
57
|
+
# actual command handling functions
|
58
|
+
# ---------------------------------
|
59
|
+
|
60
|
+
# Uses chunked (streaming) transfer, otherwise response
|
61
|
+
# blocks to calculate Content-Length header
|
62
|
+
# http://en.wikipedia.org/wiki/Chunked_transfer_encoding
|
63
|
+
|
64
|
+
CRLF = "\r\n"
|
65
|
+
|
66
|
+
def service_rpc
|
67
|
+
return render_no_access if !has_access(@rpc, true)
|
68
|
+
input = read_body
|
69
|
+
|
70
|
+
@res = Rack::Response.new
|
71
|
+
@res.status = 200
|
72
|
+
@res["Content-Type"] = "application/x-git-%s-result" % @rpc
|
73
|
+
@res["Transfer-Encoding"] = "chunked"
|
74
|
+
@res["Cache-Control"] = "no-cache"
|
75
|
+
|
76
|
+
@res.finish do
|
77
|
+
command = git_command(%W(#{@rpc} --stateless-rpc #{@dir}))
|
78
|
+
IO.popen(popen_env, command, File::RDWR, popen_options) do |pipe|
|
79
|
+
pipe.write(input)
|
80
|
+
pipe.close_write
|
81
|
+
while !pipe.eof?
|
82
|
+
block = pipe.read(8192) # 8KB at a time
|
83
|
+
@res.write encode_chunk(block) # stream it to the client
|
84
|
+
end
|
85
|
+
@res.write terminating_chunk
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def encode_chunk(chunk)
|
91
|
+
size_in_hex = chunk.size.to_s(16)
|
92
|
+
[ size_in_hex, CRLF, chunk, CRLF ].join
|
93
|
+
end
|
94
|
+
|
95
|
+
def terminating_chunk
|
96
|
+
[ 0, CRLF, CRLF ].join
|
97
|
+
end
|
98
|
+
|
99
|
+
def get_info_refs
|
100
|
+
service_name = get_service_type
|
101
|
+
|
102
|
+
if has_access(service_name)
|
103
|
+
cmd = git_command(%W(#{service_name} --stateless-rpc --advertise-refs #{@dir}))
|
104
|
+
refs = capture(cmd)
|
105
|
+
|
106
|
+
@res = Rack::Response.new
|
107
|
+
@res.status = 200
|
108
|
+
@res["Content-Type"] = "application/x-git-%s-advertisement" % service_name
|
109
|
+
hdr_nocache
|
110
|
+
@res.write(pkt_write("# service=git-#{service_name}\n"))
|
111
|
+
@res.write(pkt_flush)
|
112
|
+
@res.write(refs)
|
113
|
+
@res.finish
|
114
|
+
else
|
115
|
+
dumb_info_refs
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def dumb_info_refs
|
120
|
+
update_server_info
|
121
|
+
send_file(@reqfile, "text/plain; charset=utf-8") do
|
122
|
+
hdr_nocache
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def get_info_packs
|
127
|
+
# objects/info/packs
|
128
|
+
send_file(@reqfile, "text/plain; charset=utf-8") do
|
129
|
+
hdr_nocache
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def get_loose_object
|
134
|
+
send_file(@reqfile, "application/x-git-loose-object") do
|
135
|
+
hdr_cache_forever
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def get_pack_file
|
140
|
+
send_file(@reqfile, "application/x-git-packed-objects") do
|
141
|
+
hdr_cache_forever
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def get_idx_file
|
146
|
+
send_file(@reqfile, "application/x-git-packed-objects-toc") do
|
147
|
+
hdr_cache_forever
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def get_text_file
|
152
|
+
send_file(@reqfile, "text/plain") do
|
153
|
+
hdr_nocache
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# ------------------------
|
158
|
+
# logic helping functions
|
159
|
+
# ------------------------
|
160
|
+
|
161
|
+
F = ::File
|
162
|
+
|
163
|
+
# some of this borrowed from the Rack::File implementation
|
164
|
+
def send_file(reqfile, content_type)
|
165
|
+
reqfile = File.join(@dir, reqfile)
|
166
|
+
return render_not_found if !F.exists?(reqfile)
|
167
|
+
|
168
|
+
if reqfile == File.realpath(reqfile)
|
169
|
+
# reqfile looks legit: no path traversal, no leading '|'
|
170
|
+
else
|
171
|
+
# reqfile does not look trustworthy; abort
|
172
|
+
return render_not_found
|
173
|
+
end
|
174
|
+
|
175
|
+
@res = Rack::Response.new
|
176
|
+
@res.status = 200
|
177
|
+
@res["Content-Type"] = content_type
|
178
|
+
@res["Last-Modified"] = F.mtime(reqfile).httpdate
|
179
|
+
|
180
|
+
yield
|
181
|
+
|
182
|
+
if size = F.size?(reqfile)
|
183
|
+
@res["Content-Length"] = size.to_s
|
184
|
+
@res.finish do
|
185
|
+
F.open(reqfile, "rb") do |file|
|
186
|
+
while part = file.read(8192)
|
187
|
+
@res.write part
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
else
|
192
|
+
body = [F.read(reqfile)]
|
193
|
+
size = Rack::Utils.bytesize(body.first)
|
194
|
+
@res["Content-Length"] = size
|
195
|
+
@res.write body
|
196
|
+
@res.finish
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def get_git_dir(path)
|
201
|
+
root = @config[:project_root] || Dir.pwd
|
202
|
+
path = File.join(root, path)
|
203
|
+
if !File.exists?(path)
|
204
|
+
false
|
205
|
+
elsif File.realpath(path) != path # looks like path traversal
|
206
|
+
false
|
207
|
+
else
|
208
|
+
path # TODO: check is a valid git directory
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def get_service_type
|
213
|
+
service_type = @req.params['service']
|
214
|
+
return false if !service_type
|
215
|
+
return false if service_type[0, 4] != 'git-'
|
216
|
+
service_type.gsub('git-', '')
|
217
|
+
end
|
218
|
+
|
219
|
+
def match_routing
|
220
|
+
cmd = nil
|
221
|
+
path = nil
|
222
|
+
SERVICES.each do |method, handler, match, rpc|
|
223
|
+
if m = Regexp.new(match).match(@req.path_info)
|
224
|
+
return ['not_allowed'] if method != @req.request_method
|
225
|
+
cmd = handler
|
226
|
+
path = m[1]
|
227
|
+
file = @req.path_info.sub(path + '/', '')
|
228
|
+
return [cmd, path, file, rpc]
|
229
|
+
end
|
230
|
+
end
|
231
|
+
return nil
|
232
|
+
end
|
233
|
+
|
234
|
+
def has_access(rpc, check_content_type = false)
|
235
|
+
if check_content_type
|
236
|
+
return false if @req.content_type != "application/x-git-%s-request" % rpc
|
237
|
+
end
|
238
|
+
return false if !['upload-pack', 'receive-pack'].include? rpc
|
239
|
+
if rpc == 'receive-pack'
|
240
|
+
return @config[:receive_pack] if @config.include? :receive_pack
|
241
|
+
end
|
242
|
+
if rpc == 'upload-pack'
|
243
|
+
return @config[:upload_pack] if @config.include? :upload_pack
|
244
|
+
end
|
245
|
+
return get_config_setting(rpc)
|
246
|
+
end
|
247
|
+
|
248
|
+
def get_config_setting(service_name)
|
249
|
+
service_name = service_name.gsub('-', '')
|
250
|
+
setting = get_git_config("http.#{service_name}")
|
251
|
+
if service_name == 'uploadpack'
|
252
|
+
return setting != 'false'
|
253
|
+
else
|
254
|
+
return setting == 'true'
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
def get_git_config(config_name)
|
259
|
+
cmd = git_command(%W(config #{config_name}))
|
260
|
+
capture(cmd).chomp
|
261
|
+
end
|
262
|
+
|
263
|
+
def read_body
|
264
|
+
if @env["HTTP_CONTENT_ENCODING"] =~ /gzip/
|
265
|
+
input = Zlib::GzipReader.new(@req.body).read
|
266
|
+
else
|
267
|
+
input = @req.body.read
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
def update_server_info
|
272
|
+
cmd = git_command(%W(update-server-info))
|
273
|
+
capture(cmd)
|
274
|
+
end
|
275
|
+
|
276
|
+
def git_command(command)
|
277
|
+
[@config[:git_path] || 'git'] + command
|
278
|
+
end
|
279
|
+
|
280
|
+
def capture(command)
|
281
|
+
IO.popen(popen_env, command, popen_options) { |p| p.read }
|
282
|
+
end
|
283
|
+
|
284
|
+
def popen_options
|
285
|
+
{chdir: @dir, unsetenv_others: true}
|
286
|
+
end
|
287
|
+
|
288
|
+
def popen_env
|
289
|
+
{'PATH' => ENV['PATH'], 'GL_ID' => ENV['GL_ID']}
|
290
|
+
end
|
291
|
+
|
292
|
+
# --------------------------------------
|
293
|
+
# HTTP error response handling functions
|
294
|
+
# --------------------------------------
|
295
|
+
|
296
|
+
PLAIN_TYPE = {"Content-Type" => "text/plain"}
|
297
|
+
|
298
|
+
def render_method_not_allowed
|
299
|
+
if @env['SERVER_PROTOCOL'] == "HTTP/1.1"
|
300
|
+
[405, PLAIN_TYPE, ["Method Not Allowed"]]
|
301
|
+
else
|
302
|
+
[400, PLAIN_TYPE, ["Bad Request"]]
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
def render_not_found
|
307
|
+
[404, PLAIN_TYPE, ["Not Found"]]
|
308
|
+
end
|
309
|
+
|
310
|
+
def render_no_access
|
311
|
+
[403, PLAIN_TYPE, ["Forbidden"]]
|
312
|
+
end
|
313
|
+
|
314
|
+
|
315
|
+
# ------------------------------
|
316
|
+
# packet-line handling functions
|
317
|
+
# ------------------------------
|
318
|
+
|
319
|
+
def pkt_flush
|
320
|
+
'0000'
|
321
|
+
end
|
322
|
+
|
323
|
+
def pkt_write(str)
|
324
|
+
(str.size + 4).to_s(base=16).rjust(4, '0') + str
|
325
|
+
end
|
326
|
+
|
327
|
+
|
328
|
+
# ------------------------
|
329
|
+
# header writing functions
|
330
|
+
# ------------------------
|
331
|
+
|
332
|
+
def hdr_nocache
|
333
|
+
@res["Expires"] = "Fri, 01 Jan 1980 00:00:00 GMT"
|
334
|
+
@res["Pragma"] = "no-cache"
|
335
|
+
@res["Cache-Control"] = "no-cache, max-age=0, must-revalidate"
|
336
|
+
end
|
337
|
+
|
338
|
+
def hdr_cache_forever
|
339
|
+
now = Time.now().to_i
|
340
|
+
@res["Date"] = now.to_s
|
341
|
+
@res["Expires"] = (now + 31536000).to_s;
|
342
|
+
@res["Cache-Control"] = "public, max-age=31536000";
|
343
|
+
end
|
344
|
+
|
345
|
+
end
|
346
|
+
end
|