提交 df6e3e5f 编写于 作者: J Joerg Jaspert

Merge remote-tracking branch 'ansgar/pu/backports-merge' into merge

* ansgar/pu/backports-merge:
  daklib/checks.py: add note to send warning for DMUA later
  daklib/checks.py: restore comment about hijack check
  daklib/checks.py: include all not allowed binary architectures in error.
  Add suite ACLs and per-suite NEW.
  daklib/upload.py (Changes): add source_name property
  daklib/archive.py: set final_suites earlier.
  daklib/checks.py: use final_suites for DM check
  daklib/archive.py: look for target suites earlier
  add per-suite close_bugs option
  Add per-suite database permissions.
  setup/dak-minimal.conf.template: add missing Dir::Base
Signed-off-by: NJoerg Jaspert <joerg@debian.org>
#! /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)
......@@ -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",
......
#!/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))
#!/usr/bin/env python
# coding=utf8
"""
add per-suite database permissions
@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 = [
"""
CREATE TABLE suite_permission (
suite_id INT NOT NULL REFERENCES suite(id) ON DELETE CASCADE,
role TEXT NOT NULL,
PRIMARY KEY (suite_id, role)
)
""",
"""
CREATE OR REPLACE FUNCTION has_suite_permission(action TEXT, suite_id INT)
RETURNS BOOLEAN
STABLE
STRICT
SET search_path = public, pg_temp
LANGUAGE plpgsql
AS $$
DECLARE
v_result BOOLEAN;
BEGIN
IF pg_has_role('ftpteam', 'USAGE') THEN
RETURN 't';
END IF;
SELECT BOOL_OR(pg_has_role(sp.role, 'USAGE')) INTO v_result
FROM suite_permission sp
WHERE sp.suite_id = has_suite_permission.suite_id
GROUP BY sp.suite_id;
IF v_result IS NULL THEN
v_result := 'f';
END IF;
RETURN v_result;
END;
$$
""",
"""
CREATE OR REPLACE FUNCTION trigger_check_suite_permission() RETURNS TRIGGER
SET search_path = public, pg_temp
LANGUAGE plpgsql
AS $$
DECLARE
v_row RECORD;
v_suite_name suite.suite_name%TYPE;
BEGIN
CASE TG_OP
WHEN 'INSERT', 'UPDATE' THEN
v_row := NEW;
WHEN 'DELETE' THEN
v_row := OLD;
ELSE
RAISE EXCEPTION 'Unexpected TG_OP (%)', TG_OP;
END CASE;
IF TG_OP = 'UPDATE' AND OLD.suite != NEW.suite THEN
RAISE EXCEPTION 'Cannot change suite';
END IF;
IF NOT has_suite_permission(TG_OP, v_row.suite) THEN
SELECT suite_name INTO STRICT v_suite_name FROM suite WHERE id = v_row.suite;
RAISE EXCEPTION 'Not allowed to % in %', TG_OP, v_suite_name;
END IF;
RETURN v_row;
END;
$$
""",
"""
CREATE CONSTRAINT TRIGGER trigger_override_permission
AFTER INSERT OR UPDATE OR DELETE
ON override
FOR EACH ROW
EXECUTE PROCEDURE trigger_check_suite_permission()
""",
"""
CREATE CONSTRAINT TRIGGER trigger_src_associations_permission
AFTER INSERT OR UPDATE OR DELETE
ON src_associations
FOR EACH ROW
EXECUTE PROCEDURE trigger_check_suite_permission()
""",
"""
CREATE CONSTRAINT TRIGGER trigger_bin_associations_permission
AFTER INSERT OR UPDATE OR DELETE
ON bin_associations
FOR EACH ROW
EXECUTE PROCEDURE trigger_check_suite_permission()
""",
]
################################################################################
def do_update(self):
print __doc__
try:
cnf = Config()
c = self.db.cursor()
for stmt in statements:
c.execute(stmt)
c.execute("UPDATE config SET value = '84' WHERE name = 'db_revision'")
self.db.commit()
except psycopg2.ProgrammingError as msg:
self.db.rollback()
raise DBUpdateError('Unable to apply sick update 84, rollback issued. Error message: {0}'.format(msg))
#!/usr/bin/env python
# coding=utf8
"""
add per-suite close_bugs option
@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
def do_update(self):
print __doc__
try:
cnf = Config()
c = self.db.cursor()
c.execute("ALTER TABLE suite ADD COLUMN close_bugs BOOLEAN")
c.execute("UPDATE config SET value = '85' WHERE name = 'db_revision'")
self.db.commit()
except psycopg2.ProgrammingError as msg:
self.db.rollback()
raise DBUpdateError('Unable to apply sick update 85, rollback issued. Error message: {0}'.format(msg))
......@@ -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,
......
......@@ -46,7 +46,7 @@ from daklib.daklog import Logger
################################################################################
Cnf = None
required_database_schema = 82
required_database_schema = 86
################################################################################
......
......@@ -143,7 +143,9 @@ def announce_accept(upload):
message = TemplateSubst(my_subst, os.path.join(cnf['Dir::Templates'], 'process-unchecked.announce'))
send_mail(message)
if accepted_to_real_suite and upload.sourceful and cnf.find_b('Dinstall::CloseBugs'):
close_bugs_default = cnf.find_b('Dinstall::CloseBugs')
close_bugs = any(s.close_bugs if s.close_bugs is not None else close_bugs_default for s in upload.suites)
if accepted_to_real_suite and upload.sourceful and close_bugs:
for bug in upload.bugs:
my_subst = subst.copy()
my_subst['__BUG_NUMBER__'] = str(bug)
......
......@@ -595,6 +595,11 @@ class ArchiveUpload(object):
@type: bool
"""
self._checked = False
"""checks passes. set by C{check}
@type: bool
"""
self._new_queue = self.session.query(PolicyQueue).filter_by(queue_name='new').one()
self._new = self._new_queue.suite
......@@ -850,28 +855,35 @@ class ArchiveUpload(object):
assert self.changes.valid_signature
try:
# Validate signatures and hashes before we do any real work:
for chk in (
checks.SignatureCheck,
checks.ChangesCheck,
checks.TransitionCheck,
checks.UploadBlockCheck,
checks.HashesCheck,
checks.SourceCheck,
checks.BinaryCheck,
checks.BinaryTimestampCheck,
checks.ACLCheck,
checks.SingleDistributionCheck,
checks.NoSourceOnlyCheck,
checks.LintianCheck,
):
chk().check(self)
final_suites = self._final_suites()
if len(final_suites) == 0:
self.reject_reasons.append('Ended with no suite to install to.')
self.reject_reasons.append('No target suite found. Please check your target distribution and that you uploaded to the right archive.')
return False
self.final_suites = final_suites
for chk in (
checks.TransitionCheck,
checks.ACLCheck,
checks.NoSourceOnlyCheck,
checks.LintianCheck,
):
chk().check(self)
for chk in (
checks.ACLCheck,
checks.SourceFormatCheck,
checks.SuiteArchitectureCheck,
checks.VersionCheck,
......@@ -882,7 +894,7 @@ class ArchiveUpload(object):
if len(self.reject_reasons) != 0:
return False
self.final_suites = final_suites
self._checked = True
return True
except checks.Reject as e:
self.reject_reasons.append(unicode(e))
......@@ -999,6 +1011,7 @@ class ArchiveUpload(object):
assert len(self.reject_reasons) == 0
assert self.changes.valid_signature
assert self.final_suites is not None
assert self._checked
byhand = self.changes.byhand_files
if len(byhand) == 0:
......@@ -1110,6 +1123,7 @@ class ArchiveUpload(object):
assert len(self.reject_reasons) == 0
assert self.changes.valid_signature
assert self.final_suites is not None
assert self._checked
assert not self.new
db_changes = self._install_changes()
......@@ -1159,15 +1173,21 @@ class ArchiveUpload(object):
binaries = self.changes.binaries
byhand = self.changes.byhand_files
# 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
# 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]
def binary_component_func(binary):
return self._binary_component(suite, binary, only_overrides=False)
......
......@@ -352,28 +352,80 @@ 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):
# 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']:
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)
forbidden_arches = uploaded_arches - allowed_arches
if len(forbidden_arches) != 0:
return False, "uploads for architecture(s) {0} are not allowed".format(", ".join(forbidden_arches))
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.
# XXX: Send warning mail once users can set the new DMUA flag
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
distributions = upload.changes.distributions
assert len(distributions) == 1
suite = session.query(Suite).filter_by(suite_name=distributions[0]).one()
overridesuite = suite
if suite.overridesuite is not None:
overridesuite = session.query(Suite).filter_by(suite_name=suite.overridesuite).one()
if upload._check_new(overridesuite):
raise Reject('Uploading NEW packages is not allowed for DMs.')
# Check DM-Upload-Allowed
suites = upload.final_suites
assert len(suites) == 1
suite = list(suites)[0]
last_suites = ['unstable', 'experimental']
if suite.suite_name.endswith('-backports'):
last_suites = [suite.suite_name]
......@@ -381,84 +433,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) \
.join(DBBinary.suites).filter(Suite.suite_name.in_(upload.changes.distributions)) \
.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 ]
keyring = fingerprint.keyring
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))
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):
......
......@@ -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,
......
......@@ -269,6 +269,13 @@ class Changes(object):
self._source = Source(self.directory, source_files, self._keyrings, self._require_signature)
return self._source
@property
def source_name(self):
"""source package name
@type: str
"""
return re_field_source.match(self.changes['Source']).group('package')
@property
def binaries(self):
"""included binary packages
......
......@@ -37,6 +37,7 @@ Dinstall
Dir
{
Base "__DAKBASE__";
Root "__DAKBASE__/ftp/";
Pool "__DAKBASE__/ftp/pool/";
Templates "__DAKBASE__/templates/";
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册