← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~mbp/launchpad/dkim into lp:launchpad

 

You have been requested to review the proposed merge of lp:~mbp/launchpad/dkim into lp:launchpad.

This fixes a few more things related to authentication of incoming mail:

 * gpg signature timestamp checks should cover all mail, not just that to malone (bug 643200)

 * checks on mail to new@ should make sure it's strongly authenticated, not specifically that it has a gpg signature (bug 643219)

 * there was no test that gpg mail with implausible timestamps was actually rejected afaics

I haven't interactively tested this and I'm not utterly confident in the existing test coverage, so please review carefully and test it interactively yourself if you know how.

MaloneHandler.process was a bit large so I split out the code that decides what if any commands will be executed.

Thanks

-- 
https://code.launchpad.net/~mbp/launchpad/dkim/+merge/35985
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~mbp/launchpad/dkim into lp:launchpad.
=== renamed file 'lib/canonical/launchpad/mail/errortemplates/not-gpg-signed.txt' => 'lib/canonical/launchpad/mail/errortemplates/unauthenticated-bug-creation.txt'
=== modified file 'lib/canonical/launchpad/mail/handlers.py'
--- lib/canonical/launchpad/mail/handlers.py	2010-10-03 15:30:06 +0000
+++ lib/canonical/launchpad/mail/handlers.py	2010-10-15 09:21:30 +0000
@@ -32,14 +32,12 @@
     ISpecificationSet,
     QuestionStatus,
     )
-from canonical.launchpad.interfaces.gpghandler import IGPGHandler
 from canonical.launchpad.mail.commands import (
     BugEmailCommands,
     get_error_message,
     )
 from canonical.launchpad.mail.helpers import (
     ensure_not_weakly_authenticated,
-    ensure_sane_signature_timestamp,
     get_main_body,
     guess_bugtask,
     IncomingEmailError,
@@ -59,16 +57,6 @@
     )
 
 
