From 13c89cdb7d493c43659c7f202c6cc3f81ea4c1e8 Mon Sep 17 00:00:00 2001 From: Tommi Virtanen Date: Sat, 17 Nov 2007 16:27:21 +0200 Subject: Manage git-daemon-export-ok flags from gitosis config. --- TODO.rst | 3 +- example.conf | 6 +- gitosis/gitdaemon.py | 90 ++++++++++++++++++++++++ gitosis/run_hook.py | 4 ++ gitosis/test/test_gitdaemon.py | 155 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 253 insertions(+), 5 deletions(-) create mode 100644 gitosis/gitdaemon.py create mode 100644 gitosis/test/test_gitdaemon.py diff --git a/TODO.rst b/TODO.rst index ccf86d1..976105a 100644 --- a/TODO.rst +++ b/TODO.rst @@ -6,7 +6,8 @@ - gitosis-lint: check that the user account (e.g. ``git``) looks valid -- git-daemon-export-ok +- create git-daemon-export-ok, description, projects.list etc when + autocreating repositorites? - guard against *.pub files named -foo.pub or foo;bar.pub diff --git a/example.conf b/example.conf index 117be5c..411a647 100644 --- a/example.conf +++ b/example.conf @@ -13,8 +13,7 @@ gitweb = no ## Allow git-daemon to publish all known repositories. As with gitweb, ## this can be done globally or per-repository. -## NOT YET IMPLEMENTED. -# daemon-ok = no +daemon = no ## Logging level, one of DEBUG, INFO, WARNING, ERROR, CRITICAL loglevel = DEBUG @@ -41,8 +40,7 @@ gitweb = yes owner = John Doe ## Allow git-daemon to publish this repository. -## NOT YET IMPLEMENTED. -# daemon-ok = no +daemon = yes [gitweb] ## Where to make gitweb link to as it's "home location". diff --git a/gitosis/gitdaemon.py b/gitosis/gitdaemon.py new file mode 100644 index 0000000..78ca9ea --- /dev/null +++ b/gitosis/gitdaemon.py @@ -0,0 +1,90 @@ +import errno +import logging +import os + +from ConfigParser import NoSectionError, NoOptionError + +log = logging.getLogger('gitosis.gitdaemon') + +from gitosis import util + +def export_ok_path(repopath): + p = os.path.join(repopath, 'git-daemon-export-ok') + return p + +def allow_export(repopath): + p = export_ok_path(repopath) + file(p, 'a').close() + +def deny_export(repopath): + p = export_ok_path(repopath) + try: + os.unlink(p) + except OSError, e: + if e.errno == errno.ENOENT: + pass + else: + raise + +def _extract_reldir(topdir, dirpath): + if topdir == dirpath: + return '.' + prefix = topdir + '/' + assert dirpath.startswith(prefix) + reldir = dirpath[len(prefix):] + return reldir + +def set_export_ok(config): + repositories = util.getRepositoryDir(config) + + try: + global_enable = config.getboolean('gitosis', 'daemon') + except (NoSectionError, NoOptionError): + global_enable = False + log.debug( + 'Global default is %r', + {True: 'allow', False: 'deny'}.get(global_enable), + ) + + def _error(e): + if e.errno == errno.ENOENT: + pass + else: + raise e + + for (dirpath, dirnames, filenames) \ + in os.walk(repositories, onerror=_error): + # oh how many times i have wished for os.walk to report + # topdir and reldir separately, instead of dirpath + reldir = _extract_reldir( + topdir=repositories, + dirpath=dirpath, + ) + + log.debug('Walking %r, seeing %r', reldir, dirnames) + + to_recurse = [] + repos = [] + for dirname in dirnames: + if dirname.endswith('.git'): + repos.append(dirname) + else: + to_recurse.append(dirname) + dirnames[:] = to_recurse + + for repo in repos: + name, ext = os.path.splitext(repo) + if reldir != '.': + name = os.path.join(reldir, name) + assert ext == '.git' + try: + enable = config.getboolean('repo %s' % name, 'daemon') + except (NoSectionError, NoOptionError): + enable = global_enable + + if enable: + log.debug('Allow %r', name) + allow_export(os.path.join(dirpath, repo)) + else: + log.debug('Deny %r', name) + deny_export(os.path.join(dirpath, repo)) diff --git a/gitosis/run_hook.py b/gitosis/run_hook.py index d5dd4ab..a5ed9d0 100644 --- a/gitosis/run_hook.py +++ b/gitosis/run_hook.py @@ -11,6 +11,7 @@ import shutil from gitosis import repository from gitosis import ssh from gitosis import gitweb +from gitosis import gitdaemon from gitosis import app def post_update(cfg, git_dir): @@ -31,6 +32,9 @@ def post_update(cfg, git_dir): config=cfg, path=os.path.join(git_dir, 'projects.list'), ) + gitdaemon.set_export_ok( + config=cfg, + ) ssh.writeAuthorizedKeys( path=os.path.expanduser('~/.ssh/authorized_keys'), keydir=os.path.join(export, 'keydir'), diff --git a/gitosis/test/test_gitdaemon.py b/gitosis/test/test_gitdaemon.py new file mode 100644 index 0000000..94475ac --- /dev/null +++ b/gitosis/test/test_gitdaemon.py @@ -0,0 +1,155 @@ +from nose.tools import eq_ as eq + +import os +from ConfigParser import RawConfigParser + +from gitosis import gitdaemon +from gitosis.test.util import maketemp, writeFile + +def exported(path): + assert os.path.isdir(path) + p = gitdaemon.export_ok_path(path) + return os.path.exists(p) + +def test_git_daemon_export_ok_repo_missing(): + # configured but not created yet; before first push + tmp = maketemp() + cfg = RawConfigParser() + cfg.add_section('gitosis') + cfg.set('gitosis', 'repositories', tmp) + cfg.add_section('repo foo') + cfg.set('repo foo', 'daemon', 'yes') + gitdaemon.set_export_ok(config=cfg) + assert not os.path.exists(os.path.join(tmp, 'foo')) + assert not os.path.exists(os.path.join(tmp, 'foo.git')) + +def test_git_daemon_export_ok_repo_missing_parent(): + # configured but not created yet; before first push + tmp = maketemp() + cfg = RawConfigParser() + cfg.add_section('gitosis') + cfg.set('gitosis', 'repositories', tmp) + cfg.add_section('repo foo/bar') + cfg.set('repo foo/bar', 'daemon', 'yes') + gitdaemon.set_export_ok(config=cfg) + assert not os.path.exists(os.path.join(tmp, 'foo')) + +def test_git_daemon_export_ok_allowed(): + tmp = maketemp() + path = os.path.join(tmp, 'foo.git') + os.mkdir(path) + cfg = RawConfigParser() + cfg.add_section('gitosis') + cfg.set('gitosis', 'repositories', tmp) + cfg.add_section('repo foo') + cfg.set('repo foo', 'daemon', 'yes') + gitdaemon.set_export_ok(config=cfg) + eq(exported(path), True) + +def test_git_daemon_export_ok_allowed_already(): + tmp = maketemp() + path = os.path.join(tmp, 'foo.git') + os.mkdir(path) + writeFile(gitdaemon.export_ok_path(path), '') + cfg = RawConfigParser() + cfg.add_section('gitosis') + cfg.set('gitosis', 'repositories', tmp) + cfg.add_section('repo foo') + cfg.set('repo foo', 'daemon', 'yes') + gitdaemon.set_export_ok(config=cfg) + eq(exported(path), True) + +def test_git_daemon_export_ok_denied(): + tmp = maketemp() + path = os.path.join(tmp, 'foo.git') + os.mkdir(path) + writeFile(gitdaemon.export_ok_path(path), '') + cfg = RawConfigParser() + cfg.add_section('gitosis') + cfg.set('gitosis', 'repositories', tmp) + cfg.add_section('repo foo') + cfg.set('repo foo', 'daemon', 'no') + gitdaemon.set_export_ok(config=cfg) + eq(exported(path), False) + +def test_git_daemon_export_ok_denied_already(): + tmp = maketemp() + path = os.path.join(tmp, 'foo.git') + os.mkdir(path) + cfg = RawConfigParser() + cfg.add_section('gitosis') + cfg.set('gitosis', 'repositories', tmp) + cfg.add_section('repo foo') + cfg.set('repo foo', 'daemon', 'no') + gitdaemon.set_export_ok(config=cfg) + eq(exported(path), False) + +def test_git_daemon_export_ok_subdirs(): + tmp = maketemp() + foo = os.path.join(tmp, 'foo') + os.mkdir(foo) + path = os.path.join(foo, 'bar.git') + os.mkdir(path) + cfg = RawConfigParser() + cfg.add_section('gitosis') + cfg.set('gitosis', 'repositories', tmp) + cfg.add_section('repo foo/bar') + cfg.set('repo foo/bar', 'daemon', 'yes') + gitdaemon.set_export_ok(config=cfg) + eq(exported(path), True) + +def test_git_daemon_export_ok_denied_default(): + tmp = maketemp() + path = os.path.join(tmp, 'foo.git') + os.mkdir(path) + writeFile(gitdaemon.export_ok_path(path), '') + cfg = RawConfigParser() + cfg.add_section('gitosis') + cfg.set('gitosis', 'repositories', tmp) + cfg.add_section('repo foo') + gitdaemon.set_export_ok(config=cfg) + eq(exported(path), False) + +def test_git_daemon_export_ok_denied_even_not_configured(): + # repositories not mentioned in config also get touched; this is + # to avoid security trouble, otherwise we might expose (or + # continue to expose) old repositories removed from config + tmp = maketemp() + path = os.path.join(tmp, 'foo.git') + os.mkdir(path) + writeFile(gitdaemon.export_ok_path(path), '') + cfg = RawConfigParser() + cfg.add_section('gitosis') + cfg.set('gitosis', 'repositories', tmp) + gitdaemon.set_export_ok(config=cfg) + eq(exported(path), False) + +def test_git_daemon_export_ok_allowed_global(): + tmp = maketemp() + + for repo in [ + 'foo.git', + 'quux.git', + 'thud.git', + ]: + path = os.path.join(tmp, repo) + os.mkdir(path) + + # try to provoke an invalid allow + writeFile(gitdaemon.export_ok_path(os.path.join(tmp, 'thud.git')), '') + + cfg = RawConfigParser() + cfg.add_section('gitosis') + cfg.set('gitosis', 'repositories', tmp) + cfg.set('gitosis', 'daemon', 'yes') + cfg.add_section('repo foo') + cfg.add_section('repo quux') + # same as default, no effect + cfg.set('repo quux', 'daemon', 'yes') + cfg.add_section('repo thud') + # this is still hidden + cfg.set('repo thud', 'daemon', 'no') + gitdaemon.set_export_ok(config=cfg) + eq(exported(os.path.join(tmp, 'foo.git')), True) + eq(exported(os.path.join(tmp, 'quux.git')), True) + eq(exported(os.path.join(tmp, 'thud.git')), False) -- cgit v1.2.3