[sysadmin-bin] Add a pre-receive hook that limits the translations user



commit 3548bd217bb063cd6d5ed066a2934c36447bc83c
Author: Owen W. Taylor <otaylor fishsoup net>
Date:   Tue Aug 6 11:12:50 2013 -0400

    Add a pre-receive hook that limits the translations user
    
    Before we allow the 'translations' user to commit, check that the
    change is something expect l10n.gnome.org - a change to a .po file,
    a LINGUAS file, or the addition of a language to a help Makefile.am.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=599066
    
    Signed-off-by: Andrea Veri <av gnome org>

 git/gnome-pre-receive              |    8 +++
 git/pre-receive-check-translations |  117 ++++++++++++++++++++++++++++++++++++
 2 files changed, 125 insertions(+), 0 deletions(-)
---
diff --git a/git/gnome-pre-receive b/git/gnome-pre-receive
index a5ed9fb..296021f 100755
--- a/git/gnome-pre-receive
+++ b/git/gnome-pre-receive
@@ -63,12 +63,20 @@ EOF
     exit 1
 fi
 
+my_uid=`id -u`
+translations_uid=`id -u translations`
+[ $? == 0 ] || exit 1
+
 while read oldrev newrev refname; do
     # Unlike the gnome-post-receive script, where we play fancy games
     # with 'tee', we invoke the different pre-receive hooks separately
     # for each ref that is updated. This keeps things simple and
     # reliable and none of the scripts need all the refs at once.
 
+    # First (and written in Python) since we're trying to make it hard
+    # to bypass if l10.gnome.org were compromised.
+    [ $my_uid != $translations_uid ] || $BINDIR/pre-receive-check-translations $oldrev $newrev $refname || 
exit 1
+
     $BINDIR/pre-receive-check-policy $oldrev $newrev $refname || exit 1
     $BINDIR/pre-receive-check-maintainers $oldrev $newrev $refname || exit 1
     $BINDIR/pre-receive-check-po $oldrev $newrev $refname || exit 1
diff --git a/git/pre-receive-check-translations b/git/pre-receive-check-translations
new file mode 100755
index 0000000..7300f29
--- /dev/null
+++ b/git/pre-receive-check-translations
@@ -0,0 +1,117 @@
+#!/usr/bin/python
+
+# This checks to see if an update is an update that the automated
+# translations user is allowed to do. This user is used by l10n.gnome.org.
+#
+# The per-repository git config keys
+#
+#  hooks.po-directories (default 'po')
+#  hooks.help-directories (default 'help')
+#
+# Define the directories that have po-file or translated help structure.
+
+import re
+import os
+import sys
+from git import *
+
+def error(msg):
+    print >>sys.stderr, msg
+    sys.exit(1)
+
+def config_dirs(key, default):
+    try:
+        raw = git.config("key", _quiet=True)
+    except CalledProcessError:
+        raw = default
+
+    return [os.path.normpath(p) for p in raw.split()]
+
+def relative_path(path, base):
+    if path.startswith(base + '/'):
+        return path[len(base) + 1:]
+    else:
+        return None
+
+def check_translations(oldrev, newrev, refname):
+    if not refname.startswith('refs/heads/'):
+        error("translations user can only update branches")
+
+    if re.match(r'^0+$', oldrev):
+        error("translations user cannot create branches")
+
+    if re.match(r'^0+$', newrev):
+        error("translations user cannot delete branches")
+
+    po_directories = config_dirs('hooks.po-directories', 'po')
+    help_directories = config_dirs('hooks.help-directories', 'help')
+
+    # Iterate through all changed files.  Passing -z to git diff-tree
+    # gives a result with separation by NUL characters, avoiding
+    # having to deal with quoting. NUL separates the meta-information
+    # from the pathname and from the next entry
+    lines = git.diff_tree(oldrev, newrev, z=True, r=True).split('\0')
+    for i in xrange(0, len(lines) - 1, 2):
+        srcmode, destmode, srcsha, destsha, status = lines[i].split()
+        path = lines[i + 1]
+
+        ok = False
+        for dir in po_directories:
+            relpath = relative_path(path, dir)
+            if relpath is None:
+                continue
+
+            # Arbitrary change to a .po file
+            if re.match(r'^[a-zA-Z _]+ po$', relpath):
+                ok = True
+
+            # Arbitrary change to LINGUAS
+            if relpath == 'LINGUAS':
+                ok = True
+
+        for dir in help_directories:
+            relpath = relative_path(path, dir)
+            if relpath is None:
+                continue
+
+            # Arbitrary change to a .po file in a language subdir
+            if re.match(r'^[a-zA-Z _]+/[a-zA-Z _]+ po$', relpath):
+                ok = True
+            # Arbitrary change to a PNG in the figures/ directory
+            elif re.match(r'^[a-zA-Z _]+/figures/[a-zA-Z0-9_-].png$', relpath):
+                ok = True
+            # Very limited changes to <help-dir>/Makefile.am
+            elif relpath == 'Makefile.am':
+                if status != 'M':
+                    error("translations user cannot modify '%s' in this way" % path)
+
+                diff = git.diff(oldrev + ':' + path, newrev + ':' + path).split('\n')
+
+                in_header = True
+                for line in diff:
+                    # Ignore anything up to the first @@
+                    if line.startswith('@@'):
+                        in_header = False
+                    if in_header:
+                        continue
+
+                    # Changes in a diff are line that begin with a '+' or '-' character
+                    if not re.match(r'^[-+]', line):
+                        continue
+
+                    # The only changes that we allow are changes that add or delete a
+                    # DOC_LINGUAS line we understand and validate against a strict
+                    # pattern. All other changes are forbidden.
+                    if not re.match(r'^[-+]DOC_LINGUAS *= *([a-zA-Z _]+( +|$))*$', line):
+                        error("translations user cannot modify '%s' in this way" % path)
+
+                ok = True
+
+        if not ok:
+            error("translations user cannot modify '%s'" % path)
+
+if len(sys.argv) == 4:
+    check_translations(*sys.argv[1:])
+else:
+    for line in sys.stdin:
+        check_translations(line.split())


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]