Commit 725ebe36 authored by Barry Warsaw's avatar Barry Warsaw

IRegistrar is now a utility; it doesn't need to be adapted from an IDomain.

This is because registration confirmation messages must come from the mailing
list that the subscription request came from.

Remove IDomain.confirm_address() since this lives only on the IMailingList now.
parent 3c4c8f9c
......@@ -66,8 +66,6 @@ def create_list(fqdn_listname, owners=None):
for owner_address in owners:
addr = user_manager.get_address(owner_address)
if addr is None:
# XXX Make this use an IRegistrar instead, but that requires
# sussing out the IDomain stuff. For now, fake it.
user = user_manager.create_user(owner_address)
addr = list(user.addresses)[0]
addr.subscribe(mlist, MemberRole.owner)
......
......@@ -35,7 +35,6 @@ from mailman.config import config
from mailman.core.i18n import _
from mailman.email.message import UserNotification
from mailman.email.validate import validate
from mailman.interfaces.domain import IDomain
from mailman.interfaces.listmanager import IListManager
from mailman.interfaces.member import MemberRole
from mailman.interfaces.pending import IPendable, IPendings
......@@ -51,10 +50,9 @@ class PendableRegistration(dict):
class Registrar:
implements(IRegistrar)
"""Handle registrations and confirmations for subscriptions."""
def __init__(self, context):
self._context = context
implements(IRegistrar)
def register(self, mlist, address, real_name=None):
"""See `IUserRegistrar`."""
......@@ -68,17 +66,18 @@ class Registrar:
real_name=real_name)
pendable['list_name'] = mlist.fqdn_listname
token = getUtility(IPendings).add(pendable)
# Set up some local variables for translation interpolation.
domain = IDomain(self._context)
domain_name = _(domain.email_host)
contact_address = domain.contact_address
confirm_url = domain.confirm_url(token)
confirm_address = domain.confirm_address(token)
email_address = address
# Calculate the message's Subject header. XXX Have to deal with
# translating this subject header properly. XXX Must deal with
# VERP_CONFIRMATIONS as well.
# There are three ways for a user to confirm their subscription. They
# can reply to the original message and let the VERP'd return address
# encode the token, they can reply to the robot and keep the token in
# the Subject header, or they can click on the URL in the body of the
# message and confirm through the web.
subject = 'confirm ' + token
confirm_address = mlist.confirm_address(token)
confirm_url = mlist.domain.confirm_url(token)
# For i18n interpolation.
email_address = address
domain_name = mlist.domain.email_host
contact_address = mlist.domain.contact_address
# Send a verification email to the address.
text = _(resource_string('mailman.templates.en', 'verify.txt'))
msg = UserNotification(address, confirm_address, subject, text)
......
==================
The 'join' command
==================
============================
Membership changes via email
============================
Membership changes such as joining and leaving a mailing list, can be effected
via the email interface. The Mailman email commands 'join', 'leave', and
'confirm' are used.
Joining a mailing list
======================
The mail command 'join' subscribes an email address to the mailing list.
'subscribe' is an alias for 'join'.
>>> command = config.commands['join']
>>> print command.name
>>> from mailman.commands.eml_membership import Join
>>> join = Join()
>>> print join.name
join
>>> print command.description
>>> print join.description
Join this mailing list. You will be asked to confirm your subscription
request and you may be issued a provisional password.
<BLANKLINE>
......@@ -19,22 +28,23 @@ The mail command 'join' subscribes an email address to the mailing list.
<BLANKLINE>
join [email protected]
<BLANKLINE>
>>> print command.argument_description
>>> print join.argument_description
[digest=<yes|no>] [address=<address>]
No address to join
==================
------------------
>>> from mailman.email.message import Message
>>> from mailman.queue.command import Results
>>> mlist = create_list('[email protected]')
When no address argument is given, the message's From address will be used.
If that's missing though, then an error is returned.
>>> from mailman.queue.command import Results
>>> results = Results()
>>> print command.process(mlist, Message(), {}, (), results)
>>> from mailman.email.message import Message
>>> print join.process(mlist, Message(), {}, (), results)
ContinueProcessing.no
>>> print unicode(results)
The results of your email command are provided below.
......@@ -44,7 +54,8 @@ If that's missing though, then an error is returned.
The 'subscribe' command is an alias.
>>> subscribe = config.commands['subscribe']
>>> from mailman.commands.eml_membership import Subscribe
>>> subscribe = Subscribe()
>>> print subscribe.name
subscribe
>>> results = Results()
......@@ -58,7 +69,7 @@ The 'subscribe' command is an alias.
Joining the sender
==================
------------------
When the message has a From field, that address will be subscribed.
......@@ -67,7 +78,7 @@ When the message has a From field, that address will be subscribed.
...
... """)
>>> results = Results()
>>> print command.process(mlist, msg, {}, (), results)
>>> print join.process(mlist, msg, {}, (), results)
ContinueProcessing.yes
>>> print unicode(results)
The results of your email command are provided below.
......@@ -86,13 +97,15 @@ first.
Mailman has sent her the confirmation message.
>>> virginq = config.switchboards['virgin']
>>> qmsg, qdata = virginq.dequeue(virginq.files[0])
>>> print qmsg.as_string()
>>> from mailman.testing.helpers import get_queue_messages
>>> items = get_queue_messages('virgin')
>>> len(items)
1
>>> print items[0].msg.as_string()
MIME-Version: 1.0
...
Subject: confirm ...
From: confirm-[email protected]
From: alpha-confirm+[email protected]
To: [email protected]
...
<BLANKLINE>
......@@ -121,12 +134,27 @@ Mailman has sent her the confirmation message.
Once Anne confirms her registration, she will be made a member of the mailing
list.
>>> token = str(qmsg['subject']).split()[1].strip()
>>> from mailman.interfaces.domain import IDomainManager
>>> from mailman.interfaces.registrar import IRegistrar
>>> registrar = IRegistrar(getUtility(IDomainManager)['example.com'])
>>> registrar.confirm(token)
True
>>> def extract_token(message):
... return str(message['subject']).split()[1].strip()
>>> token = extract_token(items[0].msg)
>>> from mailman.commands.eml_confirm import Confirm
>>> confirm = Confirm()
>>> msg = message_from_string("""\
... To: alpha-confirm+{token}@example.com
... From: [email protected]
... Subject: Re: confirm {token}
...
... """.format(token=token))
>>> results = Results()
>>> print confirm.process(mlist, msg, {}, (token,), results)
ContinueProcessing.yes
>>> print unicode(results)
The results of your email command are provided below.
<BLANKLINE>
Confirmed
<BLANKLINE>
>>> user = user_manager.get_user('[email protected]')
>>> print user.real_name
......@@ -142,14 +170,14 @@ Anne is also now a member of the mailing list.
Joining a second list
=====================
---------------------
>>> mlist_2 = create_list('[email protected]')
>>> msg = message_from_string("""\
... From: Anne Person <[email protected]>
...
... """)
>>> print command.process(mlist_2, msg, {}, (), Results())
>>> print join.process(mlist_2, msg, {}, (), Results())
ContinueProcessing.yes
Anne of course, is still registered.
......@@ -164,10 +192,25 @@ But she is not a member of the mailing list.
One Anne confirms this subscription, she becomes a member of the mailing list.
>>> qmsg, qdata = virginq.dequeue(virginq.files[0])
>>> token = str(qmsg['subject']).split()[1].strip()
>>> registrar.confirm(token)
True
>>> items = get_queue_messages('virgin')
>>> len(items)
1
>>> token = extract_token(items[0].msg)
>>> msg = message_from_string("""\
... To: baker-confirm+{token}@example.com
... From: [email protected]
... Subject: Re: confirm {token}
...
... """.format(token=token))
>>> results = Results()
>>> print confirm.process(mlist_2, msg, {}, (token,), results)
ContinueProcessing.yes
>>> print unicode(results)
The results of your email command are provided below.
<BLANKLINE>
Confirmed
<BLANKLINE>
>>> print mlist_2.members.get_member('[email protected]')
<Member: Anne Person <[email protected]>
......@@ -180,10 +223,11 @@ Leaving a mailing list
The mail command 'leave' unsubscribes an email address from the mailing list.
'unsubscribe' is an alias for 'leave'.
>>> command = config.commands['leave']
>>> print command.name
>>> from mailman.commands.eml_membership import Leave
>>> leave = Leave()
>>> print leave.name
leave
>>> print command.description
>>> print leave.description
Leave this mailing list. You will be asked to confirm your request.
Anne is a member of the [email protected] mailing list, when she decides to
......@@ -191,7 +235,7 @@ leave it. She sends a message to the -leave address for the list and is sent
a confirmation message for her request.
>>> results = Results()
>>> print command.process(mlist_2, msg, {}, (), results)
>>> print leave.process(mlist_2, msg, {}, (), results)
ContinueProcessing.yes
>>> print unicode(results)
The results of your email command are provided below.
......@@ -225,7 +269,7 @@ will do.
Since Anne's alternative address has not yet been verified, it can't be used
to unsubscribe Anne from the alpha mailing list.
>>> print command.process(mlist, msg, {}, (), results)
>>> print leave.process(mlist, msg, {}, (), results)
ContinueProcessing.no
>>> print unicode(results)
......@@ -245,7 +289,7 @@ unsubscribe her from the list.
>>> address.verified_on = datetime.now()
>>> results = Results()
>>> print command.process(mlist, msg, {}, (), results)
>>> print leave.process(mlist, msg, {}, (), results)
ContinueProcessing.yes
>>> print unicode(results)
......@@ -268,21 +312,18 @@ Bart wants to join the alpha list, so he sends his subscription request.
...
... """)
>>> command = config.commands['join']
>>> print command.process(mlist, msg, {}, (), Results())
>>> print join.process(mlist, msg, {}, (), Results())
ContinueProcessing.yes
There are two messages in the virgin queue, one of which is the confirmation
message.
>>> from mailman.testing.helpers import get_queue_messages
>>> for item in get_queue_messages('virgin'):
... subject = str(item.msg['subject'])
... if subject.startswith('confirm'):
... if str(item.msg['subject']).startswith('confirm'):
... break
... else:
... raise AssertionError('No confirmation message')
>>> token = subject.split()[1].strip()
>>> token = extract_token(item.msg)
Bart is still not a user.
......@@ -295,14 +336,13 @@ a user of the system.
>>> msg = message_from_string("""\
... From: Bart Person <[email protected]>
... To: [email protected]
... To: alpha-confirm+{token}@example.com
... Subject: Re: confirm {token}
...
... """.format(token=token))
>>> command = config.commands['confirm']
>>> results = Results()
>>> print command.process(mlist, msg, {}, (token,), results)
>>> print confirm.process(mlist, msg, {}, (token,), results)
ContinueProcessing.yes
>>> print unicode(results)
......
......@@ -25,6 +25,7 @@ __all__ = [
]
from zope.component import getUtility
from zope.interface import implements
from mailman.core.i18n import _
......@@ -48,7 +49,7 @@ class Confirm:
if len(arguments) == 0:
print >> results, _('No confirmation token found')
return ContinueProcessing.no
succeeded = IRegistrar(mlist.domain).confirm(arguments[0])
succeeded = getUtility(IRegistrar).confirm(arguments[0])
if succeeded:
print >> results, _('Confirmed')
return ContinueProcessing.yes
......
......@@ -74,8 +74,7 @@ example:
print >> results, _(
'$self.name: No valid address found to subscribe')
return ContinueProcessing.no
registrar = IRegistrar(mlist.domain)
registrar.register(mlist, address, real_name)
getUtility(IRegistrar).register(mlist, address, real_name)
person = formataddr((real_name, address))
print >> results, _('Confirmation email sent to $person')
return ContinueProcessing.yes
......
......@@ -4,12 +4,6 @@
<include package="mailman.rest" file="configure.zcml"/>
<adapter
for="mailman.interfaces.domain.IDomain"
provides="mailman.interfaces.registrar.IRegistrar"
factory="mailman.app.registrar.Registrar"
/>
<adapter
for="mailman.interfaces.mailinglist.IMailingList"
provides="mailman.interfaces.autorespond.IAutoResponseSet"
......@@ -57,4 +51,9 @@
provides="mailman.interfaces.requests.IRequests"
/>
<utility
provides="mailman.interfaces.registrar.IRegistrar"
factory="mailman.app.registrar.Registrar"
/>
</configure>
......@@ -111,13 +111,9 @@ Non-existent domains cannot be removed.
Confirmation tokens
===================
Confirmation tokens can be added to either the email confirmation address...
Confirmation tokens can be added to the domain's url to generate the URL to a
page users can use to confirm their subscriptions.
>>> domain = manager['example.net']
>>> print domain.confirm_address('xyz')
[email protected]
...or the confirmation url.
>>> print domain.confirm_url('abc')
http://lists.example.net/confirm/abc
......@@ -7,24 +7,14 @@ The only thing they must supply is an email address, although there is
additional information they may supply. All registered email addresses must
be verified before Mailman will send them any list traffic.
>>> from mailman.app.registrar import Registrar
>>> from mailman.interfaces.registrar import IRegistrar
The IUserManager manages users, but it does so at a fairly low level.
Specifically, it does not handle verifications, email address syntax validity
checks, etc. The IRegistrar is the interface to the object handling all this
stuff.
>>> from mailman.interfaces.domain import IDomainManager
>>> from mailman.interfaces.registrar import IRegistrar
>>> from zope.component import getUtility
>>> domain = getUtility(IDomainManager)['example.com']
Get a registrar by adapting a domain.
>>> from zope.interface.verify import verifyObject
>>> registrar = IRegistrar(domain)
>>> verifyObject(IRegistrar, registrar)
True
>>> registrar = getUtility(IRegistrar)
Here is a helper function to check the token strings.
......@@ -48,7 +38,7 @@ Invalid email addresses
Addresses are registered within the context of a mailing list, mostly so that
confirmation emails can come from some place. You also need the email
address.
address of the user who is registering.
>>> mlist = create_list('[email protected]')
......@@ -107,7 +97,6 @@ There should be no records in the user manager for this address yet.
But this address is waiting for confirmation.
>>> from mailman.interfaces.pending import IPendings
>>> from zope.component import getUtility
>>> pendingdb = getUtility(IPendings)
>>> dump_msgdata(pendingdb.confirm(token, expunge=False))
......@@ -123,22 +112,17 @@ Verification by email
There is also a verification email sitting in the virgin queue now. This
message is sent to the user in order to verify the registered address.
>>> switchboard = config.switchboards['virgin']
>>> len(switchboard.files)
>>> from mailman.testing.helpers import get_queue_messages
>>> items = get_queue_messages('virgin')
>>> len(items)
1
>>> filebase = switchboard.files[0]
>>> qmsg, qdata = switchboard.dequeue(filebase)
>>> switchboard.finish(filebase)
>>> print qmsg.as_string()
>>> print items[0].msg.as_string()
MIME-Version: 1.0
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: 7bit
...
Subject: confirm ...
From: confirm-[email protected]
From: alpha-confirm+[email protected]
To: [email protected]
Message-ID: <...>
Date: ...
Precedence: bulk
...
<BLANKLINE>
Email Address Registration Confirmation
<BLANKLINE>
......@@ -161,7 +145,7 @@ message is sent to the user in order to verify the registered address.
<BLANKLINE>
[email protected]
<BLANKLINE>
>>> dump_msgdata(qdata)
>>> dump_msgdata(items[0].msgdata)
_parsemsg : False
listname : [email protected]
nodecorate : True
......@@ -179,7 +163,7 @@ appear in a URL in the body of the message.
The same token will appear in the From header.
>>> qmsg['from'] == 'confirm-' + token + '@example.com'
>>> qmsg['from'] == 'alpha-confirm+' + token + '@example.com'
True
It will also appear in the Subject header.
......@@ -187,9 +171,9 @@ It will also appear in the Subject header.
>>> qmsg['subject'] == 'confirm ' + token
True
The user would then validate their just registered address by clicking on a
url or responding to the message. Either way, the confirmation process
extracts the token and uses that to confirm the pending registration.
The user would then validate their registered address by clicking on a url or
responding to the message. Either way, the confirmation process extracts the
token and uses that to confirm the pending registration.
>>> registrar.confirm(token)
True
......@@ -219,10 +203,10 @@ will work. The second one is ignored.
>>> token = registrar.register(mlist, '[email protected]')
>>> check_token(token)
ok
>>> filebase = switchboard.files[0]
>>> qmsg, qdata = switchboard.dequeue(filebase)
>>> switchboard.finish(filebase)
>>> sent_token = extract_token(qmsg)
>>> items = get_queue_messages('virgin')
>>> len(items)
1
>>> sent_token = extract_token(items[0].msg)
>>> token == sent_token
True
>>> registrar.confirm(token)
......@@ -240,10 +224,11 @@ confirmation step is completed.
... mlist, '[email protected]', 'Claire Person')
>>> print user_manager.get_user('[email protected]')
None
>>> filebase = switchboard.files[0]
>>> qmsg, qdata = switchboard.dequeue(filebase)
>>> switchboard.finish(filebase)
>>> registrar.confirm(token)
>>> items = get_queue_messages('virgin')
>>> len(items)
1
>>> sent_token = extract_token(items[0].msg)
>>> registrar.confirm(sent_token)
True
>>> user_manager.get_user('[email protected]')
<User "Claire Person" at ...>
......@@ -296,10 +281,12 @@ can be used.
>>> dperson.register('[email protected]', 'David Person')
<Address: David Person <[email protected]> [not verified] at ...>
>>> token = registrar.register(mlist, '[email protected]')
>>> filebase = switchboard.files[0]
>>> qmsg, qdata = switchboard.dequeue(filebase)
>>> switchboard.finish(filebase)
>>> registrar.confirm(token)
>>> items = get_queue_messages('virgin')
>>> len(items)
1
>>> sent_token = extract_token(items[0].msg)
>>> registrar.confirm(sent_token)
True
>>> user = user_manager.get_user('[email protected]')
>>> user is dperson
......@@ -315,14 +302,14 @@ Corner cases
============
If you try to confirm a token that doesn't exist in the pending database, the
confirm method will just return None.
confirm method will just return False.
>>> registrar.confirm(bytes('no token'))
False
Likewise, if you try to confirm, through the IUserRegistrar interface, a token
that doesn't match a registration even, you will get None. However, the
pending even matched with that token will still be removed.
that doesn't match a registration event, you will get None. However, the
pending event matched with that token will still be removed.
>>> from mailman.interfaces.pending import IPendable
>>> from zope.interface import implements
......
......@@ -80,15 +80,6 @@ class IDomain(Interface):
E.g. [email protected]"""),
))
def confirm_address(token=''):
"""The address used for various forms of email confirmation.
:param token: The confirmation token to use in the email address.
:type token: string
:return: The email confirmation address.
:rtype: string
"""
def confirm_url(token=''):
"""The url used for various forms of confirmation.
......
......@@ -81,10 +81,6 @@ class Domain(Model):
# no netloc member; yes it does
return urlparse(self.base_url).netloc
def confirm_address(self, token=''):
"""See `IDomain`."""
return 'confirm-{0}@{1}'.format(token, self.email_host)
def confirm_url(self, token=''):
"""See `IDomain`."""
return urljoin(self.base_url, 'confirm/' + token)
......
......@@ -135,10 +135,9 @@ address, and the other is the results of his email command.
... return str(item.msg['subject'])
>>> messages = sorted(get_queue_messages('virgin'), key=sortkey)
>>> from mailman.interfaces.domain import IDomainManager
>>> from mailman.interfaces.registrar import IRegistrar
>>> from zope.component import getUtility
>>> registrar = IRegistrar(getUtility(IDomainManager)['example.com'])
>>> registrar = getUtility(IRegistrar)
>>> for item in messages:
... subject = item.msg['subject']
... print 'Subject:', subject
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment