summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: 7dfd71f)
raw | patch | inline | side by side (parent: 7dfd71f)
author | schlatterbeck <schlatterbeck@57a73879-2fb5-44c3-a270-3262357dd7e2> | |
Fri, 7 Oct 2011 14:21:57 +0000 (14:21 +0000) | ||
committer | schlatterbeck <schlatterbeck@57a73879-2fb5-44c3-a270-3262357dd7e2> | |
Fri, 7 Oct 2011 14:21:57 +0000 (14:21 +0000) |
now have a regression test. We now take care that bounce-messages for
incoming encrypted mails or mails where the policy dictates that
outgoing traffic should be encrypted is actually pgp-encrypted. Note
that the new pgp encrypt option for outgoing mails works only for
bounces for now.
git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/roundup/trunk@4654 57a73879-2fb5-44c3-a270-3262357dd7e2
incoming encrypted mails or mails where the policy dictates that
outgoing traffic should be encrypted is actually pgp-encrypted. Note
that the new pgp encrypt option for outgoing mails works only for
bounces for now.
git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/roundup/trunk@4654 57a73879-2fb5-44c3-a270-3262357dd7e2
diff --git a/CHANGES.txt b/CHANGES.txt
index 5a792dfc6423297bb1876edfcf3a4387f662c355..1cab75f26211ebf24d8fde373882d7992f02a72c 100644 (file)
--- a/CHANGES.txt
+++ b/CHANGES.txt
is addressed to support@example.com this would (wrongly) match. (Ralf)
- issue2550729: Fix password history display for anydbm backend, thanks
to Ralf Hemmecke for reporting. (Ralf)
+- PGP support is again working (pyme API has changed significantly) and
+ we now have a regression test. We now take care that bounce-messages
+ for incoming encrypted mails or mails where the policy dictates that
+ outgoing traffic should be encrypted is actually pgp-encrypted. Note
+ that the new pgp encrypt option for outgoing mails works only for
+ bounces for now. (Ralf)
2011-07-15 1.4.19 (r4638)
index ab43ca5f3f7a62f81392e2bf636b537ba716feda..6b9d05a7a2d8a0174c6df089b7b6c45629c49c67 100644 (file)
--- a/roundup/configuration.py
+++ b/roundup/configuration.py
), "Roundup Mail Gateway options"),
("pgp", (
(BooleanOption, "enable", "no",
- "Enable PGP processing. Requires pyme."),
+ "Enable PGP processing. Requires pyme. If you're planning\n"
+ "to send encrypted PGP mail to the tracker, you should also\n"
+ "enable the encrypt-option below, otherwise mail received\n"
+ "encrypted might be sent unencrypted to another user."),
(NullableOption, "roles", "",
"If specified, a comma-separated list of roles to perform\n"
"PGP processing on. If not specified, it happens for all\n"
- "users."),
+ "users. Note that received PGP messages (signed and/or\n"
+ "encrypted) will be processed with PGP even if the user\n"
+ "doesn't have one of the PGP roles, you can use this to make\n"
+ "PGP processing completely optional by defining a role here\n"
+ "and not assigning any users to that role."),
(NullableOption, "homedir", "",
"Location of PGP directory. Defaults to $HOME/.gnupg if\n"
"not specified."),
+ (BooleanOption, "encrypt", "no",
+ "Enable PGP encryption. All outgoing mails are encrypted.\n"
+ "This requires that keys for all users (with one of the gpg\n"
+ "roles above or all users if empty) are available. Note that\n"
+ "it makes sense to educate users to also send mails encrypted\n"
+ "to the tracker, to enforce this, set 'require_incoming'\n"
+ "option below (but see the note)."),
+ (Option, "require_incoming", "signed",
+ "Require that pgp messages received by roundup are either\n"
+ "'signed', 'encrypted' or 'both'. If encryption is required\n"
+ "we do not return the message (in clear) to the user but just\n"
+ "send an informational message that the message was rejected.\n"
+ "Note that this still presents known-plaintext to an attacker\n"
+ "when the users sends the mail a second time with encryption\n"
+ "turned on."),
), "OpenPGP mail processing options"),
("nosy", (
(RunDetectorOption, "messages_to_author", "no",
diff --git a/roundup/mailer.py b/roundup/mailer.py
index a91baf2cee5ebfeaf705c09c6e8c6a3285d29f9a..b51c81d0ad755aa78f9e01c97e999407e451e831 100644 (file)
--- a/roundup/mailer.py
+++ b/roundup/mailer.py
from email.Utils import formatdate, formataddr, specialsre, escapesre
from email.Message import Message
from email.Header import Header
+from email.MIMEBase import MIMEBase
from email.MIMEText import MIMEText
from email.MIMEMultipart import MIMEMultipart
+try:
+ import pyme, pyme.core
+except ImportError:
+ pyme = None
+
+
class MessageSendError(RuntimeError):
pass
os.environ['TZ'] = get_timezone(self.config.TIMEZONE).tzname(None)
time.tzset()
- def get_standard_message(self, to, subject, author=None, multipart=False):
- '''Form a standard email message from Roundup.
-
+ def set_message_attributes(self, message, to, subject, author=None):
+ ''' Add attributes to a standard output message
"to" - recipients list
"subject" - Subject
"author" - (name, address) tuple or None for admin email
Subject and author are encoded using the EMAIL_CHARSET from the
config (default UTF-8).
-
- Returns a Message object.
'''
# encode header values if they need to be
charset = getattr(self.config, 'EMAIL_CHARSET', 'utf-8')
else:
name = unicode(author[0], 'utf-8')
author = nice_sender_header(name, author[1], charset)
-
- if multipart:
- message = MIMEMultipart()
- else:
- message = MIMEText("")
- message.set_charset(charset)
-
try:
message['Subject'] = subject.encode('ascii')
except UnicodeError:
# finally, an aid to debugging problems
message['X-Roundup-Version'] = __version__
+ def get_standard_message(self, multipart=False):
+ '''Form a standard email message from Roundup.
+ Returns a Message object.
+ '''
+ charset = getattr(self.config, 'EMAIL_CHARSET', 'utf-8')
+ if multipart:
+ message = MIMEMultipart()
+ else:
+ message = MIMEText("")
+ message.set_charset(charset)
+
return message
def standard_message(self, to, subject, content, author=None):
All strings are assumed to be UTF-8 encoded.
"""
- message = self.get_standard_message(to, subject, author)
+ message = self.get_standard_message()
+ self.set_message_attributes(message, to, subject, author)
message.set_payload(content)
encode_quopri(message)
self.smtp_send(to, message.as_string())
def bounce_message(self, bounced_message, to, error,
- subject='Failed issue tracker submission'):
+ subject='Failed issue tracker submission', crypt=False):
"""Bounce a message, attaching the failed submission.
Arguments:
ERROR_MESSAGES_TO setting.
- error: the reason of failure as a string.
- subject: the subject as a string.
+ - crypt: require encryption with pgp for user -- applies only to
+ mail sent back to the user, not the dispatcher oder admin.
"""
+ crypt_to = None
+ if crypt:
+ crypt_to = to
+ to = None
# see whether we should send to the dispatcher or not
dispatcher_email = getattr(self.config, "DISPATCHER_EMAIL",
getattr(self.config, "ADMIN_EMAIL"))
error_messages_to = getattr(self.config, "ERROR_MESSAGES_TO", "user")
if error_messages_to == "dispatcher":
to = [dispatcher_email]
+ crypt = False
+ crypt_to = None
elif error_messages_to == "both":
- to.append(dispatcher_email)
+ if crypt:
+ to = [dispatcher_email]
+ else:
+ to.append(dispatcher_email)
- message = self.get_standard_message(to, subject, multipart=True)
+ message = self.get_standard_message(multipart=True)
# add the error text
part = MIMEText('\n'.join(error))
part = MIMEText(''.join(body))
message.attach(part)
- # send
- try:
- self.smtp_send(to, message.as_string())
- except MessageSendError:
- # squash mail sending errors when bouncing mail
- # TODO this *could* be better, as we could notify admin of the
- # problem (even though the vast majority of bounce errors are
- # because of spam)
- pass
+ if to:
+ # send
+ self.set_message_attributes(message, to, subject)
+ try:
+ self.smtp_send(to, message.as_string())
+ except MessageSendError:
+ # squash mail sending errors when bouncing mail
+ # TODO this *could* be better, as we could notify admin of the
+ # problem (even though the vast majority of bounce errors are
+ # because of spam)
+ pass
+ if crypt_to:
+ plain = pyme.core.Data(message.as_string())
+ cipher = pyme.core.Data()
+ ctx = pyme.core.Context()
+ ctx.set_armor(1)
+ keys = []
+ adrs = []
+ for adr in crypt_to:
+ ctx.op_keylist_start(adr, 0)
+ # only first key per email
+ k = ctx.op_keylist_next()
+ if k is not None:
+ adrs.append(adr)
+ keys.append(k)
+ ctx.op_keylist_end()
+ crypt_to = adrs
+ if crypt_to:
+ try:
+ ctx.op_encrypt(keys, 1, plain, cipher)
+ cipher.seek(0,0)
+ message=MIMEMultipart('encrypted', boundary=None,
+ _subparts=None, protocol="application/pgp-encrypted")
+ part=MIMEBase('application', 'pgp-encrypted')
+ part.set_payload("Version: 1\r\n")
+ message.attach(part)
+ part=MIMEBase('application', 'octet-stream')
+ part.set_payload(cipher.read())
+ message.attach(part)
+ except pyme.GPGMEError:
+ crypt_to = None
+ if crypt_to:
+ self.set_message_attributes(message, crypt_to, subject)
+ try:
+ self.smtp_send(crypt_to, message.as_string())
+ except MessageSendError:
+ # ignore on error, see above.
+ pass
def exception_message(self):
'''Send a message to the admins with information about the latest
diff --git a/roundup/mailgw.py b/roundup/mailgw.py
index d269554f8783216a05f51de73ed062c15fa28ca3..1558c0f421718505d2d8258bdd3754a779d59aaf 100644 (file)
--- a/roundup/mailgw.py
+++ b/roundup/mailgw.py
for u in key.uids:
yield getattr(u, attr)
-def check_pgp_sigs(sigs, gpgctx, author):
+def check_pgp_sigs(sigs, gpgctx, author, may_be_unsigned=False):
''' Theoretically a PGP message can have several signatures. GPGME
returns status on all signatures in a list. Walk that list
- looking for the author's signature
+ looking for the author's signature. Note that even if incoming
+ signatures are not required, the processing fails if there is an
+ invalid signature.
'''
for sig in sigs:
key = gpgctx.get_key(sig.fpr, False)
_("Invalid PGP signature detected.")
# we couldn't find a key belonging to the author of the email
- raise MailUsageError, _("Message signed with unknown key: %s") % sig.fpr
+ if sigs:
+ raise MailUsageError, _("Message signed with unknown key: %s") % sig.fpr
+ elif not may_be_unsigned:
+ raise MailUsageError, _("Unsigned Message")
class Message(mimetools.Message):
''' subclass mimetools.Message so we can retrieve the parts of the
return self.gettype() == 'multipart/encrypted' \
and self.typeheader.find('protocol="application/pgp-encrypted"') != -1
- def decrypt(self, author):
+ def decrypt(self, author, may_be_unsigned=False):
''' decrypt an OpenPGP MIME message
- This message must be signed as well as encrypted using the "combined"
- method. The decrypted contents are returned as a new message.
+ This message must be signed as well as encrypted using the
+ "combined" method if incoming signatures are configured.
+ The decrypted contents are returned as a new message.
'''
(hdr, msg) = self.getparts()
# According to the RFC 3156 encrypted mail must have exactly two parts.
# The first part contains the control information. Let's verify that
# the message meets the RFC before we try to decrypt it.
- if hdr.getbody() != 'Version: 1' or hdr.gettype() != 'application/pgp-encrypted':
+ if hdr.getbody().strip() != 'Version: 1' \
+ or hdr.gettype() != 'application/pgp-encrypted':
raise MailUsageError, \
_("Unknown multipart/encrypted version.")
# key to send it to us. now check the signatures to see if it
# was signed by someone we trust
result = context.op_verify_result()
- check_pgp_sigs(result.signatures, context, author)
+ check_pgp_sigs(result.signatures, context, author,
+ may_be_unsigned = may_be_unsigned)
plaintext.seek(0,0)
# pyme.core.Data implements a seek method with a different signature
self.props = None
self.content = None
self.attachments = None
+ self.crypt = False
def handle_ignore(self):
''' Check to see if message can be safely ignored:
else:
return True
- if self.config.PGP_ENABLE and pgp_role():
+ if self.config.PGP_ENABLE:
+ if pgp_role() and self.config.PGP_ENCRYPT:
+ self.crypt = True
assert pyme, 'pyme is not installed'
# signed/encrypted mail must come from the primary address
author_address = self.db.user.get(self.author, 'address')
if self.config.PGP_HOMEDIR:
os.environ['GNUPGHOME'] = self.config.PGP_HOMEDIR
+ if self.config.PGP_REQUIRE_INCOMING in ('encrypted', 'both') \
+ and pgp_role() and not self.message.pgp_encrypted():
+ raise MailUsageError, _(
+ "This tracker has been configured to require all email "
+ "be PGP encrypted.")
if self.message.pgp_signed():
self.message.verify_signature(author_address)
elif self.message.pgp_encrypted():
- # replace message with the contents of the decrypted
+ # Replace message with the contents of the decrypted
# message for content extraction
- # TODO: encrypted message handling is far from perfect
- # bounces probably include the decrypted message, for
- # instance :(
- self.message = self.message.decrypt(author_address)
- else:
+ # Note: the bounce-handling code now makes sure that
+ # either the encrypted mail received is sent back or
+ # that the error message is encrypted if needed.
+ encr_only = self.config.PGP_REQUIRE_INCOMING == 'encrypted'
+ encr_only = encr_only or not pgp_role()
+ self.crypt = True
+ self.message = self.message.decrypt(author_address,
+ may_be_unsigned = encr_only)
+ elif pgp_role():
raise MailUsageError, _("""
This tracker has been configured to require all email be PGP signed or
encrypted.""")
return self.handle_message(message)
# no, we want to trap exceptions
+ # Note: by default we return the message received not the
+ # internal state of the parsedMessage -- except for
+ # MailUsageError, Unauthorized and for unknown exceptions. For
+ # the latter cases we make sure the error message is encrypted
+ # if needed (if it either was received encrypted or pgp
+ # processing is turned on for the user).
try:
return self.handle_message(message)
except MailUsageHelp:
m.append(str(value))
m.append('\n\nMail Gateway Help\n=================')
m.append(fulldoc)
- self.mailer.bounce_message(message, [sendto[0][1]], m)
+ if self.parsed_message:
+ message = self.parsed_message.message
+ crypt = self.parsed_message.crypt
+ self.mailer.bounce_message(message, [sendto[0][1]], m, crypt=crypt)
except Unauthorized, value:
# just inform the user that he is not authorized
m = ['']
m.append(str(value))
- self.mailer.bounce_message(message, [sendto[0][1]], m)
+ if self.parsed_message:
+ message = self.parsed_message.message
+ crypt = self.parsed_message.crypt
+ self.mailer.bounce_message(message, [sendto[0][1]], m, crypt=crypt)
except IgnoreMessage:
# do not take any action
# this exception is thrown when email should be ignored
m.append('An unexpected error occurred during the processing')
m.append('of your message. The tracker administrator is being')
m.append('notified.\n')
- self.mailer.bounce_message(message, [sendto[0][1]], m)
+ if self.parsed_message:
+ message = self.parsed_message.message
+ crypt = self.parsed_message.crypt
+ self.mailer.bounce_message(message, [sendto[0][1]], m, crypt=crypt)
m.append('----------------')
m.append(traceback.format_exc())
# commit the changes to the DB
self.db.commit()
+ self.parsed_message = None
return nodeid
def get_class_arguments(self, class_type, classname=None):
diff --git a/roundup/roundupdb.py b/roundup/roundupdb.py
index 19b3004b102d99a117c34da4ea07f78c1c398790..91d855c9282bfae8df9fdd5f59ffa092d57b303f 100644 (file)
--- a/roundup/roundupdb.py
+++ b/roundup/roundupdb.py
# create the message
mailer = Mailer(self.db.config)
- message = mailer.get_standard_message(sendto, subject, author,
- multipart=message_files)
+ message = mailer.get_standard_message(multipart=message_files)
+ mailer.set_message_attributes(message, sendto, subject, author)
# set reply-to to the tracker
message['Reply-To'] = tracker_name
diff --git a/test/test_mailgw.py b/test/test_mailgw.py
index ea1a53a67c9906abaafba1c2bbf40dcfc4b4fafe..e0789ae2d015f6b2ab51ae3172605e22ea4ff89e 100644 (file)
--- a/test/test_mailgw.py
+++ b/test/test_mailgw.py
# TODO: test bcc
import unittest, tempfile, os, shutil, errno, imp, sys, difflib, rfc822, time
+from email.parser import FeedParser
try:
handler.db = self.db
return handler
- def _handle_mail(self, message, args=()):
+ def _handle_mail(self, message, args=(), trap_exc=0):
handler = self._create_mailgw(message, args)
- handler.trapExceptions = 0
+ handler.trapExceptions = trap_exc
return handler.main(StringIO(message))
def _get_mail(self):
This is a test submission of a new issue.
'''
- handler = self._create_mailgw(message)
- # we want a bounce message:
- handler.trapExceptions = 1
- ret = handler.main(StringIO(message))
+ # trap_exc=1: we want a bounce message:
+ ret = self._handle_mail(message, trap_exc=1)
self.compareMessages(self._get_mail(),
'''FROM: Roundup issue tracker <roundup-admin@your.tracker.email.domain.example>
TO: nonexisting@bork.bork.bork
pgphome = 'pgp-test-home'
def setUp(self):
MailgwTestAbstractBase.setUp(self)
- self.db.security.addRole (name = 'pgp', description = 'PGP Role')
+ self.db.security.addRole(name = 'pgp', description = 'PGP Role')
self.instance.config['PGP_HOMEDIR'] = self.pgphome
self.instance.config['PGP_ROLES'] = 'pgp'
self.instance.config['PGP_ENABLE'] = True
+ self.instance.config['MAIL_DOMAIN'] = 'example.com'
+ self.instance.config['ADMIN_EMAIL'] = 'roundup-admin@example.com'
self.db.user.set(self.john_id, roles='User,pgp')
os.mkdir(self.pgphome)
os.environ['GNUPGHOME'] = self.pgphome
if os.path.exists(self.pgphome):
shutil.rmtree(self.pgphome)
- def testUnsignedMessage(self):
+ def testPGPUnsignedMessage(self):
self.assertRaises(MailUsageError, self._handle_mail,
'''Content-Type: text/plain;
charset="iso-8859-1"
This is no pgp signed message.
''')
- def testSignedMessage(self):
- nodeid = self._handle_mail('''Content-Disposition: inline
+ signed_msg = '''Content-Disposition: inline
From: John Doe <john@test.test>
To: issue_tracker@your.tracker.email.domain.example
Subject: [issue] Testing signed message...
-----END PGP SIGNATURE-----
--cWoXeonUoKmBZSoM--
-''')
- m = self.db.issue.get (nodeid, 'messages') [0]
+'''
+
+ def testPGPSignedMessage(self):
+ nodeid = self._handle_mail(self.signed_msg)
+ m = self.db.issue.get(nodeid, 'messages')[0]
self.assertEqual(self.db.msg.get(m, 'content'),
'This is a pgp signed message.')
+ def testPGPSignedMessageFail(self):
+ # require both, signing and encryption
+ self.instance.config['PGP_REQUIRE_INCOMING'] = 'both'
+ self.assertRaises(MailUsageError, self._handle_mail, self.signed_msg)
+
+ encrypted_msg = '''Content-Disposition: inline
+From: John Doe <john@test.test>
+To: roundup-admin@example.com
+Subject: [issue] Testing encrypted message...
+Content-Type: multipart/encrypted; protocol="application/pgp-encrypted";
+ boundary="d6Gm4EdcadzBjdND"
+
+--d6Gm4EdcadzBjdND
+Content-Type: application/pgp-encrypted
+Content-Disposition: attachment
+
+Version: 1
+
+--d6Gm4EdcadzBjdND
+Content-Type: application/octet-stream
+Content-Disposition: inline; filename="msg.asc"
+
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v1.4.10 (GNU/Linux)
+
+hQEMAzfeQttq+Q2YAQf9FxCtZVgC7jAy6UkeAJ1imCpnh9DgKA5w40OFtrY4mVAp
+cL7kCkvGvJCW7uQZrmSgIiYaZGLI3GS42XutORC6E6PzBEW0fJUMIXYmoSd0OFeY
+3H2+854qu37W/uCOWM9OnPFIH8g8q8DgYy88i0goM+Ot9Q96yFfJ7QymanOZJgVa
+MNC+oKDiIZKiE3PCwtGr+8CHZN/9J6O4FeJijBlr09C5LXc+Nif5T0R0nt17MAns
+9g2UvGxW8U24NAS1mOg868U05hquLPIcFz9jGZGknJu7HBpOkQ9GjKqkzN8pgZVN
+VbN8IdDqi0QtRKE44jtWQlyNlESMjv6GtC2V9F6qKNK8AfHtBexDhyv4G9cPFFNO
+afQ6e4dPi89RYIQyydtwiqao8fj6jlAy2Z1cbr7YxwBG7BeUZv9yis7ShaAIo78S
+82MrCYpSjfHNwKiSfC5yITw22Uv4wWgixVdAsaSdtBqEKXJPG9LNey18ArsBjSM1
+P81iDOWUp/uyIe5ZfvNI38BBxEYslPTUlDk2GB8J2Vun7IWHoj9a4tY3IotC9jBr
+5Qnigzqrt7cJZX6OrN0c+wnOjXbMGYXmgSs4jeM=
+=XX5Q
+-----END PGP MESSAGE-----
+
+--d6Gm4EdcadzBjdND--
+'''
+ def testPGPEncryptedUnsignedMessageError(self):
+ self.assertRaises(MailUsageError, self._handle_mail, self.encrypted_msg)
+
+ def testPGPEncryptedUnsignedMessage(self):
+ # no error if we don't require a signature:
+ self.instance.config['PGP_REQUIRE_INCOMING'] = 'encrypted'
+ nodeid = self._handle_mail (self.encrypted_msg)
+ m = self.db.issue.get(nodeid, 'messages')[0]
+ self.assertEqual(self.db.msg.get(m, 'content'),
+ 'This is the text to be encrypted')
+
+ def testPGPEncryptedUnsignedMessageFromNonPGPUser(self):
+ msg = self.encrypted_msg.replace('John Doe <john@test.test>',
+ '"Contrary, Mary" <mary@test.test>')
+ nodeid = self._handle_mail (msg)
+ m = self.db.issue.get(nodeid, 'messages')[0]
+ self.assertEqual(self.db.msg.get(m, 'content'),
+ 'This is the text to be encrypted')
+ self.assertEqual(self.db.msg.get(m, 'author'), self.mary_id)
+
+ # check that a bounce-message that is triggered *after*
+ # decrypting is properly encrypted:
+ def testPGPEncryptedUnsignedMessageCheckBounce(self):
+ # allow non-signed msg
+ self.instance.config['PGP_REQUIRE_INCOMING'] = 'encrypted'
+ # don't allow creation of message, trigger error *after* decrypt
+ self.db.user.set(self.john_id, roles='pgp')
+ self.db.security.addPermissionToRole('pgp', 'Email Access')
+ self.db.security.addPermissionToRole('pgp', 'Create', 'issue')
+ # trap_exc=1: we want a bounce message:
+ self._handle_mail(self.encrypted_msg, trap_exc=1)
+ m = self._get_mail()
+ fp = FeedParser()
+ fp.feed(m)
+ parts = fp.close().get_payload()
+ self.assertEqual(len(parts),2)
+ self.assertEqual(parts[0].get_payload().strip(), 'Version: 1')
+ crypt = pyme.core.Data(parts[1].get_payload())
+ plain = pyme.core.Data()
+ ctx = pyme.core.Context()
+ res = ctx.op_decrypt(crypt, plain)
+ self.assertEqual(res, None)
+ plain.seek(0,0)
+ fp = FeedParser()
+ fp.feed(plain.read())
+ parts = fp.close().get_payload()
+ self.assertEqual(len(parts),2)
+ self.assertEqual(parts[0].get_payload().strip(),
+ 'You are not permitted to create messages.')
+ self.assertEqual(parts[1].get_payload().strip(),
+ '''Content-Type: text/plain; charset=us-ascii
+Content-Disposition: inline
+
+This is the text to be encrypted''')
+
+
+ def testPGPEncryptedSignedMessage(self):
+ # require both, signing and encryption
+ self.instance.config['PGP_REQUIRE_INCOMING'] = 'both'
+ nodeid = self._handle_mail('''Content-Disposition: inline
+From: John Doe <john@test.test>
+To: roundup-admin@example.com
+Subject: Testing encrypted and signed message
+MIME-Version: 1.0
+Content-Type: multipart/encrypted; protocol="application/pgp-encrypted";
+ boundary="ReaqsoxgOBHFXBhH"
+
+--ReaqsoxgOBHFXBhH
+Content-Type: application/pgp-encrypted
+Content-Disposition: attachment
+
+Version: 1
+
+--ReaqsoxgOBHFXBhH
+Content-Type: application/octet-stream
+Content-Disposition: inline; filename="msg.asc"
+
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v1.4.10 (GNU/Linux)
+
+hQEMAzfeQttq+Q2YAQf+NaC3r8qBURQqxHH9IAP4vg0QAP2yj3n0v6guo1lRf5BA
+EUfTQ3jc3chxLvzTgoUIuMOvhlNroqR1lgLwhfSTCyuKWDZa+aVNiSgsB2MD44Xd
+mAkKKmnmOGLmfbICbPQZxl4xNhCMTHiAy1xQE6mTj/+pEAq5XxjJUwn/gJ3O1Wmd
+NyWtJY2N+TRbxUVB2WhG1j9J1D2sjhG26TciE8JeuLDZzaiVNOW9YlX2Lw5KtlkR
+Hkgw6Xme06G0XXZUcm9JuBU/7oFP/tSrC1tBsnVlq1pZYf6AygIBdXWb9gD/WmXh
+7Eu/xCKrw4RFnXnTgmBz/NHRfVDkfdSscZqexnG1D9LAwQHSuVf8sxDPNesv0W+8
+e49loVjvU+Y0BCFQAbWSW4iOEUYZpW/ITRE4+wIqMXZbAraeBV0KPZ4hAa3qSmf+
+oZBRcbzssL163Odx/OHRuK2J2CHC654+crrlTBnxd/RUKgRbSUKwrZzB2G6OPcGv
+wfiqXsY+XvSZtTbWuvUJxePh8vhhhjpuo1JtlrYc3hZ9OYgoCoV1JiLl5c60U5Es
+oUT9GDl1Qsgb4dF4TJ1IBj+riYiocYpJxPhxzsy6liSLNy2OA6VEjG0FGk53+Ok9
+7UzOA+WaHJHSXafZzrdP1TWJUFlOMA+dOgTKpH69eL1+IRfywOjEwp1UNSbLnJpc
+D0QQLwIFttplKvYkn0DZByJCVnIlGkl4s5LM5rnc8iecX8Jad0iRIlPV6CVM+Nso
+WdARUfyJfXAmz8uk4f2sVfeMu1gdMySdjvxwlgHDJdBPIG51r2b8L/NCTiC57YjF
+zGhS06FLl3V1xx6gBlpqQHjut3efrAGpXGBVpnTJMOcgYAk=
+=jt/n
+-----END PGP MESSAGE-----
+
+--ReaqsoxgOBHFXBhH--
+''')
+ m = self.db.issue.get(nodeid, 'messages')[0]
+ self.assertEqual(self.db.msg.get(m, 'content'),
+ 'This is the text of a signed and encrypted email.')
+
+
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(MailgwTestCase))