summaryrefslogtreecommitdiff
path: root/src/mailman/model/docs
diff options
context:
space:
mode:
authorBarry Warsaw2015-04-15 10:05:35 -0400
committerBarry Warsaw2015-04-15 10:05:35 -0400
commit67fd8d6dbcf8849b3c2f8cb42a10aed465ace76c (patch)
tree97eca0ae33c97015e4d2fca31e7792e2a929fa8d /src/mailman/model/docs
parent7317b94a0b746f0287ecbc5654ec544ce0112adb (diff)
parent3e7dffa750a3e7bb15ac10b711832696554ba03a (diff)
downloadmailman-67fd8d6dbcf8849b3c2f8cb42a10aed465ace76c.tar.gz
mailman-67fd8d6dbcf8849b3c2f8cb42a10aed465ace76c.tar.zst
mailman-67fd8d6dbcf8849b3c2f8cb42a10aed465ace76c.zip
* Mailing list subscription policy work flow has been completely rewritten.
It now properly supports email verification and subscription confirmation by the user, and approval by the moderator using unique tokens. ``IMailingList`` objects now have a ``subscription_policy`` attribute.
Diffstat (limited to 'src/mailman/model/docs')
-rw-r--r--src/mailman/model/docs/membership.rst38
-rw-r--r--src/mailman/model/docs/pending.rst10
-rw-r--r--src/mailman/model/docs/registration.rst353
3 files changed, 101 insertions, 300 deletions
diff --git a/src/mailman/model/docs/membership.rst b/src/mailman/model/docs/membership.rst
index 60ccd1ac1..0fd748d6a 100644
--- a/src/mailman/model/docs/membership.rst
+++ b/src/mailman/model/docs/membership.rst
@@ -228,6 +228,38 @@ regardless of their role.
fperson@example.com MemberRole.nonmember
+Subscriber type
+===============
+
+Members can be subscribed to a mailing list either via an explicit address, or
+indirectly through a user's preferred address. Sometimes you want to know
+which one it is.
+
+Herb subscribes to the mailing list via an explicit address.
+
+ >>> herb = user_manager.create_address(
+ ... 'hperson@example.com', 'Herb Person')
+ >>> herb_member = mlist.subscribe(herb)
+
+Iris subscribes to the mailing list via her preferred address.
+
+ >>> iris = user_manager.make_user(
+ ... 'iperson@example.com', 'Iris Person')
+ >>> preferred = list(iris.addresses)[0]
+ >>> from mailman.utilities.datetime import now
+ >>> preferred.verified_on = now()
+ >>> iris.preferred_address = preferred
+ >>> iris_member = mlist.subscribe(iris)
+
+When we need to know which way a member is subscribed, we can look at the this
+attribute.
+
+ >>> herb_member.subscriber
+ <Address: Herb Person <hperson@example.com> [not verified] at ...>
+ >>> iris_member.subscriber
+ <User "Iris Person" (5) at ...>
+
+
Moderation actions
==================
@@ -250,6 +282,8 @@ should go through the normal moderation checks.
aperson@example.com MemberRole.member Action.defer
bperson@example.com MemberRole.member Action.defer
cperson@example.com MemberRole.member Action.defer
+ hperson@example.com MemberRole.member Action.defer
+ iperson@example.com MemberRole.member Action.defer
Postings by nonmembers are held for moderator approval by default.
@@ -272,7 +306,7 @@ though that the address they're changing to must be verified.
>>> gwen_member = bee.subscribe(gwen_address)
>>> for m in bee.members.members:
... print(m.member_id.int, m.mailing_list.list_id, m.address.email)
- 7 bee.example.com gwen@example.com
+ 9 bee.example.com gwen@example.com
Gwen gets a email address.
@@ -288,7 +322,7 @@ Now her membership reflects the new address.
>>> for m in bee.members.members:
... print(m.member_id.int, m.mailing_list.list_id, m.address.email)
- 7 bee.example.com gperson@example.com
+ 9 bee.example.com gperson@example.com
Events
diff --git a/src/mailman/model/docs/pending.rst b/src/mailman/model/docs/pending.rst
index a634322a1..4a14edb2a 100644
--- a/src/mailman/model/docs/pending.rst
+++ b/src/mailman/model/docs/pending.rst
@@ -13,6 +13,11 @@ In order to pend an event, you first need a pending database.
>>> from zope.component import getUtility
>>> pendingdb = getUtility(IPendings)
+There are nothing in the pendings database.
+
+ >>> pendingdb.count
+ 0
+
The pending database can add any ``IPendable`` to the database, returning a
token that can be used in urls and such.
::
@@ -33,6 +38,11 @@ token that can be used in urls and such.
>>> len(token)
40
+There's exactly one entry in the pendings database now.
+
+ >>> pendingdb.count
+ 1
+
There's not much you can do with tokens except to *confirm* them, which
basically means returning the `IPendable` structure (as a dictionary) from the
database that matches the token. If the token isn't in the database, None is
diff --git a/src/mailman/model/docs/registration.rst b/src/mailman/model/docs/registration.rst
index 2d2aa8ec7..fc7ad6f1a 100644
--- a/src/mailman/model/docs/registration.rst
+++ b/src/mailman/model/docs/registration.rst
@@ -1,333 +1,90 @@
-====================
-Address registration
-====================
-
-Before users can join a mailing list, they must first register with Mailman.
-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.
+============
+Registration
+============
-The ``IUserManager`` manages users, but it does so at a fairly low level.
-Specifically, it does not handle verification, email address syntax validity
-checks, etc. The ``IRegistrar`` is the interface to the object handling all
-this stuff.
+When a user wants to join a mailing list, they must register and verify their
+email address. Then depending on how the mailing list is configured, they may
+need to confirm their subscription and have it approved by the list
+moderator. The ``IRegistrar`` interface manages this work flow.
>>> from mailman.interfaces.registrar import IRegistrar
- >>> from zope.component import getUtility
- >>> registrar = getUtility(IRegistrar)
-
-Here is a helper function to check the token strings.
-
- >>> def check_token(token):
- ... assert isinstance(token, str), 'Not a string'
- ... assert len(token) == 40, 'Unexpected length: %d' % len(token)
- ... assert token.isalnum(), 'Not alphanumeric'
- ... print('ok')
-
-Here is a helper function to extract tokens from confirmation messages.
-
- >>> import re
- >>> cre = re.compile('http://lists.example.com/confirm/(.*)')
- >>> def extract_token(msg):
- ... mo = cre.search(msg.get_payload())
- ... return mo.group(1)
+Registrars adapt mailing lists.
-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 of the user who is registering.
-
- >>> mlist = create_list('alpha@example.com')
+ >>> from mailman.interfaces.mailinglist import SubscriptionPolicy
+ >>> mlist = create_list('ant@example.com')
>>> mlist.send_welcome_message = False
+ >>> mlist.subscription_policy = SubscriptionPolicy.open
+ >>> registrar = IRegistrar(mlist)
-Some amount of sanity checks are performed on the email address, although
-honestly, not as much as probably should be done. Still, some patently bad
-addresses are rejected outright.
-
-
-Register an email address
-=========================
-
-Registration of an unknown address creates nothing until the confirmation step
-is complete. No ``IUser`` or ``IAddress`` is created at registration time,
-but a record is added to the pending database, and the token for that record
-is returned.
-
- >>> token = registrar.register(mlist, 'aperson@example.com', 'Anne Person')
- >>> check_token(token)
- ok
-
-There should be no records in the user manager for this address yet.
+Usually, addresses are registered, but users with preferred addresses can be
+registered too.
>>> from mailman.interfaces.usermanager import IUserManager
>>> from zope.component import getUtility
- >>> user_manager = getUtility(IUserManager)
- >>> print(user_manager.get_user('aperson@example.com'))
- None
- >>> print(user_manager.get_address('aperson@example.com'))
- None
-
-But this address is waiting for confirmation.
-
- >>> from mailman.interfaces.pending import IPendings
- >>> pendingdb = getUtility(IPendings)
-
- >>> dump_msgdata(pendingdb.confirm(token, expunge=False))
- delivery_mode: regular
- display_name : Anne Person
- email : aperson@example.com
- list_id : alpha.example.com
- type : registration
-
-
-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.
-
- >>> 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: alpha-confirm+...@example.com
- To: aperson@example.com
- ...
- <BLANKLINE>
- Email Address Registration Confirmation
- <BLANKLINE>
- Hello, this is the GNU Mailman server at example.com.
- <BLANKLINE>
- We have received a registration request for the email address
- <BLANKLINE>
- aperson@example.com
- <BLANKLINE>
- Before you can start using GNU Mailman at this site, you must first
- confirm that this is your email address. You can do this by replying to
- this message, keeping the Subject header intact. Or you can visit this
- web page
- <BLANKLINE>
- http://lists.example.com/confirm/...
- <BLANKLINE>
- If you do not wish to register this email address simply disregard this
- message. If you think you are being maliciously subscribed to the list,
- or have any other questions, you may contact
- <BLANKLINE>
- alpha-owner@example.com
- <BLANKLINE>
- >>> dump_msgdata(items[0].msgdata)
- _parsemsg : False
- listid : alpha.example.com
- nodecorate : True
- recipients : {'aperson@example.com'}
- reduced_list_headers: True
- version : 3
-
-The confirmation token shows up in several places, each of which provides an
-easy way for the user to complete the confirmation. The token will always
-appear in a URL in the body of the message.
-
- >>> sent_token = extract_token(items[0].msg)
- >>> sent_token == token
- True
-
-The same token will appear in the ``From`` header.
-
- >>> items[0].msg['from'] == 'alpha-confirm+' + token + '@example.com'
- True
-
-It will also appear in the ``Subject`` header.
+ >>> anne = getUtility(IUserManager).create_address(
+ ... 'anne@example.com', 'Anne Person')
- >>> items[0].msg['subject'] == 'confirm ' + token
- True
-
-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
-
-Now, there is an `IAddress` in the database matching the address, as well as
-an `IUser` linked to this address. The `IAddress` is verified.
-
- >>> found_address = user_manager.get_address('aperson@example.com')
- >>> found_address
- <Address: Anne Person <aperson@example.com> [verified] at ...>
- >>> found_user = user_manager.get_user('aperson@example.com')
- >>> found_user
- <User "Anne Person" (...) at ...>
- >>> found_user.controls(found_address.email)
- True
- >>> from datetime import datetime
- >>> isinstance(found_address.verified_on, datetime)
- True
+Register an email address
+=========================
-Non-standard registrations
-==========================
+When the registration steps involve confirmation or moderator approval, the
+process will pause until these steps are completed. A unique token is created
+which represents this work flow.
-If you try to confirm a registration token twice, of course only the first one
-will work. The second one is ignored.
+Anne attempts to join the mailing list.
- >>> token = registrar.register(mlist, 'bperson@example.com')
- >>> check_token(token)
- ok
- >>> items = get_queue_messages('virgin')
- >>> len(items)
- 1
- >>> sent_token = extract_token(items[0].msg)
- >>> token == sent_token
- True
- >>> registrar.confirm(token)
- True
- >>> registrar.confirm(token)
- False
+ >>> token = registrar.register(anne)
-If an address is in the system, but that address is not linked to a user yet
-and the address is not yet validated, then no user is created until the
-confirmation step is completed.
+Because her email address has not yet been verified, she has not yet become a
+member of the mailing list.
- >>> user_manager.create_address('cperson@example.com')
- <Address: cperson@example.com [not verified] at ...>
- >>> token = registrar.register(
- ... mlist, 'cperson@example.com', 'Claire Person')
- >>> print(user_manager.get_user('cperson@example.com'))
+ >>> print(mlist.members.get_member('anne@example.com'))
None
- >>> 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 ...>
- >>> user_manager.get_address('cperson@example.com')
- <Address: cperson@example.com [verified] at ...>
-
-Even if the address being registered has already been verified, the
-registration sends a confirmation.
-
- >>> token = registrar.register(mlist, 'cperson@example.com')
- >>> token is not None
- True
-
-Discarding
-==========
+Once she verifies her email address, she will become a member of the mailing
+list. In this case, verifying implies that she also confirms her wish to join
+the mailing list.
-A confirmation token can also be discarded, say if the user changes his or her
-mind about registering. When discarded, no `IAddress` or `IUser` is created.
-::
+ >>> registrar.confirm(token)
+ >>> mlist.members.get_member('anne@example.com')
+ <Member: Anne Person <anne@example.com> on ant@example.com
+ as MemberRole.member>
- >>> token = registrar.register(mlist, 'eperson@example.com', 'Elly Person')
- >>> check_token(token)
- ok
- >>> registrar.discard(token)
- >>> print(pendingdb.confirm(token))
- None
- >>> print(user_manager.get_address('eperson@example.com'))
- None
- >>> print(user_manager.get_user('eperson@example.com'))
- None
- # Clear the virgin queue of all the preceding confirmation messages.
- >>> ignore = get_queue_messages('virgin')
+Register a user
+===============
+Users can also register, but they must have a preferred address. The mailing
+list will deliver messages to this preferred address.
-Registering a new address for an existing user
-==============================================
+ >>> bart = getUtility(IUserManager).make_user(
+ ... 'bart@example.com', 'Bart Person')
-When a new address for an existing user is registered, there isn't too much
-different except that the new address will still need to be verified before it
-can be used.
-::
+Bart verifies his address and makes it his preferred address.
>>> from mailman.utilities.datetime import now
- >>> dperson = user_manager.create_user(
- ... 'dperson@example.com', 'Dave Person')
- >>> dperson
- <User "Dave Person" (...) at ...>
- >>> address = user_manager.get_address('dperson@example.com')
- >>> address.verified_on = now()
-
- >>> from operator import attrgetter
- >>> dump_list(repr(address) for address in dperson.addresses)
- <Address: Dave Person <dperson@example.com> [verified] at ...>
- >>> 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')
-
- >>> 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
- True
- >>> user
- <User "Dave Person" (...) at ...>
- >>> dump_list(repr(address) for address in user.addresses)
- <Address: Dave Person <dperson@example.com> [verified] at ...>
- <Address: David Person <david.person@example.com> [verified] at ...>
-
-
-Corner cases
-============
-
-If you try to confirm a token that doesn't exist in the pending database, the
-confirm method will just return False.
-
- >>> registrar.confirm(bytes(b'no token'))
- False
-
-Likewise, if you try to confirm, through the `IRegistrar` interface, a token
-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 implementer
-
- >>> @implementer(IPendable)
- ... class SimplePendable(dict):
- ... pass
-
- >>> pendable = SimplePendable(type='foo', bar='baz')
- >>> token = pendingdb.add(pendable)
- >>> registrar.confirm(token)
- False
- >>> print(pendingdb.confirm(token))
- None
-
-
-Registration and subscription
-=============================
+ >>> preferred = list(bart.addresses)[0]
+ >>> preferred.verified_on = now()
+ >>> bart.preferred_address = preferred
-Fred registers with Mailman at the same time that he subscribes to a mailing
-list.
+The mailing list's subscription policy does not require Bart to confirm his
+subscription, but the moderate does want to approve all subscriptions.
- >>> token = registrar.register(
- ... mlist, 'fred.person@example.com', 'Fred Person')
+ >>> mlist.subscription_policy = SubscriptionPolicy.moderate
-Before confirmation, Fred is not a member of the mailing list.
+Now when Bart registers as a user for the mailing list, a token will still be
+generated, but this is only used by the moderator. At first, Bart is not
+subscribed to the mailing list.
- >>> print(mlist.members.get_member('fred.person@example.com'))
+ >>> token = registrar.register(bart)
+ >>> print(mlist.members.get_member('bart@example.com'))
None
-But after confirmation, he is.
+When the moderator confirms Bart's subscription, he joins the mailing list.
>>> registrar.confirm(token)
- True
- >>> print(mlist.members.get_member('fred.person@example.com'))
- <Member: Fred Person <fred.person@example.com>
- on alpha@example.com as MemberRole.member>
+ >>> mlist.members.get_member('bart@example.com')
+ <Member: Bart Person <bart@example.com> on ant@example.com
+ as MemberRole.member>