-def extract_signature_timestamp(signed_msg):
-    # break import cycle
-    from canonical.launchpad.mail.incoming import (
-        canonicalise_line_endings)
-    signature = getUtility(IGPGHandler).getVerifiedSignature(
-        canonicalise_line_endings(signed_msg.signedContent),
-        signed_msg.signature)
-    return signature.timestamp
-
-
 class MaloneHandler:
     """Handles emails sent to Malone.
 
@@ -88,47 +76,65 @@
                 name, args in parse_commands(content,
                                              BugEmailCommands.names())]
 
-    def process(self, signed_msg, to_addr, filealias=None, log=None,
-                extract_signature_timestamp=extract_signature_timestamp):
-        """See IMailHandler."""
+    def extractAndAuthenticateCommands(self, signed_msg, to_addr):
+        """Extract commands and handle special destinations.
+
+        NB: The authentication is carried out against the current principal,
+        not directly against the message.  authenticateEmail must previously
+        have been called on this thread.
+
+        :returns: (final_result, add_comment_to_bug, commands)
+            If final_result is non-none, stop processing and return this value
+            to indicate whether the message was dealt with or not.
+            If add_comment_to_bug, add the contents to the first bug
+            selected.
+            commands is a list of bug commands.
+        """
+        CONTEXT = 'bug report'
         commands = self.getCommands(signed_msg)
-        user, host = to_addr.split('@')
+        to_user, to_host = to_addr.split('@')
         add_comment_to_bug = False
-        signature = signed_msg.signature
+        if len(commands) > 0:
+            # If there are any commands, we must have strong authentication.
+            # We send a different failure message for attempts to create a new
+            # bug.
+            if to_user.lower() == 'new':
+                ensure_not_weakly_authenticated(signed_msg, CONTEXT,
+                    'unauthenticated-bug-creation.txt')
+            else:
+                ensure_not_weakly_authenticated(signed_msg, CONTEXT)
+        if to_user.lower() == 'new':
+            commands.insert(0, BugEmailCommands.get('bug', ['new']))
+        elif to_user.isdigit():
+            # A comment to a bug. We set add_comment_to_bug to True so
+            # that the comment gets added to the bug later. We don't add
+            # the comment now, since we want to let the 'bug' command
+            # handle the possible errors that can occur while getting
+            # the bug.
+            add_comment_to_bug = True
+            commands.insert(0, BugEmailCommands.get('bug', [to_user]))
+        elif to_user.lower() == 'help':
+            from_user = getUtility(ILaunchBag).user
+            if from_user is not None:
+                preferredemail = from_user.preferredemail
+                if preferredemail is not None:
+                    to_address = str(preferredemail.email)
+                    self.sendHelpEmail(to_address)
+            return True, False, None
+        elif to_user.lower() != 'edit':
+            # Indicate that we didn't handle the mail.
+            return False, False, None
+        return None, add_comment_to_bug, commands
+
+    def process(self, signed_msg, to_addr, filealias=None, log=None):
+        """See IMailHandler."""
 
         try:
-            if len(commands) > 0:
-                CONTEXT = 'bug report'
-                ensure_not_weakly_authenticated(signed_msg, CONTEXT)
-                if signature is not None:
-                    ensure_sane_signature_timestamp(
-                        extract_signature_timestamp(signed_msg), CONTEXT)
-
-            if user.lower() == 'new':
-                # A submit request.
-                commands.insert(0, BugEmailCommands.get('bug', ['new']))
-                if signature is None:
-                    raise IncomingEmailError(
-                        get_error_message('not-gpg-signed.txt'))
-            elif user.isdigit():
-                # A comment to a bug. We set add_comment_to_bug to True so
-                # that the comment gets added to the bug later. We don't add
-                # the comment now, since we want to let the 'bug' command
-                # handle the possible errors that can occur while getting
-                # the bug.
-                add_comment_to_bug = True
-                commands.insert(0, BugEmailCommands.get('bug', [user]))
-            elif user.lower() == 'help':
-                from_user = getUtility(ILaunchBag).user
-                if from_user is not None:
-                    preferredemail = from_user.preferredemail
-                    if preferredemail is not None:
-                        to_address = str(preferredemail.email)
-                        self.sendHelpEmail(to_address)
-                return True
-            elif user.lower() != 'edit':
-                # Indicate that we didn't handle the mail.
-                return False
+            (final_result, add_comment_to_bug,
+                commands, ) = self.extractAndAuthenticateCommands(
+                    signed_msg, to_addr)
+            if final_result is not None:
+                return final_result
 
             bug = None
             bug_event = None

=== modified file 'lib/canonical/launchpad/mail/helpers.py'
--- lib/canonical/launchpad/mail/helpers.py	2010-10-03 15:30:06 +0000
+++ lib/canonical/launchpad/mail/helpers.py	2010-10-15 09:21:30 +0000
@@ -211,7 +211,14 @@
 def ensure_not_weakly_authenticated(signed_msg, context,
                                     error_template='not-signed.txt',
                                     no_key_template='key-not-registered.txt'):
-    """Make sure that the current principal is not weakly authenticated."""
+    """Make sure that the current principal is not weakly authenticated.
+
+    NB: While handling an email, the authentication state is stored partly in
+    properties of the message object, and partly in the current security
+    principal.  As a consequence this function will only work correctly if the
+    message has just been passed through authenticateEmail -- you can't give
+    it an arbitrary message object.
+    """
     cur_principal = get_current_principal()
     # The security machinery doesn't know about
     # IWeaklyAuthenticatedPrincipal yet, so do a manual
@@ -232,7 +239,10 @@
 
 def ensure_sane_signature_timestamp(timestamp, context,
                                     error_template='old-signature.txt'):
-    """Ensure the signature was generated recently but not in the future."""
+    """Ensure the signature was generated recently but not in the future.
+
+    :raises IncomingEmailError: if the timestamp is stale or implausible.
+    """
     fourty_eight_hours = 48 * 60 * 60
     ten_minutes = 10 * 60
     now = time.time()

=== modified file 'lib/canonical/launchpad/mail/incoming.py'
--- lib/canonical/launchpad/mail/incoming.py	2010-10-03 15:30:06 +0000
+++ lib/canonical/launchpad/mail/incoming.py	2010-10-15 09:21:30 +0000
@@ -38,6 +38,9 @@
     )
 from canonical.launchpad.mail.commands import get_error_message
 from canonical.launchpad.mail.handlers import mail_handlers
+from canonical.launchpad.mail.helpers import (
+    ensure_sane_signature_timestamp,
+    )
 from canonical.launchpad.mailnotification import (
     send_process_error_notification,
     )
@@ -149,7 +152,7 @@
             % (signing_domain, from_domain))
         return False
     if not _isDkimDomainTrusted(signing_domain):
-        log.warning("valid DKIM signature from untrusted domain %s" 
+        log.warning("valid DKIM signature from untrusted domain %s"
             % (signing_domain,))
         return False
     return True
@@ -159,10 +162,12 @@
     """Authenticates an email by verifying the PGP signature.
 
     The mail is expected to be an ISignedMessage.
