Code

Fix PGP implementation -- the pyme API has changed significantly since
[roundup.git] / roundup / mailgw.py
index 7a71d6a7017dc9e90705ec7b0c0335d600342696..d269554f8783216a05f51de73ed062c15fa28ca3 100644 (file)
@@ -92,7 +92,7 @@ from roundup.i18n import _
 from roundup.hyperdb import iter_roles
 
 try:
 from roundup.hyperdb import iter_roles
 
 try:
-    import pyme, pyme.core, pyme.gpgme
+    import pyme, pyme.core, pyme.constants, pyme.constants.sigsum
 except ImportError:
     pyme = None
 
 except ImportError:
     pyme = None
 
@@ -156,39 +156,31 @@ def gpgh_key_getall(key, attr):
     ''' return list of given attribute for all uids in
         a key
     '''
     ''' return list of given attribute for all uids in
         a key
     '''
-    u = key.uids
-    while u:
+    for u in key.uids:
         yield getattr(u, attr)
         yield getattr(u, attr)
-        u = u.next
 
 
-def gpgh_sigs(sig):
-    ''' more pythonic iteration over GPG signatures '''
-    while sig:
-        yield sig
-        sig = sig.next
-
-def check_pgp_sigs(sig, gpgctx, author):
+def check_pgp_sigs(sigs, gpgctx, author):
     ''' Theoretically a PGP message can have several signatures. GPGME
     ''' Theoretically a PGP message can have several signatures. GPGME
-        returns status on all signatures in a linked list. Walk that
-        linked list looking for the author's signature
+        returns status on all signatures in a list. Walk that list
+        looking for the author's signature
     '''
     '''
-    for sig in gpgh_sigs(sig):
+    for sig in sigs:
         key = gpgctx.get_key(sig.fpr, False)
         # we really only care about the signature of the user who
         # submitted the email
         if key and (author in gpgh_key_getall(key, 'email')):
         key = gpgctx.get_key(sig.fpr, False)
         # we really only care about the signature of the user who
         # submitted the email
         if key and (author in gpgh_key_getall(key, 'email')):
-            if sig.summary & pyme.gpgme.GPGME_SIGSUM_VALID:
+            if sig.summary & pyme.constants.sigsum.VALID:
                 return True
             else:
                 # try to narrow down the actual problem to give a more useful
                 # message in our bounce
                 return True
             else:
                 # try to narrow down the actual problem to give a more useful
                 # message in our bounce
-                if sig.summary & pyme.gpgme.GPGME_SIGSUM_KEY_MISSING:
+                if sig.summary & pyme.constants.sigsum.KEY_MISSING:
                     raise MailUsageError, \
                         _("Message signed with unknown key: %s") % sig.fpr
                     raise MailUsageError, \
                         _("Message signed with unknown key: %s") % sig.fpr
-                elif sig.summary & pyme.gpgme.GPGME_SIGSUM_KEY_EXPIRED:
+                elif sig.summary & pyme.constants.sigsum.KEY_EXPIRED:
                     raise MailUsageError, \
                         _("Message signed with an expired key: %s") % sig.fpr
                     raise MailUsageError, \
                         _("Message signed with an expired key: %s") % sig.fpr
-                elif sig.summary & pyme.gpgme.GPGME_SIGSUM_KEY_REVOKED:
+                elif sig.summary & pyme.constants.sigsum.KEY_REVOKED:
                     raise MailUsageError, \
                         _("Message signed with a revoked key: %s") % sig.fpr
                 else:
                     raise MailUsageError, \
                         _("Message signed with a revoked key: %s") % sig.fpr
                 else:
@@ -511,7 +503,6 @@ class Message(mimetools.Message):
             raise MailUsageError, \
                 _("No PGP signature found in message.")
 
             raise MailUsageError, \
                 _("No PGP signature found in message.")
 
-        context = pyme.core.Context()
         # msg.getbody() is skipping over some headers that are
         # required to be present for verification to succeed so
         # we'll do this by hand
         # msg.getbody() is skipping over some headers that are
         # required to be present for verification to succeed so
         # we'll do this by hand
@@ -526,6 +517,7 @@ class Message(mimetools.Message):
         msg_data = pyme.core.Data(canonical_msg)
         sig_data = pyme.core.Data(sig.getbody())
 
         msg_data = pyme.core.Data(canonical_msg)
         sig_data = pyme.core.Data(sig.getbody())
 
+        context = pyme.core.Context()
         context.op_verify(sig_data, msg_data, None)
 
         # check all signatures for validity
         context.op_verify(sig_data, msg_data, None)
 
         # check all signatures for validity
@@ -862,7 +854,7 @@ Unknown address: %(from_address)s
                     'You are not permitted to access this tracker.')
         self.author = author
 
                     'You are not permitted to access this tracker.')
         self.author = author
 
-    def check_node_permissions(self):
+    def check_permissions(self):
         ''' Check if the author has permission to edit or create this
             class of node
         '''
         ''' Check if the author has permission to edit or create this
             class of node
         '''
@@ -995,7 +987,7 @@ Subject was: "%(subject)s"
             """
             if self.config.PGP_ROLES:
                 return self.db.user.has_role(self.author,
             """
             if self.config.PGP_ROLES:
                 return self.db.user.has_role(self.author,
-                    iter_roles(self.config.PGP_ROLES))
+                    *iter_roles(self.config.PGP_ROLES))
             else:
                 return True
 
             else:
                 return True
 
@@ -1155,6 +1147,71 @@ There was a problem with the message you sent:
 
         return self.nodeid
 
 
         return self.nodeid
 
