summaryrefslogtreecommitdiff
path: root/src/mailman/interfaces
diff options
context:
space:
mode:
Diffstat (limited to 'src/mailman/interfaces')
-rw-r--r--src/mailman/interfaces/mailinglist.py16
-rw-r--r--src/mailman/interfaces/member.py10
-rw-r--r--src/mailman/interfaces/pending.py6
-rw-r--r--src/mailman/interfaces/registrar.py90
-rw-r--r--src/mailman/interfaces/roster.py21
-rw-r--r--src/mailman/interfaces/workflow.py80
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.')