]> err.no Git - dak/commitdiff
Add suite ACLs and per-suite NEW.
authorAnsgar Burchardt <ansgar@debian.org>
Sun, 2 Sep 2012 10:07:56 +0000 (12:07 +0200)
committerAnsgar Burchardt <ansgar@debian.org>
Sun, 16 Sep 2012 15:13:02 +0000 (17:13 +0200)
dak/acl.py [new file with mode: 0644]
dak/dak.py
dak/dakdb/update83.py [new file with mode: 0644]
dak/import_keyring.py
dak/update_db.py
daklib/archive.py
daklib/checks.py
daklib/dbconn.py

diff --git a/dak/acl.py b/dak/acl.py
new file mode 100644 (file)
index 0000000..f38a3a6
--- /dev/null
@@ -0,0 +1,83 @@
+#! /usr/bin/env python
+#
+# Copyright (C) 2012, Ansgar Burchardt <ansgar@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import apt_pkg
+import sys
+
+from daklib.config import Config
+from daklib.dbconn import DBConn, Fingerprint, Uid, ACL
+
+def usage():
+    print """Usage: dak acl set-fingerprints <acl-name>
+
+Reads list of fingerprints from stdin and sets the ACL <acl-name> to these.
+"""
+
+def get_fingerprint(entry, session):
+    """get fingerprint for given ACL entry
+
+    The entry is a string in one of these formats::
+
+        uid:<uid>
+        name:<name>
+        fpr:<fingerprint>
+
+    @type  entry: string
+    @param entry: ACL entry
+
+    @param session: database session
+
+    @rtype:  L{daklib.dbconn.Fingerprint} or C{None}
+    @return: fingerprint for the entry
+    """
+    field, value = entry.split(":", 1)
+    q = session.query(Fingerprint)
+
+    if field == 'uid':
+        q = q.join(Fingerprint.uid).filter(Uid.uid == value)
+    elif field == 'name':
+        q = q.join(Fingerprint.uid).filter(Uid.name == value)
+    elif field == 'fpr':
+        q = q.filter(Fingerprint.fingerprint == value)
+
+    return q.all()
+
+def acl_set_fingerprints(acl_name, entries):
+    session = DBConn().session()
+    acl = session.query(ACL).filter_by(name=acl_name).one()
+
+    acl.fingerprints.clear()
+    for entry in entries:
+        entry = entry.strip()
+        fps = get_fingerprint(entry, session)
+        if len(fps) == 0:
+            print "Unknown key for '{0}'".format(entry)
+        else:
+            acl.fingerprints.update(fps)
+
+    session.commit()
+
+def main(argv=None):
+    if argv is None:
+        argv = sys.argv
+
+    if len(argv) != 3 or argv[1] != 'set-fingerprints':
+        usage()
+        sys.exit(1)
+
+    acl_set_fingerprints(argv[2], sys.stdin)
index 7e78bc2922439d2c4d27226fbbf64dd9b811f5f8..306137a183fa14e1106fd75a00b64f0c29cf8722 100755 (executable)
@@ -123,6 +123,8 @@ def init():
          "Syncs fingerprint and uid tables with Debian LDAP db"),
         ("import-users-from-passwd",
          "Sync PostgreSQL users with passwd file"),