+
+    If this completes, it will set the current security principal to be the
+    message sender.
     """
 
     signature = mail.signature
-    signed_content = mail.signedContent
 
     name, email_addr = parseaddr(mail['From'])
     authutil = getUtility(IPlacelessAuthUtility)
@@ -202,11 +207,15 @@
     gpghandler = getUtility(IGPGHandler)
     try:
         sig = gpghandler.getVerifiedSignature(
-            canonicalise_line_endings(signed_content), signature)
+            canonicalise_line_endings(mail.signedContent), signature)
     except GPGVerificationError, e:
         # verifySignature failed to verify the signature.
         raise InvalidSignature("Signature couldn't be verified: %s" % str(e))
 
+    ensure_sane_signature_timestamp(
+        sig.timestamp,
+        'incoming mail verification')
+
     for gpgkey in person.gpg_keys:
         if gpgkey.fingerprint == sig.fingerprint:
             break

=== modified file 'lib/canonical/launchpad/mail/tests/test_handlers.py'
--- lib/canonical/launchpad/mail/tests/test_handlers.py	2010-10-04 19:50:45 +0000
+++ lib/canonical/launchpad/mail/tests/test_handlers.py	2010-10-15 09:21:30 +0000
@@ -8,6 +8,7 @@
 from doctest import DocTestSuite
 import email
 import time
+import transaction
 import unittest
 
 from canonical.database.sqlbase import commit
@@ -44,6 +45,66 @@
         self.assertEqual('bug', commands[0].name)
         self.assertEqual(['foo'], commands[0].string_args)
 
+    def test_NonGPGAuthenticatedNewBug(self):
+        """Mail authenticated other than by gpg can create bugs.
+
+        The incoming mail layer is responsible for authenticating the mail,
+        and setting the current principal to the sender of the mail, either
+        weakly or non-weakly authenticated.  At the layer of the handler,
+        which this class is testing, we shouldn't care by what mechanism we
+        decided to act on behalf of the mail sender, only that we did.
+
+        In bug 643219, Launchpad had a problem where the MaloneHandler code
+        was puncturing that abstraction and directly looking at the GPG
+        signature; this test checks it's fixed.
+        """
+        # NB SignedMessage by default isn't actually signed, it just has the
+        # capability of knowing about signing.
+        message = self.factory.makeSignedMessage(body='  affects malone\nhi!')
+        self.assertEquals(message.signature, None)
+
+        # Pretend that the mail auth has given us a logged-in user.
+        handler = MaloneHandler()
+        with person_logged_in(self.factory.makePerson()):
+            mail_handled, add_comment_to_bug, commands = \
+                handler.extractAndAuthenticateCommands(message,
+                    'new@xxxxxxxxxxxxxxxxxx')
+        self.assertEquals(mail_handled, None)
+        self.assertEquals(map(str, commands), [
+            'bug new',
+            'affects malone',
+            ])
+
+    def test_mailToHelpFromUnknownUser(self):
+        """Mail from people of no account to help@ is simply dropped.
+        """
+        message = self.factory.makeSignedMessage()
+        handler = MaloneHandler()
+        mail_handled, add_comment_to_bug, commands = \
+            handler.extractAndAuthenticateCommands(message,
+                'help@xxxxxxxxxxxxxxxxxx')
+        self.assertEquals(mail_handled, True)
+        self.assertEquals(self.getSentMail(), [])
+
+    def test_mailToHelp(self):
+        """Mail to help@ generates a help command."""
+        message = self.factory.makeSignedMessage()
+        handler = MaloneHandler()
+        with person_logged_in(self.factory.makePerson()):
+            mail_handled, add_comment_to_bug, commands = \
+                handler.extractAndAuthenticateCommands(message,
+                    'help@xxxxxxxxxxxxxxxxxx')
+        self.assertEquals(mail_handled, True)
+        self.assertEquals(len(self.getSentMail()), 1)
+        # TODO: Check the right mail was sent. -- mbp 20100923
+
+    def getSentMail(self):
+        # Sending mail is (unfortunately) a side effect of parsing the
+        # commands, and unfortunately you must commit the transaction to get
+        # them sent.
+        transaction.commit()
+        return stub.test_emails[:]
+
 
 class FakeSignature:
 

=== modified file 'lib/canonical/launchpad/mail/tests/test_incoming.py'
--- lib/canonical/launchpad/mail/tests/test_incoming.py	2010-10-04 19:50:45 +0000
+++ lib/canonical/launchpad/mail/tests/test_incoming.py	2010-10-15 09:21:30 +0000
@@ -7,15 +7,29 @@
 
 import transaction
 
+from canonical.launchpad.ftests import import_secret_test_key
+from canonical.launchpad.interfaces import (
+    IWeaklyAuthenticatedPrincipal,
+    )
 from canonical.launchpad.mail.ftests.helpers import testmails_path
+from canonical.launchpad.mail import (
+    helpers,
+    )
 from canonical.launchpad.mail.incoming import (
+    authenticateEmail,
     handleMail,
     MailErrorUtility,
     )
+<<<<<<< TREE
 from canonical.testing.layers import LaunchpadZopelessLayer
+=======
+from canonical.launchpad.webapp.interaction import get_current_principal
+from canonical.testing import LaunchpadZopelessLayer
+>>>>>>> MERGE-SOURCE
 from lp.services.mail.sendmail import MailController
 from lp.services.mail.stub import TestMailer
 from lp.testing import TestCaseWithFactory
+from lp.testing.factory import GPGSigningContext
 from lp.testing.mail_helpers import pop_notifications
 
 
@@ -76,6 +90,32 @@
         else:
             self.assertEqual(old_oops.id, current_oops.id)
 
+    def test_bad_signature_timestamp(self):
+        """If the signature is nontrivial future-dated, it's not trusted."""
+
+        signing_context = GPGSigningContext(
+            import_secret_test_key().fingerprint, password='test')
+        msg = self.factory.makeSignedMessage(signing_context=signing_context)
+        # it's not easy (for me) to make a gpg signature with a bogus
+        # timestamp, so instead we'll just make sure that it is in fact
+        # checked
+        self._hook_timestamp_check()
+        authenticateEmail(msg)
+        self.assertTrue(
+            IWeaklyAuthenticatedPrincipal.providedBy(get_current_principal()))
+
+    def _hook_timestamp_check(self):
+        saved = helpers.ensure_sane_signature_timestamp
+
+        def restore():
+            helpers.ensure_sane_signature_timestamp = saved
+
+        def fail(timestamp, context):
+            raise helpers.IncomingEmailError("fail!")
+
+        self.addCleanup(restore)
+        helpers.ensure_sane_signature_timestamp = fail
+
 
 def test_suite():
     suite = unittest.TestSuite()

