diff options
Diffstat (limited to 'src/mailman/interfaces')
| -rw-r--r-- | src/mailman/interfaces/mailinglist.py | 16 | ||||
| -rw-r--r-- | src/mailman/interfaces/member.py | 10 | ||||
| -rw-r--r-- | src/mailman/interfaces/pending.py | 6 | ||||
| -rw-r--r-- | src/mailman/interfaces/registrar.py | 90 | ||||
| -rw-r--r-- | src/mailman/interfaces/roster.py | 21 | ||||
| -rw-r--r-- | src/mailman/interfaces/workflow.py | 80 |
6 files changed, 170 insertions, 53 deletions
diff --git a/src/mailman/interfaces/mailinglist.py b/src/mailman/interfaces/mailinglist.py index 23d2fadf4..f112b2a11 100644 --- a/src/mailman/interfaces/mailinglist.py +++ b/src/mailman/interfaces/mailinglist.py @@ -25,6 +25,7 @@ __all__ = [ 'IMailingList', 'Personalization', 'ReplyToMunging', + 'SubscriptionPolicy', ] @@ -53,6 +54,18 @@ class ReplyToMunging(Enum): explicit_header = 2 +class SubscriptionPolicy(Enum): + # Neither confirmation, nor moderator approval is required. + open = 0 + # The user must confirm the subscription. + confirm = 1 + # The moderator must approve the subscription. + moderate = 2 + # The user must first confirm their subscription, and then if that is + # successful, the moderator must also approve it. + confirm_then_moderate = 3 + + class IMailingList(Interface): """A mailing list.""" @@ -234,6 +247,9 @@ class IMailingList(Interface): deliver disabled or not, or of the type of digest they are to receive.""") + subscription_policy = Attribute( + """The policy for subscribing new members to the list.""") + subscribers = Attribute( """An iterator over all IMembers subscribed to this list, with any role. diff --git a/src/mailman/interfaces/member.py b/src/mailman/interfaces/member.py index c06cc95b1..d863e1ef1 100644 --- a/src/mailman/interfaces/member.py +++ b/src/mailman/interfaces/member.py @@ -123,7 +123,7 @@ class MembershipIsBannedError(MembershipError): """The address is not allowed to subscribe to the mailing list.""" def __init__(self, mlist, address): - super(MembershipIsBannedError, self).__init__() + super().__init__() self._mlist = mlist self._address = address @@ -175,6 +175,14 @@ class IMember(Interface): user = Attribute( """The user associated with this member.""") + subscriber = Attribute( + """The object representing how this member is subscribed. + + This will be an ``IAddress`` if the user is subscribed via an explicit + address, otherwise if the the user is subscribed via their preferred + address, it will be an ``IUser``. + """) + preferences = Attribute( """This member's preferences.""") diff --git a/src/mailman/interfaces/pending.py b/src/mailman/interfaces/pending.py index 9907aa779..222f0dfbf 100644 --- a/src/mailman/interfaces/pending.py +++ b/src/mailman/interfaces/pending.py @@ -82,11 +82,11 @@ class IPendings(Interface): :return: A token string for inclusion in urls and email confirmations. """ - def confirm(token, expunge=True): + def confirm(token, *, expunge=True): """Return the IPendable matching the token. :param token: The token string for the IPendable given by the `.add()` - method. + method, or None if there is no record associated with the token. :param expunge: A flag indicating whether the pendable record should also be removed from the database or not. :return: The matching IPendable or None if no match was found. @@ -94,3 +94,5 @@ class IPendings(Interface): def evict(): """Remove all pended items whose lifetime has expired.""" + + count = Attribute('The number of pendables in the pendings database.') diff --git a/src/mailman/interfaces/registrar.py b/src/mailman/interfaces/registrar.py index 7d3cf9c25..ff3f26898 100644 --- a/src/mailman/interfaces/registrar.py +++ b/src/mailman/interfaces/registrar.py @@ -35,79 +35,75 @@ from zope.interface import Interface class ConfirmationNeededEvent: """Triggered when an address needs confirmation. - Addresses must be verified before they can receive messages or post to - mailing list. When an address is registered with Mailman, via the - `IRegistrar` interface, an `IPendable` is created which represents the - pending registration. This pending registration is stored in the - database, keyed by a token. Then this event is triggered. - - There may be several ways to confirm an email address. On some sites, - registration may immediately produce a verification, e.g. because it is on - a known intranet. Or verification may occur via external database lookup - (e.g. LDAP). On most public mailing lists, a mail-back confirmation is - sent to the address, and only if they reply to the mail-back, or click on - an embedded link, is the registered address confirmed. + Addresses must be verified before they can receive messages or post + to mailing list. The confirmation message is sent to the user when + this event is triggered. """ - def __init__(self, mlist, pendable, token): + def __init__(self, mlist, token, email): self.mlist = mlist - self.pendable = pendable self.token = token + self.email = email class IRegistrar(Interface): - """Interface for registering and verifying email addresses and users. + """Interface for subscribing addresses and users. This is a higher level interface to user registration, email address confirmation, etc. than the IUserManager. The latter does no validation, syntax checking, or confirmation, while this interface does. """ - def register(mlist, email, display_name=None, delivery_mode=None): - """Register the email address, requesting verification. + def register(mlist, subscriber=None, *, + pre_verified=False, pre_confirmed=False, pre_approved=False): + """Subscribe an address or user according to subscription policies. - No `IAddress` or `IUser` is created during this step, but after - successful confirmation, it is guaranteed that an `IAddress` with a - linked `IUser` will exist. When a verified `IAddress` matching - `email` already exists, this method will do nothing, except link a new - `IUser` to the `IAddress` if one is not yet associated with the - email address. + The mailing list's subscription policy is used to subscribe + `subscriber` to the given mailing list. The subscriber can be + an ``IUser``, in which case the user must have a preferred + address, and that preferred address will be subscribed. The + subscriber can also be an ``IAddress``, in which case the + address will be subscribed. - In all cases, the email address is sanity checked for validity first. + The workflow may pause (i.e. be serialized, saved, and + suspended) when some out-of-band confirmation step is required. + For example, if the user must confirm, or the moderator must + approve the subscription. Use the ``confirm(token)`` method to + resume the workflow. - :param mlist: The mailing list that is the focus of this registration. + :param mlist: The mailing list to subscribe to. :type mlist: `IMailingList` - :param email: The email address to register. - :type email: str - :param display_name: The optional display name of the user. - :type display_name: str - :param delivery_mode: The optional delivery mode for this - registration. If not given, regular delivery is used. - :type delivery_mode: `DeliveryMode` - :return: The confirmation token string. - :rtype: str - :raises InvalidEmailAddressError: if the address is not allowed. + :param subscriber: The user or address to subscribe. + :type email: ``IUser`` or ``IAddress`` + :return: The confirmation token string, or None if the workflow + completes (i.e. the member has been subscribed). + :rtype: str or None + :raises MembershipIsBannedError: when the address being subscribed + appears in the global or list-centric bans. """ def confirm(token): - """Confirm the pending registration matched to the given `token`. + """Continue any paused workflow. - Confirmation ensures that the IAddress exists and is linked to an - IUser, with the latter being created and linked if necessary. + Confirmation may occur after the user confirms their + subscription request, or their email address must be verified, + or the moderator must approve the subscription request. - :param token: A token matching a pending event with a type of - 'registration'. - :return: Boolean indicating whether the confirmation succeeded or - not. It may fail if the token is no longer in the database, or if - the token did not match a registration event. + :param token: A token matching a workflow. + :type token: string + :return: The new token for any follow up confirmation, or None if the + user was subscribed. + :rtype: str or None + :raises LookupError: when no workflow is associated with the token. """ def discard(token): - """Discard the pending registration matched to the given `token`. - - The event record is discarded and the IAddress is not verified. No - IUser is created. + """Discard the workflow matched to the given `token`. :param token: A token matching a pending event with a type of 'registration'. + :raises LookupError: when no workflow is associated with the token. """ + + def evict(): + """Evict all saved workflows which have expired.""" diff --git a/src/mailman/interfaces/roster.py b/src/mailman/interfaces/roster.py index 5d0b9d6c2..af473a553 100644 --- a/src/mailman/interfaces/roster.py +++ b/src/mailman/interfaces/roster.py @@ -53,11 +53,26 @@ class IRoster(Interface): managed by this roster. """) - def get_member(address): + def get_member(email): """Get the member for the given address. - :param address: The email address to search for. - :type address: text + *Note* that it is possible for an email to be subscribed to a + mailing list twice, once through its explicit address and once + indirectly through a user's preferred address. In this case, + this API always returns the explicit address. Use + ``get_memberships()`` to return them all. + + :param email: The email address to search for. + :type email: string :return: The member if found, otherwise None :rtype: `IMember` or None """ + + def get_memberships(email): + """Get the memberships for the given address. + + :param email: The email address to search for. + :type email: string + :return: All the memberships associated with this email address. + :rtype: sequence of length 0, 1, or 2 of ``IMember`` + """ diff --git a/src/mailman/interfaces/workflow.py b/src/mailman/interfaces/workflow.py new file mode 100644 index 000000000..f80e38547 --- /dev/null +++ b/src/mailman/interfaces/workflow.py @@ -0,0 +1,80 @@ +# Copyright (C) 2015 by the Free Software Foundation, Inc. +# +# This file is part of GNU Mailman. +# +# GNU Mailman is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. + +"""Interfaces describing the state of a workflow.""" + +__all__ = [ + 'IWorkflowState', + 'IWorkflowStateManager', + ] + + +from zope.interface import Attribute, Interface + + + +class IWorkflowState(Interface): + """The state of a workflow.""" + + name = Attribute('The name of the workflow.') + + token = Attribute('A unique key identifying the workflow instance.') + + step = Attribute("This workflow's next step.") + + data = Attribute('Additional data (may be JSON-encoded).') + + + +class IWorkflowStateManager(Interface): + """The workflow states manager.""" + + def save(name, token, step, data=None): + """Save the state of a workflow. + + :param name: The name of the workflow. + :type name: str + :param token: A unique token identifying this workflow instance. + :type token: str + :param step: The next step for this workflow. + :type step: str + :param data: Additional data (workflow-specific). + :type data: str + """ + + def restore(name, token): + """Get the saved state for a workflow or None if nothing was saved. + + :param name: The name of the workflow. + :type name: str + :param token: A unique token identifying this workflow instance. + :type token: str + :return: The saved state associated with this name/token pair, or None + if the pair isn't in the database. + :rtype: ``IWorkflowState`` + """ + + def discard(name, token): + """Throw away the saved state for a workflow. + + :param name: The name of the workflow. + :type name: str + :param token: A unique token identifying this workflow instance. + :type token: str + """ + + count = Attribute('The number of saved workflows in the database.') |