+        # XXX Don't enable. This doesn't work yet.
+#  "[^A-z.]tracker\+(?P<classname>[^\d\s]+)(?P<nodeid>\d+)\@some.dom.ain[^A-z.]"
+        # handle delivery to addresses like:tracker+issue25@some.dom.ain
+        # use the embedded issue number as our issue
+#            issue_re = config['MAILGW_ISSUE_ADDRESS_RE']
+#            if issue_re:
+#                for header in ['to', 'cc', 'bcc']:
+#                    addresses = message.getheader(header, '')
+#                if addresses:
+#                  # FIXME, this only finds the first match in the addresses.
+#                    issue = re.search(issue_re, addresses, 'i')
+#                    if issue:
+#                        classname = issue.group('classname')
+#                        nodeid = issue.group('nodeid')
+#                        break
+
+    # Default sequence of methods to be called on message. Use this for
+    # easier override of the default message processing
+    # list consists of tuples (method, return_if_true), the parsing
+    # returns if the return_if_true flag is set for a method *and* the
+    # method returns something that evaluates to True.
+    method_list = [
+        # Filter out messages to ignore
+        (handle_ignore, False),
+        # Check for usage/help requests
+        (handle_help, False),
+        # Check if the subject line is valid
+        (check_subject, False),
+        # get importants parts from subject
+        (parse_subject, False),
+        # check for registration OTK
+        (rego_confirm, True),
+        # get the classname
+        (get_classname, False),
+        # get the optional nodeid:
+        (get_nodeid, False),
+        # Determine who the author is:
+        (get_author_id, False),
+        # allowed to edit or create this class?
+        (check_permissions, False),
+        # author may have been created:
+        # commit author to database and re-open as author
+        (commit_and_reopen_as_author, False),
+        # Get the recipients list
+        (get_recipients, False),
+        # get the new/updated node props
+        (get_props, False),
+        # Handle PGP signed or encrypted messages
+        (get_pgp_message, False),
+        # extract content and attachments from message body:
+        (get_content_and_attachments, False),
+        # put attachments into files linked to the issue:
+        (create_files, False),
+        # create the message if there's a message body (content):
+        (create_msg, False),
+    ]
+
+
+    def parse (self):
+        for method, flag in self.method_list:
+            ret = method(self)
+            if flag and ret:
+                return
+        # perform the node change / create:
+        return self.create_node()
 
 
 class MailGW:
 
 
 class MailGW:
@@ -1370,6 +1427,7 @@ class MailGW:
         # in some rare cases, a particularly stuffed-up e-mail will make
         # its way into here... try to handle it gracefully
 
         # in some rare cases, a particularly stuffed-up e-mail will make
         # its way into here... try to handle it gracefully
 
+        self.parsed_message = None
         sendto = message.getaddrlist('resent-from')
         if not sendto:
             sendto = message.getaddrlist('from')
         sendto = message.getaddrlist('resent-from')
         if not sendto:
             sendto = message.getaddrlist('from')
@@ -1459,77 +1517,8 @@ class MailGW:
         The following code expects an opened database and a try/finally
         that closes the database.
         '''
         The following code expects an opened database and a try/finally
         that closes the database.
         '''
-        parsed_message = self.parsed_message_class(self, message)
-
-        # Filter out messages to ignore
-        parsed_message.handle_ignore()
-        
-        # Check for usage/help requests
-        parsed_message.handle_help()
-        
-        # Check if the subject line is valid
-        parsed_message.check_subject()
-
-        # XXX Don't enable. This doesn't work yet.
-        # XXX once this works it should be moved to parsedMessage class
-#  "[^A-z.]tracker\+(?P<classname>[^\d\s]+)(?P<nodeid>\d+)\@some.dom.ain[^A-z.]"
-        # handle delivery to addresses like:tracker+issue25@some.dom.ain
-        # use the embedded issue number as our issue
-#            issue_re = config['MAILGW_ISSUE_ADDRESS_RE']
-#            if issue_re:
-#                for header in ['to', 'cc', 'bcc']:
-#                    addresses = message.getheader(header, '')
-#                if addresses:
-#                  # FIXME, this only finds the first match in the addresses.
-#                    issue = re.search(issue_re, addresses, 'i')
-#                    if issue:
-#                        classname = issue.group('classname')
-#                        nodeid = issue.group('nodeid')
-#                        break
-
-        # Parse the subject line to get the importants parts
-        parsed_message.parse_subject()
-
-        # check for registration OTK
-        if parsed_message.rego_confirm():
-            return
-
-        # get the classname
-        parsed_message.get_classname()
-
-        # get the optional nodeid
-        parsed_message.get_nodeid()
-
-        # Determine who the author is
-        parsed_message.get_author_id()
-        
-        # make sure they're allowed to edit or create this class
-        parsed_message.check_node_permissions()
-
-        # author may have been created:
-        # commit author to database and re-open as author
-        parsed_message.commit_and_reopen_as_author()
-
-        # Get the recipients list
-        parsed_message.get_recipients()
-
-        # get the new/updated node props
-        parsed_message.get_props()
-
-        # Handle PGP signed or encrypted messages
-        parsed_message.get_pgp_message()
-
-        # extract content and attachments from message body
-        parsed_message.get_content_and_attachments()
-
-        # put attachments into files linked to the issue
-        parsed_message.create_files()
-        
-        # create the message if there's a message body (content)
-        parsed_message.create_msg()
-            
-        # perform the node change / create
-        nodeid = parsed_message.create_node()
+        self.parsed_message = self.parsed_message_class(self, message)
+        nodeid = self.parsed_message.parse ()
 
         # commit the changes to the DB
         self.db.commit()
 
         # commit the changes to the DB
         self.db.commit()