=== modified file 'lib/lp/bugs/tests/bugs-emailinterface.txt'
--- lib/lp/bugs/tests/bugs-emailinterface.txt	2010-10-04 19:50:45 +0000
+++ lib/lp/bugs/tests/bugs-emailinterface.txt	2010-10-15 09:21:30 +0000
@@ -50,6 +50,8 @@
 signed, so that the system can verify the sender. But to avoid having
 to sign each email, we'll create a class which fakes a signed email:
 
+    >>> from lp.testing import sampledata
+
     >>> import email.Message
     >>> class MockSignedMessage(email.Message.Message):
     ...     def __init__(self, *args, **kws):
@@ -77,14 +79,10 @@
     ...         msg['Message-Id'] = factory.makeUniqueRFC822MsgId()
     ...     return msg
 
-    >>> import time
-    >>> def fake_extract_signature_timestamp(signed_msg):
-    ...     return time.time()
-
     >>> def process_email(raw_mail):
     ...     msg = construct_email(raw_mail)
     ...     handler.process(msg, msg['To'],
-    ...         extract_signature_timestamp=fake_extract_signature_timestamp)
+    ...         )
 
     >>> process_email(submit_mail)
 
@@ -156,7 +154,7 @@
 If we would file a bug on Ubuntu instead, we would submit a mail like
 this:
 