+        ("acl",
+         "Manage upload ACLs"),
         ("admin",
          "Perform administration on the dak database"),
         ("update-db",
diff --git a/dak/dakdb/update83.py b/dak/dakdb/update83.py
new file mode 100644 (file)
index 0000000..f0707d5
--- /dev/null
@@ -0,0 +1,219 @@
+#!/usr/bin/env python
+# coding=utf8
+
+"""
+switch to new ACL implementation and add pre-suite NEW
+
+@contact: Debian FTP Master <ftpmaster@debian.org>
+@copyright: 2012 Ansgar Burchardt <ansgar@debian.org>
+@license: GNU General Public License version 2 or later
+"""
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+import psycopg2
+from daklib.dak_exceptions import DBUpdateError
+from daklib.config import Config
+
+statements = [
+"""ALTER TABLE suite ADD COLUMN new_queue_id INT REFERENCES policy_queue(id)""",
+
+"""CREATE TABLE acl (
+    id SERIAL PRIMARY KEY NOT NULL,
+    name TEXT NOT NULL,
+    is_global BOOLEAN NOT NULL DEFAULT 'f',
+
+    match_fingerprint BOOLEAN NOT NULL DEFAULT 'f',
+    match_keyring_id INTEGER REFERENCES keyrings(id),
+
+    allow_new BOOLEAN NOT NULL DEFAULT 'f',
+    allow_source BOOLEAN NOT NULL DEFAULT 'f',
+    allow_binary BOOLEAN NOT NULL DEFAULT 'f',
+    allow_binary_all BOOLEAN NOT NULL DEFAULT 'f',
+    allow_binary_only BOOLEAN NOT NULL DEFAULT 'f',
+    allow_hijack BOOLEAN NOT NULL DEFAULT 'f',
+    allow_per_source BOOLEAN NOT NULL DEFAULT 'f',
+    deny_per_source BOOLEAN NOT NULL DEFAULT 'f'
+    )""",
+
+"""CREATE TABLE acl_architecture_map (
+    acl_id INTEGER NOT NULL REFERENCES acl(id) ON DELETE CASCADE,
+    architecture_id INTEGER NOT NULL REFERENCES architecture(id) ON DELETE CASCADE,
+    PRIMARY KEY (acl_id, architecture_id)
+    )""",
+
+"""CREATE TABLE acl_fingerprint_map (
+    acl_id INTEGER NOT NULL REFERENCES acl(id) ON DELETE CASCADE,
+    fingerprint_id INTEGER NOT NULL REFERENCES fingerprint(id) ON DELETE CASCADE,
+    PRIMARY KEY (acl_id, fingerprint_id)
+    )""",
+
+"""CREATE TABLE acl_per_source (
+    acl_id INTEGER NOT NULL REFERENCES acl(id) ON DELETE CASCADE,
+    fingerprint_id INTEGER NOT NULL REFERENCES fingerprint(id) ON DELETE CASCADE,
+    source TEXT NOT NULL,
+    reason TEXT,
+    PRIMARY KEY (acl_id, fingerprint_id, source)
+    )""",
+
+"""CREATE TABLE suite_acl_map (
+    suite_id INTEGER NOT NULL REFERENCES suite(id) ON DELETE CASCADE,
+    acl_id INTEGER NOT NULL REFERENCES acl(id),
+    PRIMARY KEY (suite_id, acl_id)
+    )""",
+]
+
+################################################################################
+
+def get_buildd_acl_id(c, keyring_id):
+    c.execute("""
+        SELECT 'buildd-' || STRING_AGG(a.arch_string, '+' ORDER BY a.arch_string)
+          FROM keyring_acl_map kam
+          JOIN architecture a ON kam.architecture_id = a.id
+         WHERE kam.keyring_id = %(keyring_id)s
+        """, {'keyring_id': keyring_id})
+    acl_name, = c.fetchone()
+
+    c.execute('SELECT id FROM acl WHERE name = %(acl_name)s', {'acl_name': acl_name})
+    row = c.fetchone()
+    if row is not None:
+        return row[0]
+
+    c.execute("""
+        INSERT INTO acl
+               (        name, allow_new, allow_source, allow_binary, allow_binary_all, allow_binary_only, allow_hijack)
+        VALUES (%(acl_name)s,       't',          'f',          't',              'f',               't',          't')
+        RETURNING id""", {'acl_name': acl_name})
+    acl_id, = c.fetchone()
+
+    c.execute("""INSERT INTO acl_architecture_map (acl_id, architecture_id)
+                 SELECT %(acl_id)s, architecture_id
+                   FROM keyring_acl_map
+                  WHERE keyring_id = %(keyring_id)s""",
+              {'acl_id': acl_id, 'keyring_id': keyring_id})
+
+    return acl_id
+
+def get_acl_id(c, acl_dd, acl_dm, keyring_id, source_acl_id, binary_acl_id):
+    c.execute('SELECT access_level FROM source_acl WHERE id = %(source_acl_id)s', {'source_acl_id': source_acl_id})
+    row = c.fetchone()
+    if row is not None:
+        source_acl = row[0]
+    else:
+        source_acl = None
+
+    c.execute('SELECT access_level FROM binary_acl WHERE id = %(binary_acl_id)s', {'binary_acl_id': binary_acl_id})
+    row = c.fetchone()
+    if row is not None:
+        binary_acl = row[0]
+    else:
+        binary_acl = None
+
+    if source_acl == 'full' and binary_acl == 'full':
+        return acl_dd
+    elif source_acl == 'dm' and binary_acl == 'full':
+        return acl_dm
+    elif source_acl is None and binary_acl == 'map':
+        return get_buildd_acl_id(c, keyring_id)
+
+    raise Exception('Cannot convert ACL combination automatically: binary_acl={0}, source_acl={1}'.format(binary_acl, source_acl))
+
+def do_update(self):
+    print __doc__
+    try:
+        cnf = Config()
+
+        c = self.db.cursor()
+
+        for stmt in statements:
+            c.execute(stmt)
+
+        c.execute("""
+            INSERT INTO acl
+                   (name, allow_new, allow_source, allow_binary, allow_binary_all, allow_binary_only, allow_hijack)
+            VALUES ('dd',       't',          't',          't',              't',               't',          't')
+            RETURNING id""")
+        acl_dd, = c.fetchone()
+
+        c.execute("""
+            INSERT INTO acl
+                   (name, allow_new, allow_source, allow_binary, allow_binary_all, allow_binary_only, allow_per_source, allow_hijack)
+            VALUES ('dm',       'f',          't',          't',              't',               'f',              't',          'f')
+            RETURNING id""")
+        acl_dm, = c.fetchone()
+
+        # convert per-fingerprint ACLs
+
+        c.execute('ALTER TABLE fingerprint ADD COLUMN acl_id INTEGER REFERENCES acl(id)')
+        c.execute("""SELECT id, keyring, source_acl_id, binary_acl_id
+                       FROM fingerprint
+                      WHERE source_acl_id IS NOT NULL OR binary_acl_id IS NOT NULL""")
+        for fingerprint_id, keyring_id, source_acl_id, binary_acl_id in c.fetchall():
+            acl_id = get_acl_id(c, acl_dd, acl_dm, keyring_id, source_acl_id, binary_acl_id)
+            c.execute('UPDATE fingerprint SET acl_id = %(acl_id)s WHERE id = %(fingerprint_id)s',
+                      {'acl_id': acl_id, 'fingerprint_id': fingerprint_id})
+        c.execute("""ALTER TABLE fingerprint
+                       DROP COLUMN source_acl_id,
+                       DROP COLUMN binary_acl_id,
+                       DROP COLUMN binary_reject""")
+
+        # convert per-keyring ACLs
+        c.execute('ALTER TABLE keyrings ADD COLUMN acl_id INTEGER REFERENCES acl(id)')
+        c.execute('SELECT id, default_source_acl_id, default_binary_acl_id FROM keyrings')
+        for keyring_id, source_acl_id, binary_acl_id in c.fetchall():
+            acl_id = get_acl_id(c, acl_dd, acl_dm, keyring_id, source_acl_id, binary_acl_id)
+            c.execute('UPDATE keyrings SET acl_id = %(acl_id)s WHERE id = %(keyring_id)s',
+                      {'acl_id': acl_id, 'keyring_id': keyring_id})
+        c.execute("""ALTER TABLE keyrings
+                       DROP COLUMN default_source_acl_id,
+                       DROP COLUMN default_binary_acl_id,
+                       DROP COLUMN default_binary_reject""")
+
+        c.execute("DROP TABLE keyring_acl_map")
+        c.execute("DROP TABLE binary_acl_map")
+        c.execute("DROP TABLE binary_acl")
+        c.execute("DROP TABLE source_acl")
+
+        # convert upload blocks
+        c.execute("""
+            INSERT INTO acl
+                   (    name, is_global, allow_new, allow_source, allow_binary, allow_binary_all, allow_hijack, allow_binary_only, deny_per_source)
+            VALUES ('blocks',       't',       't',          't',          't',              't',          't',               't',             't')
+            RETURNING id""")
+        acl_block, = c.fetchone()
+        c.execute("SELECT source, fingerprint_id, reason FROM upload_blocks")
+        for source, fingerprint_id, reason in c.fetchall():
+            if fingerprint_id is None:
+                raise Exception(
+                    "ERROR: upload blocks based on uid are no longer supported\n"
+                    "=========================================================\n"
+                    "\n"
+                    "dak now only supports upload blocks based on fingerprints. Please remove\n"
+                    "any uid-specific block by running\n"
+                    "   DELETE FROM upload_blocks WHERE fingerprint_id IS NULL\n"
+                    "and try again.")
+
+            c.execute('INSERT INTO acl_match_source_map (acl_id, fingerprint_id, source, reason) VALUES (%(acl_id)s, %(fingerprint_id)s, %(source)s, %(reason)s)',
+                      {'acl_id': acl_block, 'fingerprint_id': fingerprint_id, 'source': source, 'reason': reason})
+        c.execute("DROP TABLE upload_blocks")
+
+        c.execute("UPDATE config SET value = '83' WHERE name = 'db_revision'")
+        self.db.commit()
+
+    except psycopg2.ProgrammingError as msg:
+        self.db.rollback()
+        raise DBUpdateError('Unable to apply sick update 83, rollback issued. Error message: {0}'.format(msg))
index 89c2b755dc5942f56e8b28d42afa530a0ab73ea9..ff0cb98ea2d7c75c752629d0d4ca5a2883248577 100755 (executable)
@@ -178,13 +178,8 @@ def main():
         changes.append((db_uid_byid.get(u, [None])[0], "Removed key: %s" % (f)))
         session.execute("""UPDATE fingerprint
                               SET keyring = NULL,
-                                  source_acl_id = NULL,
-                                  binary_acl_id = NULL,
-                                  binary_reject = TRUE
                             WHERE id = :fprid""", {'fprid': fid})
 
-        session.execute("""DELETE FROM binary_acl_map WHERE fingerprint_id = :fprid""", {'fprid': fid})
-
     # For the keys in this keyring, add/update any fingerprints that've
     # changed.
 
@@ -208,19 +203,9 @@ def main():
             if newuid:
                 fp.uid_id = newuid
 
-            fp.binary_acl_id = keyring.default_binary_acl_id
-            fp.source_acl_id = keyring.default_source_acl_id
-            fp.default_binary_reject = keyring.default_binary_reject
             session.add(fp)
             session.flush()
 
-            for k in keyring.keyring_acl_map:
-                ba = BinaryACLMap()
-                ba.fingerprint_id = fp.fingerprint_id
-                ba.architecture_id = k.architecture_id
-                session.add(ba)
-                session.flush()
-
         else:
             if newuid and olduid != newuid and olduid == -1:
                 changes.append((newuiduid, "Linked key: %s" % f))
@@ -245,29 +230,14 @@ def main():
 
                 # Only change the keyring if it won't result in a loss of permissions
                 if movekey:
-                    session.execute("""DELETE FROM binary_acl_map WHERE fingerprint_id = :fprid""", {'fprid': oldfid})
-
                     session.execute("""UPDATE fingerprint
-                                          SET keyring = :keyring,
-                                              source_acl_id = :source_acl_id,
-                                              binary_acl_id = :binary_acl_id,
-                                              binary_reject = :binary_reject
+                                          SET keyring = :keyring
                                         WHERE id = :fpr""",
                                     {'keyring': keyring.keyring_id,
-                                     'source_acl_id': keyring.default_source_acl_id,
-                                     'binary_acl_id': keyring.default_binary_acl_id,
-                                     'binary_reject': keyring.default_binary_reject,
                                      'fpr': oldfid})
 
                     session.flush()
 
-                    for k in keyring.keyring_acl_map:
-                        ba = BinaryACLMap()
-                        ba.fingerprint_id = oldfid
-                        ba.architecture_id = k.architecture_id
-                        session.add(ba)
-                        session.flush()
-
                 else:
                     print "Key %s exists in both %s and %s keyrings. Not demoting." % (f,
                                                                                        oldkeyring.keyring_name,
index 3a33c97600a603b5554b0f3ebf4023f751154832..cf327b0bd1bdaf5a9a1b40965da08f442e7d19fa 100755 (executable)
@@ -46,7 +46,7 @@ from daklib.daklog import Logger
 ################################################################################
 
 Cnf = None
-required_database_schema = 85
+required_database_schema = 86
 
 ################################################################################
 
index fdd7cd7d1f9419056d4f63b8266573ab6b5a6079..5c98eeca826cfec49355fc7a43e5fdf40454c94b 100644 (file)
@@ -876,7 +876,6 @@ class ArchiveUpload(object):
 
             for chk in (
                     checks.TransitionCheck,
-                    checks.UploadBlockCheck,
                     checks.ACLCheck,
                     checks.NoSourceOnlyCheck,
                     checks.LintianCheck,
@@ -884,6 +883,7 @@ class ArchiveUpload(object):
                 chk().check(self)
 
             for chk in (
+                    checks.ACLCheck,
                     checks.SourceFormatCheck,
                     checks.SuiteArchitectureCheck,
                     checks.VersionCheck,
@@ -1173,16 +1173,22 @@ class ArchiveUpload(object):
         binaries = self.changes.binaries
         byhand = self.changes.byhand_files
 
-        new_queue = self.transaction.session.query(PolicyQueue).filter_by(queue_name='new').one()
-        if len(byhand) > 0:
-            new_queue = self.transaction.session.query(PolicyQueue).filter_by(queue_name='byhand').one()
-        new_suite = new_queue.suite
-
         # we need a suite to guess components
         suites = list(self.final_suites)
         assert len(suites) == 1, "NEW uploads must be to a single suite"
         suite = suites[0]
 
+        # decide which NEW queue to use
+        if suite.new_queue is None:
+            new_queue = self.transaction.session.query(PolicyQueue).filter_by(queue_name='new').one()
+        else:
+            new_queue = suite.new_queue
+        if len(byhand) > 0:
+            # There is only one global BYHAND queue
+            new_queue = self.transaction.session.query(PolicyQueue).filter_by(queue_name='byhand').one()
+        new_suite = new_queue.suite
+
+
         def binary_component_func(binary):
             return self._binary_component(suite, binary, only_overrides=False)
 
index 770615aa04d64235b2677176b71061e7f5e60038..81bd629e481e171991d1ddf0da8d3b4dce282c12 100644 (file)
@@ -352,21 +352,70 @@ class SingleDistributionCheck(Check):
 
 class ACLCheck(Check):
     """Check the uploader is allowed to upload the packages in .changes"""
-    def _check_dm(self, upload):
+
+    def _does_hijack(self, session, upload, suite):
+        for binary_name in upload.changes.binary_names:
+            binaries = session.query(DBBinary).join(DBBinary.source) \
+                .filter(DBBinary.suites.contains(suite)) \
+                .filter(DBBinary.package == binary_name)
+            for binary in binaries:
+                if binary.source.source != upload.changes.changes['Source']:
+                    return True, binary, binary.source.source
+        return False, None, None
+
+    def _check_acl(self, session, upload, acl):
+        source_name = upload.changes.source_name
+
+        if acl.match_fingerprint and upload.fingerprint not in acl.fingerprints:
+            return None, None
+        if acl.match_keyring is not None and upload.fingerprint.keyring != acl.match_keyring:
+            return None, None
+
+        if not acl.allow_new:
+            if upload.new:
+                return False, "NEW uploads are not allowed"
+            for f in upload.changes.files.itervalues():
+                if f.section == 'byhand' or f.section.startswith("raw-"):
+                    return False, "BYHAND uploads are not allowed"
+        if not acl.allow_source and upload.changes.source is not None:
+            return False, "sourceful uploads are not allowed"
+        binaries = upload.changes.binaries
+        if len(binaries) != 0:
+            if not acl.allow_binary:
+                return False, "binary uploads are not allowed"
+            if upload.changes.source is None and not acl.allow_binary_only:
+                return False, "binary-only uploads are not allowed"
+            if not acl.allow_binary_all:
+                uploaded_arches = set(upload.changes.architectures)
+                uploaded_arches.discard('source')
+                allowed_arches = set(a.arch_string for a in acl.architectures)
+                for a in uploaded_arches:
+                    if a not in allowed_arches:
+                        return False, "uploads for architecture {0} are not allowed".format(a)
+        if not acl.allow_hijack:
+            for suite in upload.final_suites:
+                does_hijack, hijacked_binary, hijacked_from = self._does_hijack(session, upload, suite)
+                if does_hijack:
+                    return False, "hijacks are not allowed (binary={0}, other-source={1})".format(hijacked_binary, hijacked_from)
+
+        acl_per_source = session.query(ACLPerSource).filter_by(acl=acl, fingerprint=upload.fingerprint, source=source_name).first()
+        if acl.allow_per_source:
+            # XXX: Drop DMUA part here and switch to new implementation.
+            dmua_status, dmua_reason = self._check_dmua(upload)
+            if not dmua_status:
+                return False, dmua_reason
+            #if acl_per_source is None:
+            #    return False, "not allowed to upload source package '{0}'".format(source_name)
+        if acl.deny_per_source and acl_per_source is not None:
+            return False, acl_per_source.reason or "forbidden to upload source package '{0}'".format(source_name)
+
+        return True, None
+
+    def _check_dmua(self, upload):
         # This code is not very nice, but hopefully works until we can replace
         # DM-Upload-Allowed, cf. https://lists.debian.org/debian-project/2012/06/msg00029.html
         session = upload.session
 
-        if 'source' not in upload.changes.architectures:
-            raise Reject('DM uploads must include source')
-        for f in upload.changes.files.itervalues():
-            if f.section == 'byhand' or f.section[:4] == "raw-":
-                raise Reject("Uploading byhand packages is not allowed for DMs.")
-
-        # Reject NEW packages
-        if upload.new:
-            raise Reject('Uploading NEW packages is not allowed for DMs.')
-
         # Check DM-Upload-Allowed
         suites = upload.final_suites
         assert len(suites) == 1
@@ -379,84 +428,61 @@ class ACLCheck(Check):
             .join(DBSource.suites).filter(Suite.suite_name.in_(last_suites)) \
             .order_by(DBSource.version.desc()).limit(1).first()
         if last is None:
-            raise Reject('No existing source found in {0}'.format(' or '.join(last_suites)))
+            return False, 'No existing source found in {0}'.format(' or '.join(last_suites))
         if not last.dm_upload_allowed:
-            raise Reject('DM-Upload-Allowed is not set in {0}={1}'.format(last.source, last.version))
+            return False, 'DM-Upload-Allowed is not set in {0}={1}'.format(last.source, last.version)
 
         # check current Changed-by is in last Maintainer or Uploaders
         uploader_names = [ u.name for u in last.uploaders ]
         changed_by_field = upload.changes.changes.get('Changed-By', upload.changes.changes['Maintainer'])
         if changed_by_field not in uploader_names:
-            raise Reject('{0} is not an uploader for {1}={2}'.format(changed_by_field, last.source, last.version))
+            return False, '{0} is not an uploader for {1}={2}'.format(changed_by_field, last.source, last.version)
 
         # check Changed-by is the DM
         changed_by = fix_maintainer(changed_by_field)
         uid = upload.fingerprint.uid
         if uid is None:
-            raise Reject('Unknown uid for fingerprint {0}'.format(upload.fingerprint.fingerprint))
+            return False, 'Unknown uid for fingerprint {0}'.format(upload.fingerprint.fingerprint)
         if uid.uid != changed_by[3] and uid.name != changed_by[2]:
-            raise Reject('DMs are not allowed to sponsor uploads (expected {0} <{1}> as maintainer, but got {2})'.format(uid.name, uid.uid, changed_by_field))
+            return False, 'DMs are not allowed to sponsor uploads (expected {0} <{1}> as maintainer, but got {2})'.format(uid.name, uid.uid, changed_by_field)
 
-        # Try to catch hijacks.
-        # This doesn't work correctly. Uploads to experimental can still
-        # "hijack" binaries from unstable. Also one can hijack packages
-        # via buildds (but people who try this should not be DMs).
-        for binary_name in upload.changes.binary_names:
-            binaries = session.query(DBBinary).join(DBBinary.source) \
-                .filter(DBBinary.suites.contains(suite)) \
-                .filter(DBBinary.package == binary_name)
-            for binary in binaries:
-                if binary.source.source != upload.changes.changes['Source']:
-                    raise Reject('DMs must not hijack binaries (binary={0}, other-source={1})'.format(binary_name, binary.source.source))
-
-        return True
+        return True, None
 
     def check(self, upload):
+        session = upload.session
         fingerprint = upload.fingerprint
-        source_acl = fingerprint.source_acl
-        if source_acl is None:
-            if 'source' in upload.changes.architectures:
-                raise Reject('Fingerprint {0} must not upload source'.format(fingerprint.fingerprint))
-        elif source_acl.access_level == 'dm':
-            self._check_dm(upload)
-        elif source_acl.access_level != 'full':
-            raise Reject('Unknown source_acl access level {0} for fingerprint {1}'.format(source_acl.access_level, fingerprint.fingerprint))
-
-        bin_architectures = set(upload.changes.architectures)
-        bin_architectures.discard('source')
-        binary_acl = fingerprint.binary_acl
-        if binary_acl is None:
-            if len(bin_architectures) > 0:
-                raise Reject('Fingerprint {0} must not upload binary packages'.format(fingerprint.fingerprint))
-        elif binary_acl.access_level == 'map':
-            query = upload.session.query(BinaryACLMap).filter_by(fingerprint=fingerprint)
-            allowed_architectures = [ m.architecture.arch_string for m in query ]
-
-            for arch in upload.changes.architectures:
-                if arch not in allowed_architectures:
-                    raise Reject('Fingerprint {0} must not  upload binaries for architecture {1}'.format(fingerprint.fingerprint, arch))
-        elif binary_acl.access_level != 'full':
-            raise Reject('Unknown binary_acl access level {0} for fingerprint {1}'.format(binary_acl.access_level, fingerprint.fingerprint))
+        keyring = fingerprint.keyring
 
-        return True
+        if keyring is None:
+            raise Reject('No keyring for fingerprint {0}'.format(fingerprint.fingerprint))
+        if not keyring.active:
+            raise Reject('Keyring {0} is not active'.format(keyring.name))
 
-class UploadBlockCheck(Check):
-    """check for upload blocks"""
-    def check(self, upload):
-        session = upload.session
-        control = upload.changes.changes
+        acl = fingerprint.acl or keyring.acl
+        if acl is None:
+            raise Reject('No ACL for fingerprint {0}'.format(fingerprint.fingerprint))
+        result, reason = self._check_acl(session, upload, acl)
+        if not result:
+            raise Reject(reason)
 
-        source = re_field_source.match(control['Source']).group('package')
-        version = control['Version']
-        blocks = session.query(UploadBlock).filter_by(source=source) \
-                        .filter((UploadBlock.version == version) | (UploadBlock.version == None))
+        for acl in session.query(ACL).filter_by(is_global=True):
+            result, reason = self._check_acl(session, upload, acl)
+            if result == False:
+                raise Reject(reason)
 
-        for block in blocks:
-            if block.fingerprint == upload.fingerprint:
-                raise Reject('Manual upload block in place for package {0} and fingerprint {1}:\n{2}'.format(source, upload.fingerprint.fingerprint, block.reason))
-            if block.uid == upload.fingerprint.uid:
-                raise Reject('Manual upload block in place for package {0} and uid {1}:\n{2}'.format(source, block.uid.uid, block.reason))
+        return True
 
+    def per_suite_check(self, upload, suite):
+        acls = suite.acls
+        if len(acls) != 0:
+            accept = False
+            for acl in acls:
+                result, reason = self._check_acl(upload.session, upload, acl)
+                if result == False:
+                    raise Reject(reason)
+                accept = accept or result
+            if not accept:
+                raise Reject('Not accepted by any per-suite acl (suite={0})'.format(suite.suite_name))
         return True
 
 class TransitionCheck(Check):
index 293f4dcb799e7abcf0587194dfa593925e1df58d..9617bb77a6a00241a77e3bb7332f8336d9bd11a2 100644 (file)
@@ -369,6 +369,20 @@ validator = Validator()
 
 ################################################################################
 
+class ACL(ORMObject):
+    def __repr__(self):
+        return "<ACL {0}>".format(self.name)
+
+__all__.append('ACL')
+
+class ACLPerSource(ORMObject):
+    def __repr__(self):
+        return "<ACLPerSource acl={0} fingerprint={1} source={2} reason={3}>".format(self.acl.name, self.fingerprint.fingerprint, self.source, self.reason)
+
+__all__.append('ACLPerSource')
+
+################################################################################
+
 class Architecture(ORMObject):
     def __init__(self, arch_string = None, description = None):
         self.arch_string = arch_string
@@ -642,28 +656,6 @@ __all__.append('get_component_by_package_suite')
 
 ################################################################################
 
-class BinaryACL(object):
-    def __init__(self, *args, **kwargs):
-        pass
-
-    def __repr__(self):
-        return '<BinaryACL %s>' % self.binary_acl_id
-
-__all__.append('BinaryACL')
-
-################################################################################
-
-class BinaryACLMap(object):
-    def __init__(self, *args, **kwargs):
-        pass
-
-    def __repr__(self):
-        return '<BinaryACLMap %s>' % self.binary_acl_map_id
-
-__all__.append('BinaryACLMap')
-
-################################################################################
-
 class BuildQueue(object):
     def __init__(self, *args, **kwargs):
         pass
@@ -1365,17 +1357,6 @@ __all__.append('get_primary_keyring_path')
 
 ################################################################################
 
-class KeyringACLMap(object):
-    def __init__(self, *args, **kwargs):
-        pass
-
-    def __repr__(self):
-        return '<KeyringACLMap %s>' % self.keyring_acl_map_id
-
-__all__.append('KeyringACLMap')
-
-################################################################################
-
 class DBChange(object):
     def __init__(self, *args, **kwargs):
         pass
@@ -2157,17 +2138,6 @@ __all__.append('import_metadata_into_db')
 
 ################################################################################
 
-class SourceACL(object):
-    def __init__(self, *args, **kwargs):
-        pass
-
-    def __repr__(self):
-        return '<SourceACL %s>' % self.source_acl_id
-
-__all__.append('SourceACL')
-
-################################################################################
-
 class SrcFormat(object):
     def __init__(self, *args, **kwargs):
         pass
@@ -2417,17 +2387,6 @@ __all__.append('get_uid_from_fingerprint')
 
 ################################################################################
 
-class UploadBlock(object):
-    def __init__(self, *args, **kwargs):
-        pass
-
-    def __repr__(self):
-        return '<UploadBlock %s (%s)>' % (self.source, self.upload_block_id)
-
-__all__.append('UploadBlock')
-
-################################################################################
-
 class MetadataKey(ORMObject):
     def __init__(self, key = None):
         self.key = key
@@ -2551,14 +2510,16 @@ class DBConn(object):
 
     def __setuptables(self):
         tables = (
+            'acl',
+            'acl_architecture_map',
+            'acl_fingerprint_map',
+            'acl_per_source',
             'architecture',
             'archive',
             'bin_associations',
             'bin_contents',
             'binaries',
             'binaries_metadata',
-            'binary_acl',
-            'binary_acl_map',
             'build_queue',
             'changelogs_text',
             'changes',
@@ -2571,7 +2532,6 @@ class DBConn(object):
             'files_archive_map',
             'fingerprint',
             'keyrings',
-            'keyring_acl_map',
             'maintainer',
             'metadata_keys',
             'new_comments',
@@ -2585,18 +2545,17 @@ class DBConn(object):
             'priority',
             'section',
             'source',
-            'source_acl',
             'source_metadata',
             'src_associations',
             'src_contents',
             'src_format',
             'src_uploaders',
             'suite',
+            'suite_acl_map',
             'suite_architectures',
             'suite_build_queue_copy',
             'suite_src_formats',
             'uid',
-            'upload_blocks',
             'version_check',
         )
 
@@ -2639,6 +2598,20 @@ class DBConn(object):
                    backref=backref('architectures', order_by=self.tbl_architecture.c.arch_string))),
             extension = validator)
 
+        mapper(ACL, self.tbl_acl,
+               properties = dict(
+                architectures = relation(Architecture, secondary=self.tbl_acl_architecture_map, collection_class=set),
+                fingerprints = relation(Fingerprint, secondary=self.tbl_acl_fingerprint_map, collection_class=set),
+                match_keyring = relation(Keyring, primaryjoin=(self.tbl_acl.c.match_keyring_id == self.tbl_keyrings.c.id)),
+                per_source = relation(ACLPerSource, collection_class=set),
+                ))
+
+        mapper(ACLPerSource, self.tbl_acl_per_source,
+               properties = dict(
+                acl = relation(ACL),
+                fingerprint = relation(Fingerprint),
+                ))
+
         mapper(Archive, self.tbl_archive,
                properties = dict(archive_id = self.tbl_archive.c.id,
                                  archive_name = self.tbl_archive.c.name))
@@ -2676,14 +2649,6 @@ class DBConn(object):
                                      collection_class=attribute_mapped_collection('key'))),
                 extension = validator)
 
