diff options
| author | Barry Warsaw | 2009-12-12 17:03:30 -0500 |
|---|---|---|
| committer | Barry Warsaw | 2009-12-12 17:03:30 -0500 |
| commit | 725ebe36e2548c5da711087640bd423181411241 (patch) | |
| tree | 8aae553f5b05d3889511dcbce8dd92f3fa7c469e /src | |
| parent | 3c4c8f9c383b6e92e0a9a970079b296a4ac30e88 (diff) | |
| download | mailman-725ebe36e2548c5da711087640bd423181411241.tar.gz mailman-725ebe36e2548c5da711087640bd423181411241.tar.zst mailman-725ebe36e2548c5da711087640bd423181411241.zip | |
Diffstat (limited to 'src')
| -rw-r--r-- | src/mailman/app/lifecycle.py | 2 | ||||
| -rw-r--r-- | src/mailman/app/registrar.py | 27 | ||||
| -rw-r--r-- | src/mailman/commands/docs/membership.txt | 130 | ||||
| -rw-r--r-- | src/mailman/commands/eml_confirm.py | 3 | ||||
| -rw-r--r-- | src/mailman/commands/eml_membership.py | 3 | ||||
| -rw-r--r-- | src/mailman/config/configure.zcml | 11 | ||||
| -rw-r--r-- | src/mailman/docs/domains.txt | 8 | ||||
| -rw-r--r-- | src/mailman/docs/registration.txt | 79 | ||||
| -rw-r--r-- | src/mailman/interfaces/domain.py | 9 | ||||
| -rw-r--r-- | src/mailman/model/domain.py | 4 | ||||
| -rw-r--r-- | src/mailman/queue/docs/command.txt | 3 |
11 files changed, 142 insertions, 137 deletions
diff --git a/src/mailman/app/lifecycle.py b/src/mailman/app/lifecycle.py index 971769e05..f3f01779a 100644 --- a/src/mailman/app/lifecycle.py +++ b/src/mailman/app/lifecycle.py @@ -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) diff --git a/src/mailman/app/registrar.py b/src/mailman/app/registrar.py index 5cf9cc8a0..1894521cb 100644 --- a/src/mailman/app/registrar.py +++ b/src/mailman/app/registrar.py @@ -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) diff --git a/src/mailman/commands/docs/membership.txt b/src/mailman/commands/docs/membership.txt index b91dd17f1..a0af15ba7 100644 --- a/src/mailman/commands/docs/membership.txt +++ b/src/mailman/commands/docs/membership.txt @@ -1,14 +1,23 @@ -================== -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 address=myotheraddress@example.com <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('alpha@example.com') 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-...@example.com + From: alpha-confirm+...@example.com To: anne@example.com ... <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: anne@example.com + ... 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('anne@example.com') >>> 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('baker@example.com') >>> msg = message_from_string("""\ ... From: Anne Person <anne@example.com> ... ... """) - >>> 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: anne@example.com + ... 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('anne@example.com') <Member: Anne Person <anne@example.com> @@ -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 baker@example.com 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 <bart@example.com> - ... To: alpha-confirm@example.com + ... 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) diff --git a/src/mailman/commands/eml_confirm.py b/src/mailman/commands/eml_confirm.py index 510033098..5232512b3 100644 --- a/src/mailman/commands/eml_confirm.py +++ b/src/mailman/commands/eml_confirm.py @@ -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 diff --git a/src/mailman/commands/eml_membership.py b/src/mailman/commands/eml_membership.py index 2328e1a41..8b6c576f6 100644 --- a/src/mailman/commands/eml_membership.py +++ b/src/mailman/commands/eml_membership.py @@ -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 diff --git a/src/mailman/config/configure.zcml b/src/mailman/config/configure.zcml index 378feebf9..5e8de8527 100644 --- a/src/mailman/config/configure.zcml +++ b/src/mailman/config/configure.zcml @@ -5,12 +5,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" factory="mailman.model.autorespond.AutoResponseSet" @@ -57,4 +51,9 @@ provides="mailman.interfaces.requests.IRequests" /> + <utility + provides="mailman.interfaces.registrar.IRegistrar" + factory="mailman.app.registrar.Registrar" + /> + </configure> diff --git a/src/mailman/docs/domains.txt b/src/mailman/docs/domains.txt index 3d1de7083..5673e6ee9 100644 --- a/src/mailman/docs/domains.txt +++ b/src/mailman/docs/domains.txt @@ -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') - confirm-xyz@example.net - -...or the confirmation url. - >>> print domain.confirm_url('abc') http://lists.example.net/confirm/abc diff --git a/src/mailman/docs/registration.txt b/src/mailman/docs/registration.txt index bf5329ec9..8f473403e 100644 --- a/src/mailman/docs/registration.txt +++ b/src/mailman/docs/registration.txt @@ -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('alpha@example.com') @@ -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-...@example.com + From: alpha-confirm+...@example.com To: aperson@example.com - 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> postmaster@example.com <BLANKLINE> - >>> dump_msgdata(qdata) + >>> dump_msgdata(items[0].msgdata) _parsemsg : False listname : alpha@example.com 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, 'bperson@example.com') >>> 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, 'cperson@example.com', 'Claire Person') >>> print user_manager.get_user('cperson@example.com') 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('cperson@example.com') <User "Claire Person" at ...> @@ -296,10 +281,12 @@ can be used. >>> dperson.register('david.person@example.com', 'David Person') <Address: David Person <david.person@example.com> [not verified] at ...> >>> token = registrar.register(mlist, 'david.person@example.com') - >>> 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('david.person@example.com') >>> 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 diff --git a/src/mailman/interfaces/domain.py b/src/mailman/interfaces/domain.py index 538cd0f74..a16d41466 100644 --- a/src/mailman/interfaces/domain.py +++ b/src/mailman/interfaces/domain.py @@ -80,15 +80,6 @@ class IDomain(Interface): E.g. postmaster@example.com"""), )) - 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. diff --git a/src/mailman/model/domain.py b/src/mailman/model/domain.py index a30c9c581..a24b8a1bb 100644 --- a/src/mailman/model/domain.py +++ b/src/mailman/model/domain.py @@ -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) diff --git a/src/mailman/queue/docs/command.txt b/src/mailman/queue/docs/command.txt index c63c323b4..4ffb0323c 100644 --- a/src/mailman/queue/docs/command.txt +++ b/src/mailman/queue/docs/command.txt @@ -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 |
