diff --git a/.gitignore b/.gitignore index dc2c237..9593815 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ /apidocs /gitosis/test/tmp /.coverage +.idea +venv \ No newline at end of file diff --git a/README.rst b/README.rst index 65d58f8..3266bfb 100644 --- a/README.rst +++ b/README.rst @@ -24,7 +24,17 @@ more information. You can get ``gitosis`` via ``git`` by saying:: - git clone https://github.com/tv42/gitosis.git + This repositories are from jakob@schuerz.at, support python3 and ssh-certificates + git clone git@codeberg.org:xundeenergie/gitosis.git (fetch) + git clone git@github.com:xundeenergie/gitosis.git (fetch) + git clone git@git.schuerz.at:public/gitosis.git (fetch) + + This repository translates gitosis to python3, but not fully. + git clone git@github.com:mgukov/gitosis.git (push) + + Original repository seems unmaintained + git clone git@github.com:tv42/gitosis.git (fetch) + And install it via:: @@ -75,6 +85,12 @@ it to running ``gitosis-serve``. Run:: sudo -H -u git gitosis-init >fp, line + line = ' '.join([urllib.parse.quote_plus(s) for s in response]) + #print >>fp, line + print(line, end="", file=fp) def generate_project_list(config, path): """ @@ -106,7 +107,7 @@ def generate_project_list(config, path): """ tmp = '%s.%d.tmp' % (path, os.getpid()) - f = file(tmp, 'w') + f = open(tmp, 'w') try: generate_project_list_fp(config=config, fp=f) finally: @@ -157,9 +158,10 @@ def set_descriptions(config): 'description', ) tmp = '%s.%d.tmp' % (path, os.getpid()) - f = file(tmp, 'w') + f = open(tmp, 'w') try: - print >>f, description + #print >>f, description + print(description, end="", file=f) finally: f.close() os.rename(tmp, path) diff --git a/gitosis/group.py b/gitosis/group.py index a18a731..0a2b010 100644 --- a/gitosis/group.py +++ b/gitosis/group.py @@ -1,5 +1,5 @@ import logging -from ConfigParser import NoSectionError, NoOptionError +from configparser import NoSectionError, NoOptionError def _getMembership(config, user, seen): log = logging.getLogger('gitosis.group.getMembership') diff --git a/gitosis/init.py b/gitosis/init.py index 28e7871..7d512c1 100644 --- a/gitosis/init.py +++ b/gitosis/init.py @@ -6,10 +6,11 @@ import logging import os import sys +import re from pkg_resources import resource_filename -from cStringIO import StringIO -from ConfigParser import RawConfigParser +from io import StringIO +from configparser import RawConfigParser from gitosis import repository from gitosis import run_hook @@ -32,19 +33,32 @@ def __str__(self): return '%s: %s' % (self.__doc__, ': '.join(self.args)) def ssh_extract_user(pubkey): - _, user = pubkey.rsplit(None, 1) + if not bool(re.search(r"\s", pubkey)): + _, user = pubkey.rsplit(None, 1) + else: + user = pubkey.strip() if ssh.isSafeUsername(user): return user else: raise InsecureSSHKeyUsername(repr(user)) def initial_commit(git_dir, cfg, pubkey, user): + log.debug('create initial commit') + log.info('User: ' + user) + if pubkey is None: + keyfile = 'keydir/principals' + content = user + else: + keyfile = 'keydir/%s.pub' % user + content = pubkey + log.debug('keyfile' + keyfile) + log.debug('content' + content) repository.fast_import( git_dir=git_dir, commit_msg='Automatic creation of gitosis repository.', committer='Gitosis Admin <%s>' % user, files=[ - ('keydir/%s.pub' % user, pubkey), + (keyfile, content), ('gitosis.conf', cfg), ], ) @@ -54,7 +68,7 @@ def symlink_config(git_dir): tmp = '%s.%d.tmp' % (dst, os.getpid()) try: os.unlink(tmp) - except OSError, e: + except OSError as e: if e.errno == errno.ENOENT: pass else: @@ -80,15 +94,18 @@ def init_admin_repository( # can't rely on setuptools and all kinds of distro packaging to # have kept our templates executable, it seems - os.chmod(os.path.join(git_dir, 'hooks', 'post-update'), 0755) + os.chmod(os.path.join(git_dir, 'hooks', 'post-update'), 0o755) if not repository.has_initial_commit(git_dir): log.info('Making initial commit...') # ConfigParser does not guarantee order, so jump through hoops # to make sure [gitosis] is first cfg_file = StringIO() - print >>cfg_file, '[gitosis]' - print >>cfg_file + print('[gitosis]', file=cfg_file) + #print('', end="", file=cfg_file) + + #print >>cfg_file, '[gitosis]' + #print >>cfg_file cfg = RawConfigParser() cfg.add_section('group gitosis-admin') cfg.set('group gitosis-admin', 'members', user) @@ -119,11 +136,14 @@ def read_config(self, *a, **kw): def handle_args(self, parser, cfg, options, args): super(Main, self).handle_args(parser, cfg, options, args) - os.umask(0022) + os.umask(0o022) log.info('Reading SSH public key...') pubkey = read_ssh_pubkey() user = ssh_extract_user(pubkey) + if not " " in pubkey: + pubkey = None + log.debug("pubkey: %s", pubkey) if user is None: log.error('Cannot parse user from SSH public key.') sys.exit(1) @@ -141,7 +161,7 @@ def handle_args(self, parser, cfg, options, args): user=user, ) log.info('Running post-update hook...') - util.mkdir(os.path.expanduser('~/.ssh'), 0700) + util.mkdir(os.path.expanduser('~/.ssh'), 0o700) run_hook.post_update(cfg=cfg, git_dir=admin_repository) log.info('Symlinking ~/.gitosis.conf to repository...') symlink_config(git_dir=admin_repository) diff --git a/gitosis/principals.py b/gitosis/principals.py new file mode 100644 index 0000000..4339afe --- /dev/null +++ b/gitosis/principals.py @@ -0,0 +1,48 @@ +""" +Perform gitosis actions for a git hook. +""" + +import errno +import logging +import os +import sys +import shutil + +from gitosis import repository +from gitosis import ssh +from gitosis import gitweb +from gitosis import gitdaemon +from gitosis import app +from gitosis import util + + +def serve_principal(cfg, sshUser, principals): + + TEMPLATE=('command="gitosis-serve %(user)s",no-port-forwarding,' + +'no-X11-forwarding,no-agent-forwarding,no-pty %(principals)s') + + for p in util.getAllowedSSHPrincipals(config=cfg).split() : + print(TEMPLATE % dict(user=sshUser.partition('@')[0], principals=p)) + +class Main(app.App): + def create_parser(self): + parser = super(Main, self).create_parser() + parser.set_usage('%prog [OPTS] sshUser principal principal ...') + parser.set_description( + 'Serves principals as AuthorizedPrincipalsCommand ') + return parser + + def handle_args(self, parser, cfg, options, args): + try: + sshUser = args.pop(0) + principals = ' '.join(args) + except ValueError: + parser.error('Missing argument sshUsers and/or principals.') + + log = logging.getLogger('gitosis.principals') + + if sshUser != "": + log.info('Running serve_principal for user %s', sshUser) + #log.debug('serve_principal: %s', serve_principal(cfg, sshUser, principals)) + serve_principal(cfg, sshUser, principals) + log.info('Done.') diff --git a/gitosis/repository.py b/gitosis/repository.py index 9dd0291..884549b 100644 --- a/gitosis/repository.py +++ b/gitosis/repository.py @@ -36,7 +36,7 @@ def init( if _git is None: _git = 'git' - util.mkdir(path, 0750) + util.mkdir(path, 0o750) args = [ _git, '--git-dir=.', @@ -131,7 +131,7 @@ class GitCheckoutIndexError(GitExportError): def export(git_dir, path): try: os.mkdir(path) - except OSError, e: + except OSError as e: if e.errno == errno.EEXIST: pass else: @@ -185,7 +185,7 @@ def has_initial_commit(git_dir): stdout=subprocess.PIPE, close_fds=True, ) - got = child.stdout.read() + got = child.stdout.read().decode('utf-8') returncode = child.wait() if returncode != 0: raise GitRevParseError('exit status %d' % returncode) diff --git a/gitosis/run_hook.py b/gitosis/run_hook.py index e535e6a..52716e8 100644 --- a/gitosis/run_hook.py +++ b/gitosis/run_hook.py @@ -19,7 +19,7 @@ def post_update(cfg, git_dir): export = os.path.join(git_dir, 'gitosis-export') try: shutil.rmtree(export) - except OSError, e: + except OSError as e: if e.errno == errno.ENOENT: pass else: @@ -63,16 +63,18 @@ def handle_args(self, parser, cfg, options, args): parser.error('Missing argument HOOK.') log = logging.getLogger('gitosis.run_hook') - os.umask(0022) + os.umask(0o022) git_dir = os.environ.get('GIT_DIR') if git_dir is None: log.error('Must have GIT_DIR set in enviroment') sys.exit(1) + else: + log.debug("GIT_DIR %s".format(git_dir)) if hook == 'post-update': - log.info('Running hook %s', hook) + log.info('Running hook %s'.format(hook)) post_update(cfg, git_dir) log.info('Done.') else: - log.warning('Ignoring unknown hook: %r', hook) + log.warning('Ignoring unknown hook: %r'.format(hook)) diff --git a/gitosis/serve.py b/gitosis/serve.py index fdfea53..3b69f7a 100644 --- a/gitosis/serve.py +++ b/gitosis/serve.py @@ -141,7 +141,7 @@ def serve( p = topdir for segment in repopath.split(os.sep)[:-1]: p = os.path.join(p, segment) - util.mkdir(p, 0750) + util.mkdir(p, 0o750) repository.init(path=fullpath) gitweb.set_descriptions( @@ -178,7 +178,7 @@ def handle_args(self, parser, cfg, options, args): parser.error('Missing argument USER.') main_log = logging.getLogger('gitosis.serve.main') - os.umask(0022) + os.umask(0o022) cmd = os.environ.get('SSH_ORIGINAL_COMMAND', None) if cmd is None: @@ -197,7 +197,7 @@ def handle_args(self, parser, cfg, options, args): user=user, command=cmd, ) - except ServingError, e: + except ServingError as e: main_log.error('%s', e) sys.exit(1) diff --git a/gitosis/ssh.py b/gitosis/ssh.py index a315a5c..33b3f2a 100644 --- a/gitosis/ssh.py +++ b/gitosis/ssh.py @@ -25,7 +25,7 @@ def readKeys(keydir): continue path = os.path.join(keydir, filename) - f = file(path) + f = open(path) for line in f: line = line.rstrip('\n') yield (basename, line) @@ -62,24 +62,27 @@ def filterAuthorizedKeys(fp): def writeAuthorizedKeys(path, keydir): tmp = '%s.%d.tmp' % (path, os.getpid()) + log.debug("writeAuthorizedKeys " + str(tmp) ) try: - in_ = file(path) - except IOError, e: + in_ = open(path) + except IOError as e: if e.errno == errno.ENOENT: in_ = None else: raise try: - out = file(tmp, 'w') + out = open(tmp, 'w') try: if in_ is not None: for line in filterAuthorizedKeys(in_): - print >>out, line + #print >>out, line + print(line, file=out) keygen = readKeys(keydir) for line in generateAuthorizedKeys(keygen): - print >>out, line + #print >>out, line + print(line, file=out) os.fsync(out) finally: diff --git a/gitosis/test/test_access.py b/gitosis/test/test_access.py index f39444c..a65742b 100644 --- a/gitosis/test/test_access.py +++ b/gitosis/test/test_access.py @@ -1,7 +1,7 @@ from nose.tools import eq_ as eq import logging -from ConfigParser import RawConfigParser +from configparser import RawConfigParser from gitosis import access diff --git a/gitosis/test/test_gitdaemon.py b/gitosis/test/test_gitdaemon.py index 94475ac..d9547a6 100644 --- a/gitosis/test/test_gitdaemon.py +++ b/gitosis/test/test_gitdaemon.py @@ -1,7 +1,7 @@ from nose.tools import eq_ as eq import os -from ConfigParser import RawConfigParser +from configparser import RawConfigParser from gitosis import gitdaemon from gitosis.test.util import maketemp, writeFile diff --git a/gitosis/test/test_gitweb.py b/gitosis/test/test_gitweb.py index e38b881..4b35962 100644 --- a/gitosis/test/test_gitweb.py +++ b/gitosis/test/test_gitweb.py @@ -1,8 +1,8 @@ from nose.tools import eq_ as eq import os -from ConfigParser import RawConfigParser -from cStringIO import StringIO +from configparser import RawConfigParser +from io import StringIO from gitosis import gitweb from gitosis.test.util import mkdir, maketemp, readFile, writeFile diff --git a/gitosis/test/test_group.py b/gitosis/test/test_group.py index 9ea035a..4d878a8 100644 --- a/gitosis/test/test_group.py +++ b/gitosis/test/test_group.py @@ -1,6 +1,6 @@ from nose.tools import eq_ as eq, assert_raises -from ConfigParser import RawConfigParser +from configparser import RawConfigParser from gitosis import group diff --git a/gitosis/test/test_init.py b/gitosis/test/test_init.py index fb6b286..dcef74c 100644 --- a/gitosis/test/test_init.py +++ b/gitosis/test/test_init.py @@ -2,7 +2,7 @@ from gitosis.test.util import assert_raises, maketemp import os -from ConfigParser import RawConfigParser +from configparser import RawConfigParser from gitosis import init from gitosis import repository @@ -113,7 +113,7 @@ def test_init_admin_repository(): 'hooks', 'post-update', ) - util.check_mode(hook, 0755, is_file=True) + util.check_mode(hook, 0o755, is_file=True) got = util.readFile(hook).splitlines() assert 'gitosis-run-hook post-update' in got export_dir = os.path.join(tmp, 'export') diff --git a/gitosis/test/test_repository.py b/gitosis/test/test_repository.py index 1646e6c..9e5c7cf 100644 --- a/gitosis/test/test_repository.py +++ b/gitosis/test/test_repository.py @@ -23,17 +23,17 @@ def test_init_simple(): tmp = maketemp() path = os.path.join(tmp, 'repo.git') repository.init(path) - check_mode(path, 0750, is_dir=True) + check_mode(path, 0o750, is_dir=True) check_bare(path) def test_init_exist_dir(): tmp = maketemp() path = os.path.join(tmp, 'repo.git') - mkdir(path, 0710) - check_mode(path, 0710, is_dir=True) + mkdir(path, 0o710) + check_mode(path, 0o710, is_dir=True) repository.init(path) # my weird access mode is preserved - check_mode(path, 0710, is_dir=True) + check_mode(path, 0o710, is_dir=True) check_bare(path) def test_init_exist_git(): @@ -41,7 +41,7 @@ def test_init_exist_git(): path = os.path.join(tmp, 'repo.git') repository.init(path) repository.init(path) - check_mode(path, 0750, is_dir=True) + check_mode(path, 0o750, is_dir=True) check_bare(path) def test_init_templates(): @@ -53,7 +53,7 @@ def test_init_templates(): ) # for reproducibility - os.umask(0022) + os.umask(0o022) repository.init(path, template=templatedir) repository.init(path) @@ -61,7 +61,7 @@ def test_init_templates(): eq(got, 'i should show up\n') check_mode( os.path.join(path, 'hooks', 'post-update'), - 0755, + 0o755, is_file=True, ) got = readFile(os.path.join(path, 'hooks', 'post-update')) @@ -91,7 +91,7 @@ def test_init_environment(): exec git "$@" ''') - os.chmod(mockgit, 0755) + os.chmod(mockgit, 0o755) magic_cookie = '%d' % random.randint(1, 100000) good_path = os.environ['PATH'] try: @@ -130,7 +130,7 @@ def test_fast_import_environment(): exec git "$@" ''') - os.chmod(mockgit, 0755) + os.chmod(mockgit, 0o755) magic_cookie = '%d' % random.randint(1, 100000) good_path = os.environ['PATH'] try: @@ -226,7 +226,7 @@ def test_export_environment(): exec git "$@" ''') - os.chmod(mockgit, 0755) + os.chmod(mockgit, 0o755) repository.init(path=git_dir) repository.fast_import( git_dir=git_dir, @@ -301,7 +301,7 @@ def test_has_initial_commit_environment(): exec git "$@" ''') - os.chmod(mockgit, 0755) + os.chmod(mockgit, 0o755) repository.init(path=tmp) repository.fast_import( git_dir=tmp, diff --git a/gitosis/test/test_run_hook.py b/gitosis/test/test_run_hook.py index db01e0c..ae674cc 100644 --- a/gitosis/test/test_run_hook.py +++ b/gitosis/test/test_run_hook.py @@ -1,8 +1,8 @@ from nose.tools import eq_ as eq import os -from ConfigParser import RawConfigParser -from cStringIO import StringIO +from configparser import RawConfigParser +from io import StringIO from gitosis import init, repository, run_hook from gitosis.test.util import maketemp, readFile diff --git a/gitosis/test/test_serve.py b/gitosis/test/test_serve.py index 88ce474..9d126c1 100644 --- a/gitosis/test/test_serve.py +++ b/gitosis/test/test_serve.py @@ -3,8 +3,8 @@ import logging import os -from cStringIO import StringIO -from ConfigParser import RawConfigParser +from io import StringIO +from configparser import RawConfigParser from gitosis import serve from gitosis import repository @@ -354,7 +354,7 @@ def test_push_inits_subdir_parent_missing(): ) eq(os.listdir(repositories), ['foo']) foo = os.path.join(repositories, 'foo') - util.check_mode(foo, 0750, is_dir=True) + util.check_mode(foo, 0o750, is_dir=True) eq(os.listdir(foo), ['bar.git']) assert os.path.isfile(os.path.join(repositories, 'foo', 'bar.git', 'HEAD')) @@ -366,7 +366,7 @@ def test_push_inits_subdir_parent_exists(): os.mkdir(repositories) foo = os.path.join(repositories, 'foo') # silly mode on purpose; not to be touched - os.mkdir(foo, 0751) + os.mkdir(foo, 0o751) cfg.set('gitosis', 'repositories', repositories) generated = os.path.join(tmp, 'generated') os.mkdir(generated) @@ -380,7 +380,7 @@ def test_push_inits_subdir_parent_exists(): command="git-receive-pack 'foo/bar.git'", ) eq(os.listdir(repositories), ['foo']) - util.check_mode(foo, 0751, is_dir=True) + util.check_mode(foo, 0o751, is_dir=True) eq(os.listdir(foo), ['bar.git']) assert os.path.isfile(os.path.join(repositories, 'foo', 'bar.git', 'HEAD')) diff --git a/gitosis/test/test_ssh.py b/gitosis/test/test_ssh.py index fc6ecbc..8714684 100644 --- a/gitosis/test/test_ssh.py +++ b/gitosis/test/test_ssh.py @@ -1,7 +1,7 @@ from nose.tools import eq_ as eq, assert_raises import os -from cStringIO import StringIO +from io import StringIO from gitosis import ssh from gitosis.test.util import mkdir, maketemp, writeFile, readFile @@ -171,7 +171,7 @@ class WriteAuthorizedKeys_Test(object): def test_simple(self): tmp = maketemp() path = os.path.join(tmp, 'authorized_keys') - f = file(path, 'w') + f = open(path, 'w') try: f.write('''\ # foo diff --git a/gitosis/test/util.py b/gitosis/test/util.py index 592b766..6b04c9a 100644 --- a/gitosis/test/util.py +++ b/gitosis/test/util.py @@ -9,7 +9,7 @@ def mkdir(*a, **kw): try: os.mkdir(*a, **kw) - except OSError, e: + except OSError as e: if e.errno == errno.EEXIST: pass else: @@ -27,7 +27,7 @@ def maketemp(): tmp = os.path.join(tmp, name) try: shutil.rmtree(tmp) - except OSError, e: + except OSError as e: if e.errno == errno.ENOENT: pass else: @@ -37,7 +37,7 @@ def maketemp(): def writeFile(path, content): tmp = '%s.tmp' % path - f = file(tmp, 'w') + f = open(tmp, 'w') try: f.write(content) finally: @@ -45,7 +45,7 @@ def writeFile(path, content): os.rename(tmp, path) def readFile(path): - f = file(path) + f = open(path) try: data = f.read() finally: @@ -58,7 +58,7 @@ def assert_raises(excClass, callableObj, *args, **kwargs): """ try: callableObj(*args, **kwargs) - except excClass, e: + except excClass as e: return e else: if hasattr(excClass,'__name__'): excName = excClass.__name__ diff --git a/gitosis/util.py b/gitosis/util.py index 479b2e9..59cd625 100644 --- a/gitosis/util.py +++ b/gitosis/util.py @@ -1,11 +1,11 @@ import errno import os -from ConfigParser import NoSectionError, NoOptionError +from configparser import NoSectionError, NoOptionError def mkdir(*a, **kw): try: os.mkdir(*a, **kw) - except OSError, e: + except OSError as e: if e.errno == errno.EEXIST: pass else: @@ -34,3 +34,17 @@ def getSSHAuthorizedKeysPath(config): except (NoSectionError, NoOptionError): path = os.path.expanduser('~/.ssh/authorized_keys') return path + +def getSSHPrincipalsPath(config): + try: + path = config.get('gitosis', 'ssh-principals-path') + except (NoSectionError, NoOptionError): + path = os.path.expanduser('~/.ssh/principals') + return path + +def getAllowedSSHPrincipals(config): + try: + principals = config.get('gitosis', 'allowedPrincipals') + except (NoSectionError, NoOptionError): + principals = "git" + return principals diff --git a/setup.py b/setup.py index 30eb9a5..4635d87 100755 --- a/setup.py +++ b/setup.py @@ -45,6 +45,7 @@ def subdir_contents(path): 'gitosis-serve = gitosis.serve:Main.run', 'gitosis-run-hook = gitosis.run_hook:Main.run', 'gitosis-init = gitosis.init:Main.run', + 'gitosis-authorized-principals = gitosis.principals:Main.run', ], },