-        mapper(BinaryACL, self.tbl_binary_acl,
-               properties = dict(binary_acl_id = self.tbl_binary_acl.c.id))
-
-        mapper(BinaryACLMap, self.tbl_binary_acl_map,
-               properties = dict(binary_acl_map_id = self.tbl_binary_acl_map.c.id,
-                                 fingerprint = relation(Fingerprint, backref="binary_acl_map"),
-                                 architecture = relation(Architecture)))
-
         mapper(Component, self.tbl_component,
                properties = dict(component_id = self.tbl_component.c.id,
                                  component_name = self.tbl_component.c.name),
@@ -2717,8 +2682,7 @@ class DBConn(object):
                                  uid = relation(Uid),
                                  keyring_id = self.tbl_fingerprint.c.keyring,
                                  keyring = relation(Keyring),
-                                 source_acl = relation(SourceACL),
-                                 binary_acl = relation(BinaryACL)),
+                                 acl = relation(ACL)),
                extension = validator)
 
         mapper(Keyring, self.tbl_keyrings,
@@ -2738,11 +2702,6 @@ class DBConn(object):
                                  date = self.tbl_changes.c.date,
                                  version = self.tbl_changes.c.version))
 
-        mapper(KeyringACLMap, self.tbl_keyring_acl_map,
-               properties = dict(keyring_acl_map_id = self.tbl_keyring_acl_map.c.id,
-                                 keyring = relation(Keyring, backref="keyring_acl_map"),
-                                 architecture = relation(Architecture)))
-
         mapper(Maintainer, self.tbl_maintainer,
                properties = dict(maintainer_id = self.tbl_maintainer.c.id,
                    maintains_sources = relation(DBSource, backref='maintainer',
@@ -2821,9 +2780,6 @@ class DBConn(object):
                                      collection_class=attribute_mapped_collection('key'))),
                extension = validator)
 