-    >>> login('test@xxxxxxxxxxxxx')
+    >>> login(sampledata.USER_EMAIL)
     >>> submit_mail = """From: Sample Person <test@xxxxxxxxxxxxx>
     ... To: new@xxxxxxxxxxxxxxxxxx
     ... Date: Fri Jun 17 10:20:23 BST 2005
@@ -343,13 +341,15 @@
     ...     IWeaklyAuthenticatedPrincipal)
     >>> from zope.interface import directlyProvides, directlyProvidedBy
     >>> from zope.security.management import queryInteraction
-    >>> participations = queryInteraction().participations
-    >>> len(participations)
-    1
-    >>> current_principal = participations[0].principal
-    >>> directlyProvides(
-    ...     current_principal, directlyProvidedBy(current_principal),
-    ...     IWeaklyAuthenticatedPrincipal)
+
+    >>> def simulate_receiving_untrusted_mail():
+    ...     participations = queryInteraction().participations
+    ...     assert len(participations) == 1
+    ...     current_principal = participations[0].principal
+    ...     directlyProvides(
+    ...         current_principal, directlyProvidedBy(current_principal),
+    ...         IWeaklyAuthenticatedPrincipal)
+    >>> simulate_receiving_untrusted_mail()
 
 Now we send a comment containing commands.
 
@@ -386,6 +386,8 @@
 
     >>> def print_latest_email():
     ...     commit()
+    ...     if not stub.test_emails:
+    ...         raise AssertionError("No emails queued!")
     ...     from_addr, to_addrs, raw_message = stub.test_emails[-1]
     ...     sent_msg = email.message_from_string(raw_message)
     ...     error_mail, original_mail = sent_msg.get_payload()
@@ -412,7 +414,7 @@
     ...     comment_mail, _class=MockUnsignedMessage)
     >>> handler.process(
     ...     msg, msg['To'],
-    ...     extract_signature_timestamp=fake_extract_signature_timestamp)
+    ...     )
     True
     >>> commit()
 
@@ -458,12 +460,9 @@
     >>> added_message in bug_one.messages
     True
 
-Unmark the principal:
+In these tests, every time we log in, we're fully trusted again:
 
-    >>> provided_interfaces = directlyProvidedBy(current_principal)
-    >>> directlyProvides(
-    ...     current_principal,
-    ...     provided_interfaces - IWeaklyAuthenticatedPrincipal)
+    >>> login(sampledata.USER_EMAIL)
 
 
 Commands
@@ -486,7 +485,7 @@
     >>> def submit_command_email(msg):
     ...     handler.process(
     ...         msg, msg['To'],
-    ...         extract_signature_timestamp=fake_extract_signature_timestamp)
+    ...         )
     ...     commit()
     ...     sync(bug)
 
@@ -735,7 +734,7 @@
     >>> 'Foo Bar' in [subscription.person.displayname
     ...               for subscription in bug_four.subscriptions]
     False
-    >>> login('test@xxxxxxxxxxxxx')
+    >>> login(sampledata.USER_EMAIL)
     >>> submit_commands(bug_four, 'unsubscribe')
     >>> 'Sample Person' in [subscription.person.displayname
     ...                     for subscription in bug_four.subscriptions]
@@ -794,9 +793,9 @@
     ...  for subscriber in bug_five.getIndirectSubscribers()])
     [u'Sample Person', u'Ubuntu Team']
 
-(Log back in as test@xxxxxxxxxxxxx for the tests that follow.)
+(Log back in for the tests that follow.)
 
-    >>> login("test@xxxxxxxxxxxxx")
+    >>> login(sampledata.USER_EMAIL)
 
 If we specify a non-existant user, an error message will be sent:
 
@@ -1109,7 +1108,7 @@
 Attempting to set the milestone for a bug without sufficient
 permissions also elicits an error message:
 
-    >>> login('test@xxxxxxxxxxxxx')
+    >>> login(sampledata.USER_EMAIL)
     >>> bug = new_firefox_bug()
     >>> commit()
 
@@ -1135,7 +1134,7 @@
     Only owners, drivers and bug supervisors may assign milestones.
     ...
 
-    >>> login('test@xxxxxxxxxxxxx')
+    >>> login(sampledata.USER_EMAIL)
 
 Like the web UI, we can assign a bug to nobody.
 
@@ -1231,10 +1230,10 @@
     >>> login('foo.bar@xxxxxxxxxxxxx')
     >>> ubuntu = getUtility(IDistributionSet).getByName('ubuntu')
     >>> ubuntu.driver = getUtility(IPersonSet).getByEmail(
-    ...     'test@xxxxxxxxxxxxx')
+    ...     sampledata.USER_EMAIL)
     >>> commit()
     >>> LaunchpadZopelessLayer.switchDbUser(config.processmail.dbuser)
-    >>> login('test@xxxxxxxxxxxxx')
+    >>> login(sampledata.USER_EMAIL)
     >>> sync(bug)
 
 Now a new bugtask for the series will be create directly.
@@ -1287,7 +1286,7 @@
     >>> ubuntu.driver = None
     >>> commit()
     >>> LaunchpadZopelessLayer.switchDbUser(config.processmail.dbuser)
-    >>> login('test@xxxxxxxxxxxxx')
+    >>> login(sampledata.USER_EMAIL)
 
     >>> bug = new_firefox_bug()
     >>> for bugtask in bug.bugtasks:
@@ -1314,7 +1313,7 @@
     ...     print driver.displayname
     Sample Person
 
-    >>> login('test@xxxxxxxxxxxxx')
+    >>> login(sampledata.USER_EMAIL)
     >>> submit_commands(bug, 'affects /firefox/trunk')
 
     >>> for bugtask in bug.bugtasks:
@@ -1345,7 +1344,7 @@
     ...     print nomination.target.bugtargetdisplayname
     Evolution trunk
 
-    >>> login('test@xxxxxxxxxxxxx')
+    >>> login(sampledata.USER_EMAIL)
 
 Let's take on the upstream task on bug four as well. This time we'll
 sneak in a 'subscribe' command between the 'affects' and the other
@@ -1620,7 +1619,7 @@
 The user is a bug supervisors of the upstream product
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-    >>> login('test@xxxxxxxxxxxxx')
+    >>> login(sampledata.USER_EMAIL)
     >>> bug_one = getUtility(IBugSet).get(1)
     >>> submit_commands(
     ...     bug_one, 'status confirmed', 'assignee test@xxxxxxxxxxxxx')
@@ -1724,6 +1723,7 @@
 If none of the bug tasks can be chosen, an error message is sent to the
 user, telling him that he has to use the 'affects' command.
 
+    >>> del stub.test_emails[:]
     >>> login('stuart.bishop@xxxxxxxxxxxxx')
     >>> submit_commands(
     ...     bug_one, 'status new', 'assignee foo.bar@xxxxxxxxxxxxx')
@@ -1754,7 +1754,9 @@
 him about the error. Let's start with trying to submit a bug without
 signing the mail:
 
-    >>> login('test@xxxxxxxxxxxxx')
+    >>> del stub.test_emails[:]
+    >>> login(sampledata.USER_EMAIL)
+    >>> simulate_receiving_untrusted_mail()
 
     >>> from canonical.launchpad.mail import signed_message_from_string
     >>> msg = signed_message_from_string(submit_mail)
@@ -1762,7 +1764,7 @@
     >>> msg['Message-Id'] = email.Utils.make_msgid()
     >>> handler.process(
     ...     msg, msg['To'],
-    ...     extract_signature_timestamp=fake_extract_signature_timestamp)
+    ...     )
     True
     >>> print_latest_email()
     Subject: Submit Request Failure
@@ -1775,6 +1777,7 @@
 
 A submit without specifying on what we want to file the bug on:
 
+    >>> login(sampledata.USER_EMAIL)
     >>> submit_mail_no_bugtask = """From: test@xxxxxxxxxxxxx
     ... To: new@malone
     ... Date: Fri Jun 17 10:20:23 BST 2005
