56d50b0d1fe0734349e33f1079cf95997b09d685
[gitosis.git] / gitosis / test / test_serve.py
1 from nose.tools import eq_ as eq
2 from gitosis.test.util import assert_raises
3
4 import logging
5 import os
6 from cStringIO import StringIO
7 from ConfigParser import RawConfigParser
8
9 from gitosis import serve
10 from gitosis import repository
11
12 from gitosis.test import util
13
14 def test_bad_newLine():
15     cfg = RawConfigParser()
16     e = assert_raises(
17         serve.CommandMayNotContainNewlineError,
18         serve.serve,
19         cfg=cfg,
20         user='jdoe',
21         command='ev\nil',
22         )
23     eq(str(e), 'Command may not contain newline')
24     assert isinstance(e, serve.ServingError)
25
26 def test_bad_nospace():
27     cfg = RawConfigParser()
28     e = assert_raises(
29         serve.UnknownCommandError,
30         serve.serve,
31         cfg=cfg,
32         user='jdoe',
33         command='git-upload-pack',
34         )
35     eq(str(e), 'Unknown command denied')
36     assert isinstance(e, serve.ServingError)
37
38 def test_bad_command():
39     cfg = RawConfigParser()
40     e = assert_raises(
41         serve.UnknownCommandError,
42         serve.serve,
43         cfg=cfg,
44         user='jdoe',
45         command="evil 'foo'",
46         )
47     eq(str(e), 'Unknown command denied')
48     assert isinstance(e, serve.ServingError)
49
50 def test_bad_unsafeArguments_notQuoted():
51     cfg = RawConfigParser()
52     e = assert_raises(
53         serve.UnsafeArgumentsError,
54         serve.serve,
55         cfg=cfg,
56         user='jdoe',
57         command="git-upload-pack foo",
58         )
59     eq(str(e), 'Arguments to command look dangerous')
60     assert isinstance(e, serve.ServingError)
61
62 def test_bad_unsafeArguments_badCharacters():
63     cfg = RawConfigParser()
64     e = assert_raises(
65         serve.UnsafeArgumentsError,
66         serve.serve,
67         cfg=cfg,
68         user='jdoe',
69         command="git-upload-pack 'ev!l'",
70         )
71     eq(str(e), 'Arguments to command look dangerous')
72     assert isinstance(e, serve.ServingError)
73
74 def test_bad_unsafeArguments_dotdot():
75     cfg = RawConfigParser()
76     e = assert_raises(
77         serve.UnsafeArgumentsError,
78         serve.serve,
79         cfg=cfg,
80         user='jdoe',
81         command="git-upload-pack 'something/../evil'",
82         )
83     eq(str(e), 'Arguments to command look dangerous')
84     assert isinstance(e, serve.ServingError)
85
86 def test_bad_forbiddenCommand_read():
87     cfg = RawConfigParser()
88     e = assert_raises(
89         serve.ReadAccessDenied,
90         serve.serve,
91         cfg=cfg,
92         user='jdoe',
93         command="git-upload-pack 'foo'",
94         )
95     eq(str(e), 'Repository read access denied')
96     assert isinstance(e, serve.AccessDenied)
97     assert isinstance(e, serve.ServingError)
98
99 def test_bad_forbiddenCommand_write_noAccess():
100     cfg = RawConfigParser()
101     e = assert_raises(
102         serve.ReadAccessDenied,
103         serve.serve,
104         cfg=cfg,
105         user='jdoe',
106         command="git-receive-pack 'foo'",
107         )
108     # error message talks about read in an effort to make it more
109     # obvious that jdoe doesn't have *even* read access
110     eq(str(e), 'Repository read access denied')
111     assert isinstance(e, serve.AccessDenied)
112     assert isinstance(e, serve.ServingError)
113
114 def test_bad_forbiddenCommand_write_readAccess():
115     cfg = RawConfigParser()
116     cfg.add_section('group foo')
117     cfg.set('group foo', 'members', 'jdoe')
118     cfg.set('group foo', 'readonly', 'foo')
119     e = assert_raises(
120         serve.WriteAccessDenied,
121         serve.serve,
122         cfg=cfg,
123         user='jdoe',
124         command="git-receive-pack 'foo'",
125         )
126     eq(str(e), 'Repository write access denied')
127     assert isinstance(e, serve.AccessDenied)
128     assert isinstance(e, serve.ServingError)
129
130 def test_simple_read():
131     tmp = util.maketemp()
132     repository.init(os.path.join(tmp, 'foo.git'))
133     cfg = RawConfigParser()
134     cfg.add_section('gitosis')
135     cfg.set('gitosis', 'repositories', tmp)
136     cfg.add_section('group foo')
137     cfg.set('group foo', 'members', 'jdoe')
138     cfg.set('group foo', 'readonly', 'foo')
139     got = serve.serve(
140         cfg=cfg,
141         user='jdoe',
142         command="git-upload-pack 'foo'",
143         )
144     eq(got, "git-upload-pack '%s/foo.git'" % tmp)
145
146 def test_simple_write():
147     tmp = util.maketemp()
148     repository.init(os.path.join(tmp, 'foo.git'))
149     cfg = RawConfigParser()
150     cfg.add_section('gitosis')
151     cfg.set('gitosis', 'repositories', tmp)
152     cfg.add_section('group foo')
153     cfg.set('group foo', 'members', 'jdoe')
154     cfg.set('group foo', 'writable', 'foo')
155     got = serve.serve(
156         cfg=cfg,
157         user='jdoe',
158         command="git-receive-pack 'foo'",
159         )
160     eq(got, "git-receive-pack '%s/foo.git'" % tmp)
161
162 def test_push_inits_if_needed():
163     # a push to a non-existent repository (but where config authorizes
164     # you to do that) will create the repository on the fly
165     tmp = util.maketemp()
166     cfg = RawConfigParser()
167     cfg.add_section('gitosis')
168     repositories = os.path.join(tmp, 'repositories')
169     os.mkdir(repositories)
170     cfg.set('gitosis', 'repositories', repositories)
171     generated = os.path.join(tmp, 'generated')
172     os.mkdir(generated)
173     cfg.set('gitosis', 'generate-files-in', generated)
174     cfg.add_section('group foo')
175     cfg.set('group foo', 'members', 'jdoe')
176     cfg.set('group foo', 'writable', 'foo')
177     serve.serve(
178         cfg=cfg,
179         user='jdoe',
180         command="git-receive-pack 'foo'",
181         )
182     eq(os.listdir(repositories), ['foo.git'])
183     assert os.path.isfile(os.path.join(repositories, 'foo.git', 'HEAD'))
184
185 def test_push_inits_if_needed_haveExtension():
186     # a push to a non-existent repository (but where config authorizes
187     # you to do that) will create the repository on the fly
188     tmp = util.maketemp()
189     cfg = RawConfigParser()
190     cfg.add_section('gitosis')
191     repositories = os.path.join(tmp, 'repositories')
192     os.mkdir(repositories)
193     cfg.set('gitosis', 'repositories', repositories)
194     generated = os.path.join(tmp, 'generated')
195     os.mkdir(generated)
196     cfg.set('gitosis', 'generate-files-in', generated)
197     cfg.add_section('group foo')
198     cfg.set('group foo', 'members', 'jdoe')
199     cfg.set('group foo', 'writable', 'foo')
200     serve.serve(
201         cfg=cfg,
202         user='jdoe',
203         command="git-receive-pack 'foo.git'",
204         )
205     eq(os.listdir(repositories), ['foo.git'])
206     assert os.path.isfile(os.path.join(repositories, 'foo.git', 'HEAD'))
207
208 def test_push_inits_subdir_parent_missing():
209     tmp = util.maketemp()
210     cfg = RawConfigParser()
211     cfg.add_section('gitosis')
212     repositories = os.path.join(tmp, 'repositories')
213     os.mkdir(repositories)
214     cfg.set('gitosis', 'repositories', repositories)
215     generated = os.path.join(tmp, 'generated')
216     os.mkdir(generated)
217     cfg.set('gitosis', 'generate-files-in', generated)
218     cfg.add_section('group foo')
219     cfg.set('group foo', 'members', 'jdoe')
220     cfg.set('group foo', 'writable', 'foo/bar')
221     serve.serve(
222         cfg=cfg,
223         user='jdoe',
224         command="git-receive-pack 'foo/bar.git'",
225         )
226     eq(os.listdir(repositories), ['foo'])
227     foo = os.path.join(repositories, 'foo')
228     util.check_mode(foo, 0750, is_dir=True)
229     eq(os.listdir(foo), ['bar.git'])
230     assert os.path.isfile(os.path.join(repositories, 'foo', 'bar.git', 'HEAD'))
231
232 def test_push_inits_subdir_parent_exists():
233     tmp = util.maketemp()
234     cfg = RawConfigParser()
235     cfg.add_section('gitosis')
236     repositories = os.path.join(tmp, 'repositories')
237     os.mkdir(repositories)
238     foo = os.path.join(repositories, 'foo')
239     # silly mode on purpose; not to be touched
240     os.mkdir(foo, 0751)
241     cfg.set('gitosis', 'repositories', repositories)
242     generated = os.path.join(tmp, 'generated')
243     os.mkdir(generated)
244     cfg.set('gitosis', 'generate-files-in', generated)
245     cfg.add_section('group foo')
246     cfg.set('group foo', 'members', 'jdoe')
247     cfg.set('group foo', 'writable', 'foo/bar')
248     serve.serve(
249         cfg=cfg,
250         user='jdoe',
251         command="git-receive-pack 'foo/bar.git'",
252         )
253     eq(os.listdir(repositories), ['foo'])
254     util.check_mode(foo, 0751, is_dir=True)
255     eq(os.listdir(foo), ['bar.git'])
256     assert os.path.isfile(os.path.join(repositories, 'foo', 'bar.git', 'HEAD'))
257
258 def test_push_inits_if_needed_existsWithExtension():
259     tmp = util.maketemp()
260     cfg = RawConfigParser()
261     cfg.add_section('gitosis')
262     repositories = os.path.join(tmp, 'repositories')
263     os.mkdir(repositories)
264     cfg.set('gitosis', 'repositories', repositories)
265     cfg.add_section('group foo')
266     cfg.set('group foo', 'members', 'jdoe')
267     cfg.set('group foo', 'writable', 'foo')
268     os.mkdir(os.path.join(repositories, 'foo.git'))
269     serve.serve(
270         cfg=cfg,
271         user='jdoe',
272         command="git-receive-pack 'foo'",
273         )
274     eq(os.listdir(repositories), ['foo.git'])
275     # it should *not* have HEAD here as we just mkdirred it and didn't
276     # create it properly, and the mock repo didn't have anything in
277     # it.. having HEAD implies serve ran git init, which is supposed
278     # to be unnecessary here
279     eq(os.listdir(os.path.join(repositories, 'foo.git')), [])
280
281 def test_push_inits_no_stdout_spam():
282     # git init has a tendency to spew to stdout, and that confuses
283     # e.g. a git push
284     tmp = util.maketemp()
285     cfg = RawConfigParser()
286     cfg.add_section('gitosis')
287     repositories = os.path.join(tmp, 'repositories')
288     os.mkdir(repositories)
289     cfg.set('gitosis', 'repositories', repositories)
290     generated = os.path.join(tmp, 'generated')
291     os.mkdir(generated)
292     cfg.set('gitosis', 'generate-files-in', generated)
293     cfg.add_section('group foo')
294     cfg.set('group foo', 'members', 'jdoe')
295     cfg.set('group foo', 'writable', 'foo')
296     old_stdout = os.dup(1)
297     try:
298         new_stdout = os.tmpfile()
299         os.dup2(new_stdout.fileno(), 1)
300         serve.serve(
301             cfg=cfg,
302             user='jdoe',
303             command="git-receive-pack 'foo'",
304             )
305     finally:
306         os.dup2(old_stdout, 1)
307         os.close(old_stdout)
308     new_stdout.seek(0)
309     got = new_stdout.read()
310     new_stdout.close()
311     eq(got, '')
312     eq(os.listdir(repositories), ['foo.git'])
313     assert os.path.isfile(os.path.join(repositories, 'foo.git', 'HEAD'))
314
315 def test_push_inits_sets_description():
316     tmp = util.maketemp()
317     cfg = RawConfigParser()
318     cfg.add_section('gitosis')
319     repositories = os.path.join(tmp, 'repositories')
320     os.mkdir(repositories)
321     cfg.set('gitosis', 'repositories', repositories)
322     generated = os.path.join(tmp, 'generated')
323     os.mkdir(generated)
324     cfg.set('gitosis', 'generate-files-in', generated)
325     cfg.add_section('group foo')
326     cfg.set('group foo', 'members', 'jdoe')
327     cfg.set('group foo', 'writable', 'foo')
328     cfg.add_section('repo foo')
329     cfg.set('repo foo', 'description', 'foodesc')
330     serve.serve(
331         cfg=cfg,
332         user='jdoe',
333         command="git-receive-pack 'foo'",
334         )
335     eq(os.listdir(repositories), ['foo.git'])
336     path = os.path.join(repositories, 'foo.git', 'description')
337     assert os.path.exists(path)
338     got = util.readFile(path)
339     eq(got, 'foodesc\n')
340
341 def test_push_inits_updates_projects_list():
342     tmp = util.maketemp()
343     cfg = RawConfigParser()
344     cfg.add_section('gitosis')
345     repositories = os.path.join(tmp, 'repositories')
346     os.mkdir(repositories)
347     cfg.set('gitosis', 'repositories', repositories)
348     generated = os.path.join(tmp, 'generated')
349     os.mkdir(generated)
350     cfg.set('gitosis', 'generate-files-in', generated)
351     cfg.add_section('group foo')
352     cfg.set('group foo', 'members', 'jdoe')
353     cfg.set('group foo', 'writable', 'foo')
354     cfg.add_section('repo foo')
355     cfg.set('repo foo', 'gitweb', 'yes')
356     os.mkdir(os.path.join(repositories, 'gitosis-admin.git'))
357     serve.serve(
358         cfg=cfg,
359         user='jdoe',
360         command="git-receive-pack 'foo'",
361         )
362     eq(
363         sorted(os.listdir(repositories)),
364         sorted(['foo.git', 'gitosis-admin.git']),
365         )
366     path = os.path.join(generated, 'projects.list')
367     assert os.path.exists(path)
368     got = util.readFile(path)
369     eq(got, 'foo.git\n')
370
371 def test_push_inits_sets_export_ok():
372     tmp = util.maketemp()
373     cfg = RawConfigParser()
374     cfg.add_section('gitosis')
375     repositories = os.path.join(tmp, 'repositories')
376     os.mkdir(repositories)
377     cfg.set('gitosis', 'repositories', repositories)
378     generated = os.path.join(tmp, 'generated')
379     os.mkdir(generated)
380     cfg.set('gitosis', 'generate-files-in', generated)
381     cfg.add_section('group foo')
382     cfg.set('group foo', 'members', 'jdoe')
383     cfg.set('group foo', 'writable', 'foo')
384     cfg.add_section('repo foo')
385     cfg.set('repo foo', 'daemon', 'yes')
386     serve.serve(
387         cfg=cfg,
388         user='jdoe',
389         command="git-receive-pack 'foo'",
390         )
391     eq(os.listdir(repositories), ['foo.git'])
392     path = os.path.join(repositories, 'foo.git', 'git-daemon-export-ok')
393     assert os.path.exists(path)
394
395 def test_absolute():
396     # as the only convenient way to use non-standard SSH ports with
397     # git is via the ssh://user@host:port/path syntax, and that syntax
398     # forces absolute urls, just force convert absolute paths to
399     # relative paths; you'll never really want absolute paths via
400     # gitosis, anyway.
401     tmp = util.maketemp()
402     repository.init(os.path.join(tmp, 'foo.git'))
403     cfg = RawConfigParser()
404     cfg.add_section('gitosis')
405     cfg.set('gitosis', 'repositories', tmp)
406     cfg.add_section('group foo')
407     cfg.set('group foo', 'members', 'jdoe')
408     cfg.set('group foo', 'readonly', 'foo')
409     got = serve.serve(
410         cfg=cfg,
411         user='jdoe',
412         command="git-upload-pack '/foo'",
413         )
414     eq(got, "git-upload-pack '%s/foo.git'" % tmp)
415
416 def test_typo_writeable():
417     tmp = util.maketemp()
418     repository.init(os.path.join(tmp, 'foo.git'))
419     cfg = RawConfigParser()
420     cfg.add_section('gitosis')
421     cfg.set('gitosis', 'repositories', tmp)
422     cfg.add_section('group foo')
423     cfg.set('group foo', 'members', 'jdoe')
424     cfg.set('group foo', 'writeable', 'foo')
425     log = logging.getLogger('gitosis.serve')
426     buf = StringIO()
427     handler = logging.StreamHandler(buf)
428     log.addHandler(handler)
429     try:
430         got = serve.serve(
431             cfg=cfg,
432             user='jdoe',
433             command="git-receive-pack 'foo'",
434             )
435     finally:
436         log.removeHandler(handler)
437     eq(got, "git-receive-pack '%s/foo.git'" % tmp)
438     handler.flush()
439     eq(
440         buf.getvalue(),
441         "Repository 'foo' config has typo \"writeable\", shou"
442         +"ld be \"writable\"\n",
443         )