-        mapper(SourceACL, self.tbl_source_acl,
-               properties = dict(source_acl_id = self.tbl_source_acl.c.id))
-
         mapper(SrcFormat, self.tbl_src_format,
                properties = dict(src_format_id = self.tbl_src_format.c.id,
                                  format_name = self.tbl_src_format.c.format_name))
@@ -2831,11 +2787,13 @@ class DBConn(object):
         mapper(Suite, self.tbl_suite,
                properties = dict(suite_id = self.tbl_suite.c.id,
                                  policy_queue = relation(PolicyQueue, primaryjoin=(self.tbl_suite.c.policy_queue_id == self.tbl_policy_queue.c.id)),
+                                 new_queue = relation(PolicyQueue, primaryjoin=(self.tbl_suite.c.new_queue_id == self.tbl_policy_queue.c.id)),
                                  copy_queues = relation(BuildQueue,
                                      secondary=self.tbl_suite_build_queue_copy),
                                  srcformats = relation(SrcFormat, secondary=self.tbl_suite_src_formats,
                                      backref=backref('suites', lazy='dynamic')),
-                                 archive = relation(Archive, backref='suites')),
+                                 archive = relation(Archive, backref='suites'),
+                                 acls = relation(ACL, secondary=self.tbl_suite_acl_map, collection_class=set)),
                 extension = validator)
 
         mapper(Uid, self.tbl_uid,
@@ -2843,11 +2801,6 @@ class DBConn(object):
                                  fingerprint = relation(Fingerprint)),
                extension = validator)
 
-        mapper(UploadBlock, self.tbl_upload_blocks,
-               properties = dict(upload_block_id = self.tbl_upload_blocks.c.id,
-                                 fingerprint = relation(Fingerprint, backref="uploadblocks"),
-                                 uid = relation(Uid, backref="uploadblocks")))
-
         mapper(BinContents, self.tbl_bin_contents,
             properties = dict(
                 binary = relation(DBBinary,