Rev 3100: Add a bzr_access script for allowing custom access control over bzr+ssh in http://bzr.arbash-meinel.com/branches/bzr/0.93-dev/contrib_bzr_access
John Arbash Meinel
john at arbash-meinel.com
Mon Dec 10 16:32:22 GMT 2007
At http://bzr.arbash-meinel.com/branches/bzr/0.93-dev/contrib_bzr_access
------------------------------------------------------------
revno: 3100
revision-id:john at arbash-meinel.com-20071210163156-lr415galj8b1670t
parent: pqm at pqm.ubuntu.com-20071210120611-a3j02d26cbzvlyju
author: Bálint Aradi <aradi at bccms.uni-bremen.de>
committer: John Arbash Meinel <john at arbash-meinel.com>
branch nick: contrib_bzr_access
timestamp: Mon 2007-12-10 10:31:56 -0600
message:
Add a bzr_access script for allowing custom access control over bzr+ssh
added:
contrib/bzr_access bzr_access-20071210163004-c9lb1renhra2ncg0-1
-------------- next part --------------
=== added file 'contrib/bzr_access'
--- a/contrib/bzr_access 1970-01-01 00:00:00 +0000
+++ b/contrib/bzr_access 2007-12-10 16:31:56 +0000
@@ -0,0 +1,214 @@
+#!/usr/bin/env python
+###############################################################################
+#
+# bzr_access:
+# Simple access control for shared bazaar repository accessed over ssh.
+#
+# (c) Balint Aradi, 2007
+#
+###############################################################################
+#
+# Invokation: bzr_access <bzr_executable> <repo_collection> <user>
+#
+# The script extracts from the SSH_ORIGINAL_COMMAND environment variable the
+# repository, which bazaar tries to access through the bzr+ssh protocol. The
+# repository is assumed to be relative to <repo_collection>. Based
+# on the configuration file <repo_collection>/bzr_access.conf it determines
+# the access rights (denied, read-only, read-write) for the specified user.
+# If the user has read-only or read-write access a bazaar smart server is
+# started for it in read-only or in read-write mode, rsp., using the specified
+# bzr executable.
+#
+# Config file: INI format, pretty much similar to the autzhfile of subversion.
+#
+# Groups can be defined in the [groups] section. The options in this block are
+# the names of the groups to be defined, the corresponding values the lists of
+# the users belonging to the given groups. (User names must be separated by
+# whitespace characters.)
+#
+# All other sections names should be path names (starting with '/'), defining
+# the permissions for the given path. The options in those sections are user
+# names or group references (group name with a leading '@'), the corresponding
+# values are the permissions: 'rw', 'r' and '' (without the quotes) for
+# read-write, read-only and no access, respectively.
+#
+# Only the options in the section with the longest matching name are evaluated.
+# The last relevant option for the user is used.
+#
+# Sample bzr_access.conf:
+#
+# [groups]
+# admins = alpha
+# devels = beta gamma delta
+#
+# [/test/trunk]
+# @admins = rw
+# @devels = r
+#
+# [/test/branches]
+# @admins = rw
+# @devels = rw
+#
+###############################################################################
+import ConfigParser
+import re
+import sys
+import os
+
+CONFIG_FILE = "bzr_access.conf"
+SCRIPT_NAME = os.path.basename(sys.argv[0])
+
+# Permission constants
+PERM_DENIED = 0
+PERM_READ = 1
+PERM_READWRITE = 2
+PERM_DICT = { "r": PERM_READ, "rw": PERM_READWRITE }
+
+# Exit codes
+EXIT_BAD_NR_ARG = 1
+EXIT_BZR_NOEXEC = 2
+EXIT_REPO_NOREAD = 3
+EXIT_BADENV = 4
+EXIT_BADDIR = 5
+EXIT_NOCONF = 6
+EXIT_NOACCESS = 7
+
+# pattern for the bzr command passed to ssh
+PAT_SSH_COMMAND = re.compile(r"""^bzr\s+
+ serve\s+
+ --inet\s+
+ --directory=(?P<dir>\S+)\s+
+ --allow-writes\s*$""", re.VERBOSE)
+
+# Command line for staring bzr (executable and repository are substituted)
+BZR_READWRITE = "%s serve --inet --directory='%s' --allow-writes"
+BZR_READ = "%s serve --inet --directory='%s'"
+
+
+
+def error(msg, exitCode):
+ """Prints error message to stdout and exits with given error code."""
+
+ print >>sys.stderr, "%s::error: %s" % (SCRIPT_NAME, msg)
+ sys.exit(exitCode)
+
+
+
+class AccessManager(object):
+ """Manages the permissions, can be queried for a specific user and path."""
+
+ def __init__(self, fp):
+ """:param fp: File like object, containing the configuration options.
+ """
+ self.config = ConfigParser.ConfigParser()
+ self.config.readfp(fp)
+ self.groups = {}
+ if self.config.has_section("groups"):
+ for group, users in self.config.items("groups"):
+ self.groups[group] = dict.fromkeys(users.split())
+
+
+ def permission(self, user, path):
+ """Determines the permission for a given user and a given path
+ :param user: user to look for.
+ :param path: path to look for.
+ :return: permission.
+ """
+ if not path.startswith("/"):
+ return PERM_DENIED
+ perm = PERM_DENIED
+ pathFound = False
+ while not pathFound and path != "/":
+ pathFound = self.config.has_section(path)
+ if (pathFound):
+ options = self.config.options(path)[-1::-1]
+ for option in options:
+ value = PERM_DICT.get(self.config.get(path, option), PERM_DENIED)
+ if self._isRelevant(option, user):
+ perm = value
+ else:
+ path = os.path.dirname(path)
+ return perm
+
+
+ def _isRelevant(self, option, user):
+ """Decides if a certain option is relevant for a given user.
+
+ An option is relevant if it is identical with the user or with a reference
+ to a group including the user.
+
+ :param option: Option to check.
+ :param user: User
+ :return: True if option is relevant for the user, False otherwise.
+ """
+ if option.startswith("@"):
+ result = self.groups.get(option[1:], {}).has_key(user)
+ else:
+ result = (option == user)
+ return result
+
+
+
+def getDirectory(command):
+ """Extracts the directory name from the command pass to ssh.
+ :param command: command to parse.
+ :return: Directory name or empty string, if directory was not found or if it
+ does not start with '/'.
+ """
+ match = PAT_SSH_COMMAND.match(command)
+ if not match:
+ return ""
+ directory = match.group("dir")
+ return os.path.normpath(directory)
+
+
+
+############################################################################
+# Main program
+############################################################################
+def main():
+
+ # Read arguments
+ if len(sys.argv) != 4:
+ error("Invalid number or arguments.", EXIT_BAD_NR_ARG)
+ (bzrExec, repoRoot, user) = sys.argv[1:4]
+
+ # Sanity checks
+ if not os.access(bzrExec, os.X_OK):
+ error("bzr is not executable.", EXIT_BZR_NOEXEC)
+ if not os.access(repoRoot, os.R_OK):
+ error("Path to repository not readable.", EXIT_REPO_NOREAD)
+
+ # Extract the repository path from the command passed to ssh.
+ if not os.environ.has_key("SSH_ORIGINAL_COMMAND"):
+ error("Environment variable SSH_ORIGINAL_COMMAND missing.", EXIT_BADENV)
+ directory = getDirectory(os.environ["SSH_ORIGINAL_COMMAND"])
+ if not len(directory):
+ error("Bad directory name.", EXIT_BADDIR)
+
+ # Read in config file.
+ try:
+ fp = open(os.path.join(repoRoot, CONFIG_FILE), "r")
+ accessMan = AccessManager(fp)
+ fp.close()
+ except IOError:
+ error("Can't read config file.", EXIT_NOCONF)
+
+ # Determine permission and execute bzr with appropriate options
+ perm = accessMan.permission(user, directory)
+ absDir = os.path.join(repoRoot, directory)
+ if perm == PERM_READ:
+ os.system(BZR_READ % (bzrExec, absDir))
+ elif perm == PERM_READWRITE:
+ os.system(BZR_READWRITE % (bzrExec, absDir))
+ else:
+ error("Access denied.", EXIT_NOACCESS)
+
+
+if __name__ == "__main__":
+ main()
+
+
+### Local Variables:
+### mode:python
+### End:
More information about the bazaar-commits
mailing list