From: Ansgar Burchardt Date: Sun, 2 Sep 2012 10:07:56 +0000 (+0200) Subject: Add suite ACLs and per-suite NEW. X-Git-Url: https://git.decadent.org.uk/gitweb/?a=commitdiff_plain;h=707a89a3b86961755a99cb9e1a0a5f23690f9529;p=dak.git Add suite ACLs and per-suite NEW. --- diff --git a/dak/acl.py b/dak/acl.py new file mode 100644 index 00000000..f38a3a60 --- /dev/null +++ b/dak/acl.py @@ -0,0 +1,83 @@ +#! /usr/bin/env python +# +# Copyright (C) 2012, Ansgar Burchardt +# +# 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 + +Reads list of fingerprints from stdin and sets the ACL to these. +""" + +def get_fingerprint(entry, session): + """get fingerprint for given ACL entry + + The entry is a string in one of these formats:: + + uid: + name: + fpr: + + @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) diff --git a/dak/dak.py b/dak/dak.py index 7e78bc29..306137a1 100755 --- a/dak/dak.py +++ b/dak/dak.py @@ -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 index 00000000..f0707d58 --- /dev/null +++ b/dak/dakdb/update83.py @@ -0,0 +1,219 @@ +#!/usr/bin/env python +# coding=utf8 + +""" +switch to new ACL implementation and add pre-suite NEW + +@contact: Debian FTP Master +@copyright: 2012 Ansgar Burchardt +@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)) diff --git a/dak/import_keyring.py b/dak/import_keyring.py index 89c2b755..ff0cb98e 100755 --- a/dak/import_keyring.py +++ b/dak/import_keyring.py @@ -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, diff --git a/dak/update_db.py b/dak/update_db.py index 3a33c976..cf327b0b 100755 --- a/dak/update_db.py +++ b/dak/update_db.py @@ -46,7 +46,7 @@ from daklib.daklog import Logger ################################################################################ Cnf = None -required_database_schema = 85 +required_database_schema = 86 ################################################################################ diff --git a/daklib/archive.py b/daklib/archive.py index fdd7cd7d..5c98eeca 100644 --- a/daklib/archive.py +++ b/daklib/archive.py @@ -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) diff --git a/daklib/checks.py b/daklib/checks.py index 770615aa..81bd629e 100644 --- a/daklib/checks.py +++ b/daklib/checks.py @@ -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): diff --git a/daklib/dbconn.py b/daklib/dbconn.py index 293f4dcb..9617bb77 100644 --- a/daklib/dbconn.py +++ b/daklib/dbconn.py @@ -369,6 +369,20 @@ validator = Validator() ################################################################################ +class ACL(ORMObject): + def __repr__(self): + return "".format(self.name) + +__all__.append('ACL') + +class ACLPerSource(ORMObject): + def __repr__(self): + return "".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 '' % self.binary_acl_id - -__all__.append('BinaryACL') - -################################################################################ - -class BinaryACLMap(object): - def __init__(self, *args, **kwargs): - pass - - def __repr__(self): - return '' % 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 '' % 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 '' % 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 '' % (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,