summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBarry Warsaw2015-04-15 16:24:15 -0400
committerBarry Warsaw2015-04-15 16:24:15 -0400
commit569553ab883624c9c0dc2217d4e16b6841fb0f23 (patch)
tree2c8a5208ed5bfb3b056b209add488c9192ee0af2
parent4b42d1c4cca07396585dbfd6265ecd751b419b06 (diff)
downloadmailman-569553ab883624c9c0dc2217d4e16b6841fb0f23.tar.gz
mailman-569553ab883624c9c0dc2217d4e16b6841fb0f23.tar.zst
mailman-569553ab883624c9c0dc2217d4e16b6841fb0f23.zip
-rw-r--r--src/mailman/app/registrar.py4
-rw-r--r--src/mailman/app/subscriptions.py83
-rw-r--r--src/mailman/app/tests/test_registrar.py72
-rw-r--r--src/mailman/app/tests/test_subscriptions.py41
-rw-r--r--src/mailman/interfaces/registrar.py8
-rw-r--r--src/mailman/interfaces/subscriptions.py52
6 files changed, 137 insertions, 123 deletions
diff --git a/src/mailman/app/registrar.py b/src/mailman/app/registrar.py
index ae4322d22..a3699de91 100644
--- a/src/mailman/app/registrar.py
+++ b/src/mailman/app/registrar.py
@@ -63,7 +63,7 @@ class Registrar:
pre_confirmed=pre_confirmed,
pre_approved=pre_approved)
list(workflow)
- return workflow.token
+ return workflow.token, workflow.token_owner
def confirm(self, token):
"""See `IRegistrar`."""
@@ -71,7 +71,7 @@ class Registrar:
workflow.token = token
workflow.restore()
list(workflow)
- return workflow.token
+ return workflow.token, workflow.token_owner
def discard(self, token):
"""See `IRegistrar`."""
diff --git a/src/mailman/app/subscriptions.py b/src/mailman/app/subscriptions.py
index 3138c513b..de68a2a46 100644
--- a/src/mailman/app/subscriptions.py
+++ b/src/mailman/app/subscriptions.py
@@ -31,9 +31,8 @@ import logging
from email.utils import formataddr
from enum import Enum
from datetime import timedelta
-from mailman.app.membership import add_member, delete_member
+from mailman.app.membership import delete_member
from mailman.app.workflow import Workflow
-from mailman.core.constants import system_preferences
from mailman.core.i18n import _
from mailman.database.transaction import dbconnection
from mailman.email.message import UserNotification
@@ -42,12 +41,10 @@ from mailman.interfaces.bans import IBanManager
from mailman.interfaces.listmanager import (
IListManager, ListDeletingEvent, NoSuchListError)
from mailman.interfaces.mailinglist import SubscriptionPolicy
-from mailman.interfaces.member import (
- DeliveryMode, MemberRole, MembershipIsBannedError)
+from mailman.interfaces.member import MembershipIsBannedError
from mailman.interfaces.pending import IPendable, IPendings
from mailman.interfaces.registrar import ConfirmationNeededEvent
-from mailman.interfaces.subscriptions import (
- ISubscriptionService, MissingUserError, RequestRecord)
+from mailman.interfaces.subscriptions import ISubscriptionService, TokenOwner
from mailman.interfaces.user import IUser
from mailman.interfaces.usermanager import IUserManager
from mailman.interfaces.workflow import IWorkflowStateManager
@@ -56,7 +53,6 @@ from mailman.utilities.datetime import now
from mailman.utilities.i18n import make
from operator import attrgetter
from sqlalchemy import and_, or_
-from uuid import UUID
from zope.component import getUtility
from zope.event import notify
from zope.interface import implementer
@@ -97,6 +93,7 @@ class SubscriptionWorkflow(Workflow):
'address_key',
'subscriber_key',
'user_key',
+ 'token_owner_key',
)
def __init__(self, mlist, subscriber=None, *,
@@ -106,6 +103,7 @@ class SubscriptionWorkflow(Workflow):
self.address = None
self.user = None
self.which = None
+ self._set_token(TokenOwner.no_one)
# The subscriber must be either an IUser or IAddress.
if IAddress.providedBy(subscriber):
self.address = subscriber
@@ -151,6 +149,29 @@ class SubscriptionWorkflow(Workflow):
def subscriber_key(self, key):
self.which = WhichSubscriber(key)
+ @property
+ def token_owner_key(self):
+ return self.token_owner.value
+
+ @token_owner_key.setter
+ def token_owner_key(self, value):
+ self.token_owner = TokenOwner(value)
+
+ def _set_token(self, token_owner):
+ assert isinstance(token_owner, TokenOwner)
+ # Create a new token to prevent replay attacks. It seems like this
+ # should produce the same token, but it won't because the pending adds
+ # a bit of randomization.
+ self.token_owner = token_owner
+ if token_owner is TokenOwner.no_one:
+ self.token = None
+ return
+ pendable = Pendable(
+ list_id=self.mlist.list_id,
+ address=self.address.email,
+ )
+ self.token = getUtility(IPendings).add(pendable, timedelta(days=3650))
+
def _step_sanity_checks(self):
# Ensure that we have both an address and a user, even if the address
# is not verified. We can't set the preferred address until it is
@@ -174,13 +195,7 @@ class SubscriptionWorkflow(Workflow):
# Is this email address banned?
if IBanManager(self.mlist).is_banned(self.address.email):
raise MembershipIsBannedError(self.mlist, self.address.email)
- # Create a pending record. This will give us the hash token we can use
- # to uniquely name this workflow.
- pendable = Pendable(
- list_id=self.mlist.list_id,
- address=self.address.email,
- )
- self.token = getUtility(IPendings).add(pendable, timedelta(days=3650))
+ # Start out with the subscriber being the token owner.
self.push('verification_checks')
def _step_verification_checks(self):
@@ -229,6 +244,7 @@ class SubscriptionWorkflow(Workflow):
# Here's the next step in the workflow, assuming the moderator
# approves of the subscription. If they don't, the workflow and
# subscription request will just be thrown away.
+ self._set_token(TokenOwner.moderator)
self.push('subscribe_from_restored')
self.save()
log.info('{}: held subscription request from {}'.format(
@@ -255,6 +271,8 @@ class SubscriptionWorkflow(Workflow):
raise StopIteration
def _step_subscribe_from_restored(self):
+ # Prevent replay attacks.
+ self._set_token(TokenOwner.no_one)
# Restore a little extra state that can't be stored in the database
# (because the order of setattr() on restore is indeterminate), then
# subscribe the user.
@@ -270,9 +288,9 @@ class SubscriptionWorkflow(Workflow):
self.mlist.subscribe(self.subscriber)
# This workflow is done so throw away any associated state.
getUtility(IWorkflowStateManager).restore(self.name, self.token)
- self.token = None
def _step_send_confirmation(self):
+ self._set_token(TokenOwner.subscriber)
self.push('do_confirm_verify')
self.save()
# Triggering this event causes the confirmation message to be sent.
@@ -290,14 +308,8 @@ class SubscriptionWorkflow(Workflow):
else:
assert self.which is WhichSubscriber.user
self.subscriber = self.user
- # Create a new token to prevent replay attacks. It seems like this
- # should produce the same token, but it won't because the pending adds
- # a bit of randomization.
- pendable = Pendable(
- list_id=self.mlist.list_id,
- address=self.address.email,
- )
- self.token = getUtility(IPendings).add(pendable, timedelta(days=3650))
+ # Reset the token so it can't be used in a replay attack.
+ self._set_token(TokenOwner.no_one)
# The user has confirmed their subscription request, and also verified
# their email address if necessary. This latter needs to be set on the
# IAddress, but there's nothing more to do about the confirmation step.
@@ -396,31 +408,6 @@ class SubscriptionService:
for member in self.get_members():
yield member
- def join(self, list_id, subscriber,
- display_name=None,
- delivery_mode=DeliveryMode.regular,
- role=MemberRole.member):
- """See `ISubscriptionService`."""
- mlist = getUtility(IListManager).get_by_list_id(list_id)
- if mlist is None:
- raise NoSuchListError(list_id)
- # Is the subscriber an email address or user id?
- if isinstance(subscriber, str):
- if display_name is None:
- display_name, at, domain = subscriber.partition('@')
- return add_member(
- mlist,
- RequestRecord(subscriber, display_name, delivery_mode,
- system_preferences.preferred_language),
- role)
- else:
- # We have to assume it's a UUID.
- assert isinstance(subscriber, UUID), 'Not a UUID'
- user = getUtility(IUserManager).get_user_by_id(subscriber)
- if user is None:
- raise MissingUserError(subscriber)
- return mlist.subscribe(user, role)
-
def leave(self, list_id, email):
"""See `ISubscriptionService`."""
mlist = getUtility(IListManager).get_by_list_id(list_id)
diff --git a/src/mailman/app/tests/test_registrar.py b/src/mailman/app/tests/test_registrar.py
index 4f5e1e3f9..1431b5e6f 100644
--- a/src/mailman/app/tests/test_registrar.py
+++ b/src/mailman/app/tests/test_registrar.py
@@ -28,6 +28,7 @@ from mailman.app.lifecycle import create_list
from mailman.interfaces.mailinglist import SubscriptionPolicy
from mailman.interfaces.pending import IPendings
from mailman.interfaces.registrar import IRegistrar
+from mailman.interfaces.subscriptions import TokenOwner
from mailman.interfaces.usermanager import IUserManager
from mailman.testing.layers import ConfigLayer
from mailman.utilities.datetime import now
@@ -49,35 +50,30 @@ class TestRegistrar(unittest.TestCase):
def test_unique_token(self):
# Registering a subscription request provides a unique token associated
- # with a pendable.
+ # with a pendable, and the owner of the token.
self.assertEqual(self._pendings.count, 0)
- token = self._registrar.register(self._anne)
+ token, token_owner = self._registrar.register(self._anne)
self.assertIsNotNone(token)
+ self.assertEqual(token_owner, TokenOwner.subscriber)
self.assertEqual(self._pendings.count, 1)
record = self._pendings.confirm(token, expunge=False)
self.assertEqual(record['list_id'], self._mlist.list_id)
self.assertEqual(record['address'], 'anne@example.com')
- def test_no_token(self):
+ def test_subscribe(self):
# Registering a subscription request where no confirmation or
- # moderation steps are needed, leaves us with no token, since there's
- # nothing more to do.
+ # moderation steps are needed, leaves us with no token or owner, since
+ # there's nothing more to do.
self._mlist.subscription_policy = SubscriptionPolicy.open
self._anne.verified_on = now()
- token = self._registrar.register(self._anne)
+ token, token_owner = self._registrar.register(self._anne)
self.assertIsNone(token)
- record = self._pendings.confirm(token, expunge=False)
- self.assertIsNone(record)
-
- def test_is_subscribed(self):
- # Where no confirmation or moderation steps are needed, registration
- # happens immediately.
- self._mlist.subscription_policy = SubscriptionPolicy.open
- self._anne.verified_on = now()
- status = self._registrar.register(self._anne)
- self.assertIsNone(status)
+ self.assertEqual(token_owner, TokenOwner.no_one)
member = self._mlist.regular_members.get_member('anne@example.com')
self.assertEqual(member.address, self._anne)
+ # There's nothing to confirm.
+ record = self._pendings.confirm(token, expunge=False)
+ self.assertIsNone(record)
def test_no_such_token(self):
# Given a token which is not in the database, a LookupError is raised.
@@ -90,12 +86,15 @@ class TestRegistrar(unittest.TestCase):
# to approve. Running the workflow gives us a token. Confirming the
# token subscribes the user.
self._mlist.subscription_policy = SubscriptionPolicy.open
- token = self._registrar.register(self._anne)
+ token, token_owner = self._registrar.register(self._anne)
self.assertIsNotNone(token)
+ self.assertEqual(token_owner, TokenOwner.subscriber)
member = self._mlist.regular_members.get_member('anne@example.com')
self.assertIsNone(member)
# Now confirm the subscription.
- self._registrar.confirm(token)
+ token, token_owner = self._registrar.confirm(token)
+ self.assertIsNone(token)
+ self.assertEqual(token_owner, TokenOwner.no_one)
member = self._mlist.regular_members.get_member('anne@example.com')
self.assertEqual(member.address, self._anne)
@@ -106,12 +105,15 @@ class TestRegistrar(unittest.TestCase):
# user.
self._mlist.subscription_policy = SubscriptionPolicy.confirm
self._anne.verified_on = now()
- token = self._registrar.register(self._anne)
+ token, token_owner = self._registrar.register(self._anne)
self.assertIsNotNone(token)
+ self.assertEqual(token_owner, TokenOwner.subscriber)
member = self._mlist.regular_members.get_member('anne@example.com')
self.assertIsNone(member)
# Now confirm the subscription.
- self._registrar.confirm(token)
+ token, token_owner = self._registrar.confirm(token)
+ self.assertIsNone(token)
+ self.assertEqual(token_owner, TokenOwner.no_one)
member = self._mlist.regular_members.get_member('anne@example.com')
self.assertEqual(member.address, self._anne)
@@ -121,12 +123,15 @@ class TestRegistrar(unittest.TestCase):
# token subscribes the user.
self._mlist.subscription_policy = SubscriptionPolicy.moderate
self._anne.verified_on = now()
- token = self._registrar.register(self._anne)
+ token, token_owner = self._registrar.register(self._anne)
self.assertIsNotNone(token)
+ self.assertEqual(token_owner, TokenOwner.moderator)
member = self._mlist.regular_members.get_member('anne@example.com')
self.assertIsNone(member)
# Now confirm the subscription.
- self._registrar.confirm(token)
+ token, token_owner = self._registrar.confirm(token)
+ self.assertIsNone(token)
+ self.assertEqual(token_owner, TokenOwner.no_one)
member = self._mlist.regular_members.get_member('anne@example.com')
self.assertEqual(member.address, self._anne)
@@ -140,21 +145,26 @@ class TestRegistrar(unittest.TestCase):
SubscriptionPolicy.confirm_then_moderate
self._anne.verified_on = now()
# Runs until subscription confirmation.
- token = self._registrar.register(self._anne)
+ token, token_owner = self._registrar.register(self._anne)
self.assertIsNotNone(token)
+ self.assertEqual(token_owner, TokenOwner.subscriber)
member = self._mlist.regular_members.get_member('anne@example.com')
self.assertIsNone(member)
# Now confirm the subscription, and wait for the moderator to approve
# the subscription. She is still not subscribed.
- new_token = self._registrar.confirm(token)
+ new_token, token_owner = self._registrar.confirm(token)
# The new token, used for the moderator to approve the message, is not
# the same as the old token.
self.assertNotEqual(new_token, token)
+ self.assertIsNotNone(new_token)
+ self.assertEqual(token_owner, TokenOwner.moderator)
member = self._mlist.regular_members.get_member('anne@example.com')
self.assertIsNone(member)
# Confirm once more, this time as the moderator approving the
# subscription. Now she's a member.
- self._registrar.confirm(new_token)
+ token, token_owner = self._registrar.confirm(new_token)
+ self.assertIsNone(token)
+ self.assertEqual(token_owner, TokenOwner.no_one)
member = self._mlist.regular_members.get_member('anne@example.com')
self.assertEqual(member.address, self._anne)
@@ -167,16 +177,18 @@ class TestRegistrar(unittest.TestCase):
SubscriptionPolicy.confirm_then_moderate
self._anne.verified_on = now()
# Runs until subscription confirmation.
- token = self._registrar.register(self._anne)
+ token, token_owner = self._registrar.register(self._anne)
self.assertIsNotNone(token)
+ self.assertEqual(token_owner, TokenOwner.subscriber)
member = self._mlist.regular_members.get_member('anne@example.com')
self.assertIsNone(member)
# Now confirm the subscription, and wait for the moderator to approve
# the subscription. She is still not subscribed.
- new_token = self._registrar.confirm(token)
+ new_token, token_owner = self._registrar.confirm(token)
# The status is not true because the user has not yet been subscribed
# to the mailing list.
self.assertIsNotNone(new_token)
+ self.assertEqual(token_owner, TokenOwner.moderator)
member = self._mlist.regular_members.get_member('anne@example.com')
self.assertIsNone(member)
# The new token is different than the old token.
@@ -185,9 +197,10 @@ class TestRegistrar(unittest.TestCase):
self.assertRaises(LookupError, self._registrar.confirm, token)
# Confirm once more, this time with the new token, as the moderator
# approving the subscription. Now she's a member.
- done_token = self._registrar.confirm(new_token)
+ done_token, token_owner = self._registrar.confirm(new_token)
# The token is None, signifying that the member has been subscribed.
self.assertIsNone(done_token)
+ self.assertEqual(token_owner, TokenOwner.no_one)
member = self._mlist.regular_members.get_member('anne@example.com')
self.assertEqual(member.address, self._anne)
@@ -197,8 +210,9 @@ class TestRegistrar(unittest.TestCase):
self._mlist.subscription_policy = SubscriptionPolicy.confirm
self._anne.verified_on = now()
# Runs until subscription confirmation.
- token = self._registrar.register(self._anne)
+ token, token_owner = self._registrar.register(self._anne)
self.assertIsNotNone(token)
+ self.assertEqual(token_owner, TokenOwner.subscriber)
member = self._mlist.regular_members.get_member('anne@example.com')
self.assertIsNone(member)
# Now discard the subscription request.
diff --git a/src/mailman/app/tests/test_subscriptions.py b/src/mailman/app/tests/test_subscriptions.py
index a4971d793..77f308b9c 100644
--- a/src/mailman/app/tests/test_subscriptions.py
+++ b/src/mailman/app/tests/test_subscriptions.py
@@ -34,7 +34,7 @@ from mailman.interfaces.member import (
MemberRole, MembershipIsBannedError, MissingPreferredAddressError)
from mailman.interfaces.pending import IPendings
from mailman.interfaces.subscriptions import (
- MissingUserError, ISubscriptionService)
+ ISubscriptionService, MissingUserError, TokenOwner)
from mailman.testing.helpers import LogFileMark, get_queue_messages
from mailman.testing.layers import ConfigLayer
from mailman.interfaces.mailinglist import SubscriptionPolicy
@@ -45,6 +45,7 @@ from zope.component import getUtility
+@unittest.skip('XXX -- no more .join()')
class TestJoin(unittest.TestCase):
layer = ConfigLayer
@@ -88,6 +89,12 @@ class TestSubscriptionWorkflow(unittest.TestCase):
self._anne = 'anne@example.com'
self._user_manager = getUtility(IUserManager)
+ def test_no_token_to_start_with(self):
+ # The workflow starts with no tokens.
+ workflow = SubscriptionWorkflow(self._mlist)
+ self.assertIsNone(workflow.token)
+ self.assertEqual(workflow.token_owner, TokenOwner.no_one)
+
def test_user_or_address_required(self):
# The `subscriber` attribute must be a user or address.
workflow = SubscriptionWorkflow(self._mlist)
@@ -311,6 +318,9 @@ class TestSubscriptionWorkflow(unittest.TestCase):
# Anne is now a member of the mailing list.
member = self._mlist.regular_members.get_member(self._anne)
self.assertEqual(member.address, anne)
+ # No further token is needed.
+ self.assertIsNone(workflow.token)
+ self.assertEqual(workflow.token_owner, TokenOwner.no_one)
def test_do_subscription_pre_approved(self):
# An moderation-requiring subscription policy plus a pre-verified and
@@ -326,6 +336,9 @@ class TestSubscriptionWorkflow(unittest.TestCase):
# Anne is now a member of the mailing list.
member = self._mlist.regular_members.get_member(self._anne)
self.assertEqual(member.address, anne)
+ # No further token is needed.
+ self.assertIsNone(workflow.token)
+ self.assertEqual(workflow.token_owner, TokenOwner.no_one)
def test_do_subscription_pre_approved_pre_confirmed(self):
# An moderation-requiring subscription policy plus a pre-verified and
@@ -343,6 +356,9 @@ class TestSubscriptionWorkflow(unittest.TestCase):
# Anne is now a member of the mailing list.
member = self._mlist.regular_members.get_member(self._anne)
self.assertEqual(member.address, anne)
+ # No further token is needed.
+ self.assertIsNone(workflow.token)
+ self.assertEqual(workflow.token_owner, TokenOwner.no_one)
def test_do_subscription_cleanups(self):
# Once the user is subscribed, the token, and its associated pending
@@ -362,6 +378,7 @@ class TestSubscriptionWorkflow(unittest.TestCase):
self.assertEqual(member.address, anne)
# The workflow is done, so it has no token.
self.assertIsNone(workflow.token)
+ self.assertEqual(workflow.token_owner, TokenOwner.no_one)
# The pendable associated with the token has been evicted.
self.assertIsNone(getUtility(IPendings).confirm(token, expunge=False))
# There is no saved workflow associated with the token. This shows up
@@ -384,6 +401,9 @@ class TestSubscriptionWorkflow(unittest.TestCase):
# The user is not currently subscribed to the mailing list.
member = self._mlist.regular_members.get_member(self._anne)
self.assertIsNone(member)
+ # The token is owned by the moderator.
+ self.assertIsNotNone(workflow.token)
+ self.assertEqual(workflow.token_owner, TokenOwner.moderator)
# Create a new workflow with the previous workflow's save token, and
# restore its state. This models an approved subscription and should
# result in the user getting subscribed.
@@ -394,6 +414,9 @@ class TestSubscriptionWorkflow(unittest.TestCase):
# Now the user is subscribed to the mailing list.
member = self._mlist.regular_members.get_member(self._anne)
self.assertEqual(member.address, anne)
+ # No further token is needed.
+ self.assertIsNone(approved_workflow.token)
+ self.assertEqual(approved_workflow.token_owner, TokenOwner.no_one)
def test_get_moderator_approval_log_on_hold(self):
# When the subscription is held for moderator approval, a message is
@@ -530,6 +553,10 @@ approval:
workflow = SubscriptionWorkflow(self._mlist, anne)
list(workflow)
self.assertIsNone(self._mlist.regular_members.get_member(self._anne))
+ # The token is owned by the subscriber.
+ self.assertIsNotNone(workflow.token)
+ self.assertEqual(workflow.token_owner, TokenOwner.subscriber)
+ # Confirm.
confirm_workflow = SubscriptionWorkflow(self._mlist)
confirm_workflow.token = workflow.token
confirm_workflow.restore()
@@ -537,6 +564,9 @@ approval:
self.assertIsNotNone(anne.verified_on)
self.assertEqual(
self._mlist.regular_members.get_member(self._anne).address, anne)
+ # No further token is needed.
+ self.assertIsNone(confirm_workflow.token)
+ self.assertEqual(confirm_workflow.token_owner, TokenOwner.no_one)
def test_prevent_confirmation_replay_attacks(self):
# Ensure that if the workflow requires two confirmations, e.g. first
@@ -553,11 +583,17 @@ approval:
# Anne is not yet a member of the mailing list.
member = self._mlist.regular_members.get_member(self._anne)
self.assertIsNone(member)
+ # The token is owned by the subscriber.
+ self.assertIsNotNone(workflow.token)
+ self.assertEqual(workflow.token_owner, TokenOwner.subscriber)
# The old token will not work for moderator approval.
moderator_workflow = SubscriptionWorkflow(self._mlist)
moderator_workflow.token = token
moderator_workflow.restore()
list(moderator_workflow)
+ # The token is owned by the moderator.
+ self.assertIsNotNone(moderator_workflow.token)
+ self.assertEqual(moderator_workflow.token_owner, TokenOwner.moderator)
# While we wait for the moderator to approve the subscription, note
# that there's a new token for the next steps.
self.assertNotEqual(token, moderator_workflow.token)
@@ -578,3 +614,6 @@ approval:
# And now Anne is a member.
member = self._mlist.regular_members.get_member(self._anne)
self.assertEqual(member.address.email, self._anne)
+ # No further token is needed.
+ self.assertIsNone(final_workflow.token)
+ self.assertEqual(final_workflow.token_owner, TokenOwner.no_one)
diff --git a/src/mailman/interfaces/registrar.py b/src/mailman/interfaces/registrar.py
index ff3f26898..e19a3137c 100644
--- a/src/mailman/interfaces/registrar.py
+++ b/src/mailman/interfaces/registrar.py
@@ -75,9 +75,11 @@ class IRegistrar(Interface):
:type mlist: `IMailingList`
: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
+ :return: None if the workflow completes with the member being
+ subscribed. If the workflow is paused for user confirmation or
+ moderator approval, a 2-tuple is returned where the first element
+ is a ``TokenOwner`` and the second element is the token hash.
+ :rtype: None or 2-tuple of (TokenOwner, str)
:raises MembershipIsBannedError: when the address being subscribed
appears in the global or list-centric bans.
"""
diff --git a/src/mailman/interfaces/subscriptions.py b/src/mailman/interfaces/subscriptions.py
index 677f591ef..e6ffd29ce 100644
--- a/src/mailman/interfaces/subscriptions.py
+++ b/src/mailman/interfaces/subscriptions.py
@@ -19,14 +19,16 @@
__all__ = [
'ISubscriptionService',
+ 'MissingUserError',
'RequestRecord',
+ 'TokenOwner',
]
from collections import namedtuple
-
+from enum import Enum
from mailman.interfaces.errors import MailmanError
-from mailman.interfaces.member import DeliveryMode, MemberRole
+from mailman.interfaces.member import DeliveryMode
from zope.interface import Interface
@@ -56,6 +58,14 @@ def RequestRecord(email, display_name='',
+class TokenOwner(Enum):
+ """Who 'owns' the token returned from the registrar?"""
+ no_one = 0
+ subscriber = 1
+ moderator = 2
+
+
+
class ISubscriptionService(Interface):
"""General Subscription services."""
@@ -104,44 +114,6 @@ class ISubscriptionService(Interface):
def __iter__():
"""See `get_members()`."""
- def join(list_id, subscriber, display_name=None,
- delivery_mode=DeliveryMode.regular,
- role=MemberRole.member):
- """Subscribe to a mailing list.
-
- A user for the address is created if it is not yet known to Mailman,
- however newly registered addresses will not yet be validated. No
- confirmation message will be sent to the address, and the approval of
- the subscription request is still dependent on the policy of the
- mailing list.
-
- :param list_id: The list id of the mailing list the user is
- subscribing to.
- :type list_id: string
- :param subscriber: The email address or user id of the user getting
- subscribed.
- :type subscriber: string or int
- :param display_name: The name of the user. This is only used if a new
- user is created, and it defaults to the local part of the email
- address if not given.
- :type display_name: string
- :param delivery_mode: The delivery mode for this subscription. This
- can be one of the enum values of `DeliveryMode`. If not given,
- regular delivery is assumed.
- :type delivery_mode: string
- :param role: The membership role for this subscription.
- :type role: `MemberRole`
- :return: The just created member.
- :rtype: `IMember`
- :raises AlreadySubscribedError: if the user is already subscribed to
- the mailing list.
- :raises InvalidEmailAddressError: if the email address is not valid.
- :raises MembershipIsBannedError: if the membership is not allowed.
- :raises MissingUserError: when a bogus user id is given.
- :raises NoSuchListError: if the named mailing list does not exist.
- :raises ValueError: when `delivery_mode` is invalid.
- """
-
def leave(list_id, email):
"""Unsubscribe from a mailing list.