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