]> git.decadent.org.uk Git - dak.git/blobdiff - daklib/command.py
Add by-hash support
[dak.git] / daklib / command.py
index a3092f6f1c72e2ef732f8270a65ef0fca8c1f3a1..867f7e3692589a5c26d1f1bc059cb40d7a62298f 100644 (file)
@@ -35,15 +35,15 @@ class CommandError(Exception):
     pass
 
 class CommandFile(object):
-    def __init__(self, path, log=None):
+    def __init__(self, filename, data, log=None):
         if log is None:
             from daklib.daklog import Logger
             log = Logger()
         self.cc = []
         self.result = []
         self.log = log
-        self.path = path
-        self.filename = os.path.basename(path)
+        self.filename = filename
+        self.data = data
 
     def _check_replay(self, signed_file, session):
         """check for replays
@@ -59,23 +59,36 @@ class CommandFile(object):
         session.add(signature_history)
         session.commit()
 
+    def _quote_section(self, section):
+        lines = []
+        for l in str(section).splitlines():
+            lines.append("> {0}".format(l))
+        return "\n".join(lines)
+
     def _evaluate_sections(self, sections, session):
         session.rollback()
         try:
-            sections.next()
-            section = sections.section
-
-            action = section.get('Action', None)
-            if action is None:
-                raise CommandError('Encountered section without Action field')
-            self.result.append('Action: {0}'.format(action))
-
-            if action == 'dm':
-                self.action_dm(self.fingerprint, section, session)
-            elif action == 'break-the-archive':
-                self.action_break_the_archive(self.fingerprint, section, session)
-            else:
-                raise CommandError('Unknown action: {0}'.format(action))
+            while True:
+                sections.next()
+                section = sections.section
+                self.result.append(self._quote_section(section))
+
+                action = section.get('Action', None)
+                if action is None:
+                    raise CommandError('Encountered section without Action field')
+
+                if action == 'dm':
+                    self.action_dm(self.fingerprint, section, session)
+                elif action == 'dm-remove':
+                    self.action_dm_remove(self.fingerprint, section, session)
+                elif action == 'dm-migrate':
+                    self.action_dm_migrate(self.fingerprint, section, session)
+                elif action == 'break-the-archive':
+                    self.action_break_the_archive(self.fingerprint, section, session)
+                else:
+                    raise CommandError('Unknown action: {0}'.format(action))
+
+                self.result.append('')
         except StopIteration:
             pass
         finally:
@@ -117,8 +130,7 @@ class CommandFile(object):
         keyrings = session.query(Keyring).filter_by(active=True).order_by(Keyring.priority)
         keyring_files = [ k.keyring_name for k in keyrings ]
 
-        raw_contents = open(self.path, 'r').read()
-        signed_file = SignedFile(raw_contents, keyring_files)
+        signed_file = SignedFile(self.data, keyring_files)
         if not signed_file.valid:
             self.log.log(['invalid signature', self.filename])
             return False
@@ -146,6 +158,8 @@ class CommandFile(object):
             section = sections.section
             if 'Uploader' in section:
                 self.uploader = section['Uploader']
+            if 'Cc' in section:
+                self.cc.append(section['Cc'])
             # TODO: Verify first section has valid Archive field
             if 'Archive' not in section:
                 raise CommandError('No Archive field in first section.')
@@ -163,7 +177,6 @@ class CommandFile(object):
         self._notify_uploader()
 
         session.close()
-        self.log.log(['done', self.filename])
 
         return result
 
@@ -189,7 +202,10 @@ class CommandFile(object):
         acl_name = cnf.get('Command::DM::ACL', 'dm')
         acl = session.query(ACL).filter_by(name=acl_name).one()
 
-        fpr = session.query(Fingerprint).filter_by(fingerprint=section['Fingerprint']).one()
+        fpr_hash = section['Fingerprint'].translate(None, ' ')
+        fpr = session.query(Fingerprint).filter_by(fingerprint=fpr_hash).first()
+        if fpr is None:
+            raise CommandError('Unknown fingerprint {0}'.format(fpr_hash))
         if fpr.keyring is None or fpr.keyring.keyring_name not in cnf.value_list('Command::DM::Keyrings'):
             raise CommandError('Key {0} is not in DM keyring.'.format(fpr.fingerprint))
         addresses = gpg_get_key_addresses(fpr.fingerprint)
@@ -233,6 +249,75 @@ class CommandFile(object):
 
         session.commit()
 
+    def _action_dm_admin_common(self, fingerprint, section, session):
+        cnf = Config()
+
+        if 'Command::DM-Admin::AdminFingerprints' not in cnf \
+                or 'Command::DM::ACL' not in cnf:
+            raise CommandError('DM admin command is not configured for this archive.')
+
+        allowed_fingerprints = cnf.value_list('Command::DM-Admin::AdminFingerprints')
+        if fingerprint.fingerprint not in allowed_fingerprints:
+            raise CommandError('Key {0} is not allowed to admin DM'.format(fingerprint.fingerprint))
+
+    def action_dm_remove(self, fingerprint, section, session):
+        self._action_dm_admin_common(fingerprint, section, session)
+
+        cnf = Config()
+        acl_name = cnf.get('Command::DM::ACL', 'dm')
+        acl = session.query(ACL).filter_by(name=acl_name).one()
+
+        fpr_hash = section['Fingerprint'].translate(None, ' ')
+        fpr = session.query(Fingerprint).filter_by(fingerprint=fpr_hash).first()
+        if fpr is None:
+            self.result.append('Unknown fingerprint: {0}\nNo action taken.'.format(fpr_hash))
+            return
+
+        self.log.log(['dm-remove', fpr.fingerprint])
+
+        count = 0
+        for entry in session.query(ACLPerSource).filter_by(acl=acl, fingerprint=fpr):
+            self.log.log(['dm-remove', fpr.fingerprint, 'source={0}'.format(entry.source)])
+            count += 1
+            session.delete(entry)
+
+        self.result.append('Removed: {0}.\n{1} acl entries removed.'.format(fpr.fingerprint, count))
+
+        session.commit()
+
+    def action_dm_migrate(self, fingerprint, section, session):
+        self._action_dm_admin_common(fingerprint, section, session)
+        cnf = Config()
+        acl_name = cnf.get('Command::DM::ACL', 'dm')
+        acl = session.query(ACL).filter_by(name=acl_name).one()
+
+        fpr_hash_from = section['From'].translate(None, ' ')
+        fpr_from = session.query(Fingerprint).filter_by(fingerprint=fpr_hash_from).first()
+        if fpr_from is None:
+            self.result.append('Unknown fingerprint (From): {0}\nNo action taken.'.format(fpr_hash_from))
+            return
+
+        fpr_hash_to = section['To'].translate(None, ' ')
+        fpr_to = session.query(Fingerprint).filter_by(fingerprint=fpr_hash_to).first()
+        if fpr_to is None:
+            self.result.append('Unknown fingerprint (To): {0}\nNo action taken.'.format(fpr_hash_to))
+            return
+        if fpr_to.keyring is None or fpr_to.keyring.keyring_name not in cnf.value_list('Command::DM::Keyrings'):
+            self.result.append('Key (To) {0} is not in DM keyring.\nNo action taken.'.format(fpr_to.fingerprint))
+            return
+
+        self.log.log(['dm-migrate', 'from={0}'.format(fpr_hash_from), 'to={0}'.format(fpr_hash_to)])
+
+        sources = []
+        for entry in session.query(ACLPerSource).filter_by(acl=acl, fingerprint=fpr_from):
+            self.log.log(['dm-migrate', 'from={0}'.format(fpr_hash_from), 'to={0}'.format(fpr_hash_to), 'source={0}'.format(entry.source)])
+            entry.fingerprint = fpr_to
+            sources.append(entry.source)
+
+        self.result.append('Migrated {0} to {1}.\n{2} acl entries changed: {3}'.format(fpr_hash_from, fpr_hash_to, len(sources), ", ".join(sources)))
+
+        session.commit()
+
     def action_break_the_archive(self, fingerprint, section, session):
         name = 'Dave'
         uid = fingerprint.uid