@@ -1981,7 +1984,7 @@
     >>> from canonical.launchpad.mailnotification import (
     ...     send_process_error_notification)
     >>> send_process_error_notification(
-    ...     'test@xxxxxxxxxxxxx', 'Some subject', 'Some error message.',
+    ...     sampledata.USER_EMAIL, 'Some subject', 'Some error message.',
     ...     msg, failing_command=['foo bar'])
 
 The To and Subject headers got set to the values we provided:
@@ -2045,7 +2048,7 @@
 
 First, we create a new firefox bug.
 
-    >>> login('test@xxxxxxxxxxxxx')
+    >>> login(sampledata.USER_EMAIL)
     >>> submit_mail = """From: Sample Person <test@xxxxxxxxxxxxx>
     ... To: new@xxxxxxxxxxxxxxxxxx
     ... Date: Fri Jun 17 10:20:23 BST 2006

=== modified file 'utilities/migrater/file-ownership.txt'
--- utilities/migrater/file-ownership.txt	2010-09-21 18:56:32 +0000
+++ utilities/migrater/file-ownership.txt	2010-10-15 09:21:30 +0000
@@ -1053,7 +1053,7 @@
     ./mail/errortemplates/num-arguments-mismatch.txt
     ./mail/errortemplates/no-such-bug.txt
     ./mail/errortemplates/security-parameter-mismatch.txt
-    ./mail/errortemplates/not-gpg-signed.txt
+    ./mail/errortemplates/unauthenticated-bug-creation.txt
     ./mail/errortemplates/key-not-registered.txt
     ./mail/errortemplates/oops.txt
     ./mail/errortemplates/branchmergeproposal-exists.txt


References