summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/mailman/app/subscriptions.py56
-rw-r--r--src/mailman/app/tests/test_subscriptions.py60
-rw-r--r--src/mailman/templates/en/subauth.txt6
3 files changed, 108 insertions, 14 deletions
diff --git a/src/mailman/app/subscriptions.py b/src/mailman/app/subscriptions.py
index 22f4bdf56..982c04547 100644
--- a/src/mailman/app/subscriptions.py
+++ b/src/mailman/app/subscriptions.py
@@ -26,24 +26,30 @@ __all__ = [
import uuid
+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.moderator import hold_subscription
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
from mailman.interfaces.address import IAddress
from mailman.interfaces.listmanager import (
IListManager, ListDeletingEvent, NoSuchListError)
from mailman.interfaces.mailinglist import SubscriptionPolicy
from mailman.interfaces.member import DeliveryMode, MemberRole
+from mailman.interfaces.pending import IPendable, IPendings
from mailman.interfaces.subscriptions import (
ISubscriptionService, MissingUserError, RequestRecord)
from mailman.interfaces.user import IUser
from mailman.interfaces.usermanager import IUserManager
from mailman.model.member import Member
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
@@ -51,6 +57,9 @@ from zope.component import getUtility
from zope.interface import implementer
+log = logging.getLogger('mailman.subscribe')
+
+
def _membership_sort_key(member):
"""Sort function for find_members().
@@ -66,6 +75,11 @@ class WhichSubscriber(Enum):
user = 2
+@implementer(IPendable)
+class Pendable(dict):
+ pass
+
+
class SubscriptionWorkflow(Workflow):
"""Workflow of a subscription request."""
@@ -201,18 +215,44 @@ class SubscriptionWorkflow(Workflow):
self.mlist.subscribe(self.subscriber)
def _step_get_moderator_approval(self):
- # In order to get the moderator's approval, we need to hold the
- # subscription request in the database
- request = RequestRecord(
- self.address.email, self.subscriber.display_name,
- # XXX Need to get these last to into the constructor.
- DeliveryMode.regular, 'en')
- self.token = hold_subscription(self.mlist, request)
+ # Getting the moderator's approval requires several steps. We'll need
+ # to suspend this workflow for an indeterminate amount of time while
+ # we wait for that approval. We need a unique token for this
+ # suspended workflow, so we'll create a minimal pending record. We
+ # also might need to send an email notification to the list
+ # moderators.
+ #
+ # Start by creating the pending record. This will give us a hash
+ # token we can use to uniquely name this workflow. It only needs to
+ # contain the current date, which we'll use to expire requests from
+ # the database, say if the moderator never approves the request.
+ pendable = Pendable(when=now().isoformat())
+ self.token = getUtility(IPendings).add(pendable, timedelta(days=3650))
# 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.push('subscribe_from_restored')
self.save()
+ log.info('{}: held subscription request from {}'.format(
+ self.mlist.fqdn_listname, self.address.email))
+ # Possibly send a notification to the list moderators.
+ if self.mlist.admin_immed_notify:
+ subject = _(
+ 'New subscription request to $self.mlist.display_name '
+ 'from $self.address.email')
+ username = formataddr(
+ (self.subscriber.display_name, self.address.email))
+ text = make('subauth.txt',
+ mailing_list=self.mlist,
+ username=username,
+ listname=self.mlist.fqdn_listname,
+ )
+ # This message should appear to come from the <list>-owner so as
+ # to avoid any useless bounce processing.
+ msg = UserNotification(
+ self.mlist.owner_address, self.mlist.owner_address,
+ subject, text, self.mlist.preferred_language)
+ msg.send(self.mlist, tomoderators=True)
# The workflow must stop running here.
raise StopIteration
diff --git a/src/mailman/app/tests/test_subscriptions.py b/src/mailman/app/tests/test_subscriptions.py
index 61d341d6d..45e17a9e5 100644
--- a/src/mailman/app/tests/test_subscriptions.py
+++ b/src/mailman/app/tests/test_subscriptions.py
@@ -33,6 +33,7 @@ from mailman.interfaces.member import MemberRole, MissingPreferredAddressError
from mailman.interfaces.requests import IListRequests, RequestType
from mailman.interfaces.subscriptions import (
MissingUserError, ISubscriptionService)
+from mailman.testing.helpers import LogFileMark, get_queue_messages
from mailman.testing.layers import ConfigLayer
from mailman.interfaces.mailinglist import SubscriptionPolicy
from mailman.interfaces.usermanager import IUserManager
@@ -77,9 +78,11 @@ class TestJoin(unittest.TestCase):
class TestSubscriptionWorkflow(unittest.TestCase):
layer = ConfigLayer
+ maxDiff = None
def setUp(self):
self._mlist = create_list('test@example.com')
+ self._mlist.admin_immed_notify = False
self._anne = 'anne@example.com'
self._user_manager = getUtility(IUserManager)
@@ -350,6 +353,63 @@ class TestSubscriptionWorkflow(unittest.TestCase):
member = self._mlist.regular_members.get_member(self._anne)
self.assertEqual(member.address, anne)
+ def test_get_moderator_approval_log_on_hold(self):
+ # When the subscription is held for moderator approval, a message is
+ # logged.
+ mark = LogFileMark('mailman.subscribe')
+ self._mlist.subscription_policy = SubscriptionPolicy.moderate
+ anne = self._user_manager.create_address(self._anne)
+ workflow = SubscriptionWorkflow(self._mlist, anne,
+ pre_verified=True,
+ pre_confirmed=True)
+ # Consume the entire state machine.
+ list(workflow)
+ line = mark.readline()
+ self.assertEqual(
+ line[29:-1],
+ 'test@example.com: held subscription request from anne@example.com'
+ )
+
+ def test_get_moderator_approval_notifies_moderators(self):
+ # When the subscription is held for moderator approval, and the list
+ # is so configured, a notification is sent to the list moderators.
+ self._mlist.admin_immed_notify = True
+ self._mlist.subscription_policy = SubscriptionPolicy.moderate
+ anne = self._user_manager.create_address(self._anne)
+ workflow = SubscriptionWorkflow(self._mlist, anne,
+ pre_verified=True,
+ pre_confirmed=True)
+ # Consume the entire state machine.
+ list(workflow)
+ items = get_queue_messages('virgin')
+ self.assertEqual(len(items), 1)
+ message = items[0].msg
+ self.assertEqual(message['From'], 'test-owner@example.com')
+ self.assertEqual(message['To'], 'test-owner@example.com')
+ self.assertEqual(
+ message['Subject'],
+ 'New subscription request to Test from anne@example.com')
+ self.assertEqual(message.get_payload(), """\
+Your authorization is required for a mailing list subscription request
+approval:
+
+ For: anne@example.com
+ List: test@example.com""")
+
+ def test_get_moderator_approval_no_notifications(self):
+ # When the subscription is held for moderator approval, and the list
+ # is so configured, a notification is sent to the list moderators.
+ self._mlist.admin_immed_notify = False
+ self._mlist.subscription_policy = SubscriptionPolicy.moderate
+ anne = self._user_manager.create_address(self._anne)
+ workflow = SubscriptionWorkflow(self._mlist, anne,
+ pre_verified=True,
+ pre_confirmed=True)
+ # Consume the entire state machine.
+ list(workflow)
+ items = get_queue_messages('virgin')
+ self.assertEqual(len(items), 0)
+
# XXX
@unittest.expectedFailure
diff --git a/src/mailman/templates/en/subauth.txt b/src/mailman/templates/en/subauth.txt
index 1b13ebaeb..041be5e55 100644
--- a/src/mailman/templates/en/subauth.txt
+++ b/src/mailman/templates/en/subauth.txt
@@ -3,9 +3,3 @@ approval:
For: $username
List: $listname
-
-At your convenience, visit:
-
- $admindb_url
-
-to process the request.