Import order flake8 plugin.
[mailman.git] / src / mailman / app / tests / test_subscriptions.py
blob1139591de7af6afc6d571e975be10961d15c209b
1 # Copyright (C) 2011-2016 by the Free Software Foundation, Inc.
3 # This file is part of GNU Mailman.
5 # GNU Mailman is free software: you can redistribute it and/or modify it under
6 # the terms of the GNU General Public License as published by the Free
7 # Software Foundation, either version 3 of the License, or (at your option)
8 # any later version.
10 # GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 # more details.
15 # You should have received a copy of the GNU General Public License along with
16 # GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
18 """Tests for the subscription service."""
20 import unittest
22 from mailman.app.lifecycle import create_list
23 from mailman.app.subscriptions import SubscriptionWorkflow
24 from mailman.interfaces.bans import IBanManager
25 from mailman.interfaces.mailinglist import SubscriptionPolicy
26 from mailman.interfaces.member import MembershipIsBannedError
27 from mailman.interfaces.pending import IPendings
28 from mailman.interfaces.subscriptions import TokenOwner
29 from mailman.interfaces.usermanager import IUserManager
30 from mailman.testing.helpers import (
31 LogFileMark, get_queue_messages, set_preferred)
32 from mailman.testing.layers import ConfigLayer
33 from mailman.utilities.datetime import now
34 from unittest.mock import patch
35 from zope.component import getUtility
38 class TestSubscriptionWorkflow(unittest.TestCase):
39 layer = ConfigLayer
40 maxDiff = None
42 def setUp(self):
43 self._mlist = create_list('test@example.com')
44 self._mlist.admin_immed_notify = False
45 self._anne = 'anne@example.com'
46 self._user_manager = getUtility(IUserManager)
48 def test_start_state(self):
49 # The workflow starts with no tokens or member.
50 workflow = SubscriptionWorkflow(self._mlist)
51 self.assertIsNone(workflow.token)
52 self.assertEqual(workflow.token_owner, TokenOwner.no_one)
53 self.assertIsNone(workflow.member)
55 def test_pended_data(self):
56 # There is a Pendable associated with the held request, and it has
57 # some data associated with it.
58 anne = self._user_manager.create_address(self._anne)
59 workflow = SubscriptionWorkflow(self._mlist, anne)
60 try:
61 workflow.run_thru('send_confirmation')
62 except StopIteration:
63 pass
64 self.assertIsNotNone(workflow.token)
65 pendable = getUtility(IPendings).confirm(workflow.token, expunge=False)
66 self.assertEqual(pendable['list_id'], 'test.example.com')
67 self.assertEqual(pendable['email'], 'anne@example.com')
68 self.assertEqual(pendable['display_name'], '')
69 self.assertEqual(pendable['when'], '2005-08-01T07:49:23')
70 self.assertEqual(pendable['token_owner'], 'subscriber')
72 def test_user_or_address_required(self):
73 # The `subscriber` attribute must be a user or address.
74 workflow = SubscriptionWorkflow(self._mlist)
75 self.assertRaises(AssertionError, list, workflow)
77 def test_sanity_checks_address(self):
78 # Ensure that the sanity check phase, when given an IAddress, ends up
79 # with a linked user.
80 anne = self._user_manager.create_address(self._anne)
81 workflow = SubscriptionWorkflow(self._mlist, anne)
82 self.assertIsNotNone(workflow.address)
83 self.assertIsNone(workflow.user)
84 workflow.run_thru('sanity_checks')
85 self.assertIsNotNone(workflow.address)
86 self.assertIsNotNone(workflow.user)
87 self.assertEqual(list(workflow.user.addresses)[0].email, self._anne)
89 def test_sanity_checks_user_with_preferred_address(self):
90 # Ensure that the sanity check phase, when given an IUser with a
91 # preferred address, ends up with an address.
92 anne = self._user_manager.make_user(self._anne)
93 address = set_preferred(anne)
94 workflow = SubscriptionWorkflow(self._mlist, anne)
95 # The constructor sets workflow.address because the user has a
96 # preferred address.
97 self.assertEqual(workflow.address, address)
98 self.assertEqual(workflow.user, anne)
99 workflow.run_thru('sanity_checks')
100 self.assertEqual(workflow.address, address)
101 self.assertEqual(workflow.user, anne)
103 def test_sanity_checks_user_without_preferred_address(self):
104 # Ensure that the sanity check phase, when given a user without a
105 # preferred address, but with at least one linked address, gets an
106 # address.
107 anne = self._user_manager.make_user(self._anne)
108 workflow = SubscriptionWorkflow(self._mlist, anne)
109 self.assertIsNone(workflow.address)
110 self.assertEqual(workflow.user, anne)
111 workflow.run_thru('sanity_checks')
112 self.assertIsNotNone(workflow.address)
113 self.assertEqual(workflow.user, anne)
115 def test_sanity_checks_user_with_multiple_linked_addresses(self):
116 # Ensure that the santiy check phase, when given a user without a
117 # preferred address, but with multiple linked addresses, gets of of
118 # those addresses (exactly which one is undefined).
119 anne = self._user_manager.make_user(self._anne)
120 anne.link(self._user_manager.create_address('anne@example.net'))
121 anne.link(self._user_manager.create_address('anne@example.org'))
122 workflow = SubscriptionWorkflow(self._mlist, anne)
123 self.assertIsNone(workflow.address)
124 self.assertEqual(workflow.user, anne)
125 workflow.run_thru('sanity_checks')
126 self.assertIn(workflow.address.email, ['anne@example.com',
127 'anne@example.net',
128 'anne@example.org'])
129 self.assertEqual(workflow.user, anne)
131 def test_sanity_checks_user_without_addresses(self):
132 # It is an error to try to subscribe a user with no linked addresses.
133 user = self._user_manager.create_user()
134 workflow = SubscriptionWorkflow(self._mlist, user)
135 self.assertRaises(AssertionError, workflow.run_thru, 'sanity_checks')
137 def test_sanity_checks_globally_banned_address(self):
138 # An exception is raised if the address is globally banned.
139 anne = self._user_manager.create_address(self._anne)
140 IBanManager(None).ban(self._anne)
141 workflow = SubscriptionWorkflow(self._mlist, anne)
142 self.assertRaises(MembershipIsBannedError, list, workflow)
144 def test_sanity_checks_banned_address(self):
145 # An exception is raised if the address is banned by the mailing list.
146 anne = self._user_manager.create_address(self._anne)
147 IBanManager(self._mlist).ban(self._anne)
148 workflow = SubscriptionWorkflow(self._mlist, anne)
149 self.assertRaises(MembershipIsBannedError, list, workflow)
151 def test_verification_checks_with_verified_address(self):
152 # When the address is already verified, we skip straight to the
153 # confirmation checks.
154 anne = self._user_manager.create_address(self._anne)
155 anne.verified_on = now()
156 workflow = SubscriptionWorkflow(self._mlist, anne)
157 workflow.run_thru('verification_checks')
158 with patch.object(workflow, '_step_confirmation_checks') as step:
159 next(workflow)
160 step.assert_called_once_with()
162 def test_verification_checks_with_pre_verified_address(self):
163 # When the address is not yet verified, but the pre-verified flag is
164 # passed to the workflow, we skip to the confirmation checks.
165 anne = self._user_manager.create_address(self._anne)
166 workflow = SubscriptionWorkflow(self._mlist, anne, pre_verified=True)
167 workflow.run_thru('verification_checks')
168 with patch.object(workflow, '_step_confirmation_checks') as step:
169 next(workflow)
170 step.assert_called_once_with()
171 # And now the address is verified.
172 self.assertIsNotNone(anne.verified_on)
174 def test_verification_checks_confirmation_needed(self):
175 # The address is neither verified, nor is the pre-verified flag set.
176 # A confirmation message must be sent to the user which will also
177 # verify their address.
178 anne = self._user_manager.create_address(self._anne)
179 workflow = SubscriptionWorkflow(self._mlist, anne)
180 workflow.run_thru('verification_checks')
181 with patch.object(workflow, '_step_send_confirmation') as step:
182 next(workflow)
183 step.assert_called_once_with()
184 # The address still hasn't been verified.
185 self.assertIsNone(anne.verified_on)
187 def test_confirmation_checks_open_list(self):
188 # A subscription to an open list does not need to be confirmed or
189 # moderated.
190 self._mlist.subscription_policy = SubscriptionPolicy.open
191 anne = self._user_manager.create_address(self._anne)
192 workflow = SubscriptionWorkflow(self._mlist, anne, pre_verified=True)
193 workflow.run_thru('confirmation_checks')
194 with patch.object(workflow, '_step_do_subscription') as step:
195 next(workflow)
196 step.assert_called_once_with()
198 def test_confirmation_checks_no_user_confirmation_needed(self):
199 # A subscription to a list which does not need user confirmation skips
200 # to the moderation checks.
201 self._mlist.subscription_policy = SubscriptionPolicy.moderate
202 anne = self._user_manager.create_address(self._anne)
203 workflow = SubscriptionWorkflow(self._mlist, anne, pre_verified=True)
204 workflow.run_thru('confirmation_checks')
205 with patch.object(workflow, '_step_moderation_checks') as step:
206 next(workflow)
207 step.assert_called_once_with()
209 def test_confirmation_checks_confirm_pre_confirmed(self):
210 # The subscription policy requires user confirmation, but their
211 # subscription is pre-confirmed. Since moderation is not required,
212 # the user will be immediately subscribed.
213 self._mlist.subscription_policy = SubscriptionPolicy.confirm
214 anne = self._user_manager.create_address(self._anne)
215 workflow = SubscriptionWorkflow(self._mlist, anne,
216 pre_verified=True,
217 pre_confirmed=True)
218 workflow.run_thru('confirmation_checks')
219 with patch.object(workflow, '_step_do_subscription') as step:
220 next(workflow)
221 step.assert_called_once_with()
223 def test_confirmation_checks_confirm_then_moderate_pre_confirmed(self):
224 # The subscription policy requires user confirmation, but their
225 # subscription is pre-confirmed. Since moderation is required, that
226 # check will be performed.
227 self._mlist.subscription_policy = (
228 SubscriptionPolicy.confirm_then_moderate)
229 anne = self._user_manager.create_address(self._anne)
230 workflow = SubscriptionWorkflow(self._mlist, anne,
231 pre_verified=True,
232 pre_confirmed=True)
233 workflow.run_thru('confirmation_checks')
234 with patch.object(workflow, '_step_moderation_checks') as step:
235 next(workflow)
236 step.assert_called_once_with()
238 def test_confirmation_checks_confirm_and_moderate_pre_confirmed(self):
239 # The subscription policy requires user confirmation and moderation,
240 # but their subscription is pre-confirmed.
241 self._mlist.subscription_policy = (
242 SubscriptionPolicy.confirm_then_moderate)
243 anne = self._user_manager.create_address(self._anne)
244 workflow = SubscriptionWorkflow(self._mlist, anne,
245 pre_verified=True,
246 pre_confirmed=True)
247 workflow.run_thru('confirmation_checks')
248 with patch.object(workflow, '_step_moderation_checks') as step:
249 next(workflow)
250 step.assert_called_once_with()
252 def test_confirmation_checks_confirmation_needed(self):
253 # The subscription policy requires confirmation and the subscription
254 # is not pre-confirmed.
255 self._mlist.subscription_policy = SubscriptionPolicy.confirm
256 anne = self._user_manager.create_address(self._anne)
257 workflow = SubscriptionWorkflow(self._mlist, anne, pre_verified=True)
258 workflow.run_thru('confirmation_checks')
259 with patch.object(workflow, '_step_send_confirmation') as step:
260 next(workflow)
261 step.assert_called_once_with()
263 def test_confirmation_checks_moderate_confirmation_needed(self):
264 # The subscription policy requires confirmation and moderation, and the
265 # subscription is not pre-confirmed.
266 self._mlist.subscription_policy = (
267 SubscriptionPolicy.confirm_then_moderate)
268 anne = self._user_manager.create_address(self._anne)
269 workflow = SubscriptionWorkflow(self._mlist, anne, pre_verified=True)
270 workflow.run_thru('confirmation_checks')
271 with patch.object(workflow, '_step_send_confirmation') as step:
272 next(workflow)
273 step.assert_called_once_with()
275 def test_moderation_checks_pre_approved(self):
276 # The subscription is pre-approved by the moderator.
277 self._mlist.subscription_policy = SubscriptionPolicy.moderate
278 anne = self._user_manager.create_address(self._anne)
279 workflow = SubscriptionWorkflow(self._mlist, anne,
280 pre_verified=True,
281 pre_approved=True)
282 workflow.run_thru('moderation_checks')
283 with patch.object(workflow, '_step_do_subscription') as step:
284 next(workflow)
285 step.assert_called_once_with()
287 def test_moderation_checks_approval_required(self):
288 # The moderator must approve the subscription.
289 self._mlist.subscription_policy = SubscriptionPolicy.moderate
290 anne = self._user_manager.create_address(self._anne)
291 workflow = SubscriptionWorkflow(self._mlist, anne, pre_verified=True)
292 workflow.run_thru('moderation_checks')
293 with patch.object(workflow, '_step_get_moderator_approval') as step:
294 next(workflow)
295 step.assert_called_once_with()
297 def test_do_subscription(self):
298 # An open subscription policy plus a pre-verified address means the
299 # user gets subscribed to the mailing list without any further
300 # confirmations or approvals.
301 self._mlist.subscription_policy = SubscriptionPolicy.open
302 anne = self._user_manager.create_address(self._anne)
303 workflow = SubscriptionWorkflow(self._mlist, anne, pre_verified=True)
304 # Consume the entire state machine.
305 list(workflow)
306 # Anne is now a member of the mailing list.
307 member = self._mlist.regular_members.get_member(self._anne)
308 self.assertEqual(member.address, anne)
309 self.assertEqual(workflow.member, member)
310 # No further token is needed.
311 self.assertIsNone(workflow.token)
312 self.assertEqual(workflow.token_owner, TokenOwner.no_one)
314 def test_do_subscription_pre_approved(self):
315 # An moderation-requiring subscription policy plus a pre-verified and
316 # pre-approved address means the user gets subscribed to the mailing
317 # list without any further confirmations or approvals.
318 self._mlist.subscription_policy = SubscriptionPolicy.moderate
319 anne = self._user_manager.create_address(self._anne)
320 workflow = SubscriptionWorkflow(self._mlist, anne,
321 pre_verified=True,
322 pre_approved=True)
323 # Consume the entire state machine.
324 list(workflow)
325 # Anne is now a member of the mailing list.
326 member = self._mlist.regular_members.get_member(self._anne)
327 self.assertEqual(member.address, anne)
328 self.assertEqual(workflow.member, member)
329 # No further token is needed.
330 self.assertIsNone(workflow.token)
331 self.assertEqual(workflow.token_owner, TokenOwner.no_one)
333 def test_do_subscription_pre_approved_pre_confirmed(self):
334 # An moderation-requiring subscription policy plus a pre-verified and
335 # pre-approved address means the user gets subscribed to the mailing
336 # list without any further confirmations or approvals.
337 self._mlist.subscription_policy = (
338 SubscriptionPolicy.confirm_then_moderate)
339 anne = self._user_manager.create_address(self._anne)
340 workflow = SubscriptionWorkflow(self._mlist, anne,
341 pre_verified=True,
342 pre_confirmed=True,
343 pre_approved=True)
344 # Consume the entire state machine.
345 list(workflow)
346 # Anne is now a member of the mailing list.
347 member = self._mlist.regular_members.get_member(self._anne)
348 self.assertEqual(member.address, anne)
349 self.assertEqual(workflow.member, member)
350 # No further token is needed.
351 self.assertIsNone(workflow.token)
352 self.assertEqual(workflow.token_owner, TokenOwner.no_one)
354 def test_do_subscription_cleanups(self):
355 # Once the user is subscribed, the token, and its associated pending
356 # database record will be removed from the database.
357 self._mlist.subscription_policy = SubscriptionPolicy.open
358 anne = self._user_manager.create_address(self._anne)
359 workflow = SubscriptionWorkflow(self._mlist, anne,
360 pre_verified=True,
361 pre_confirmed=True,
362 pre_approved=True)
363 # Cache the token.
364 token = workflow.token
365 # Consume the entire state machine.
366 list(workflow)
367 # Anne is now a member of the mailing list.
368 member = self._mlist.regular_members.get_member(self._anne)
369 self.assertEqual(member.address, anne)
370 self.assertEqual(workflow.member, member)
371 # The workflow is done, so it has no token.
372 self.assertIsNone(workflow.token)
373 self.assertEqual(workflow.token_owner, TokenOwner.no_one)
374 # The pendable associated with the token has been evicted.
375 self.assertIsNone(getUtility(IPendings).confirm(token, expunge=False))
376 # There is no saved workflow associated with the token. This shows up
377 # as an exception when we try to restore the workflow.
378 new_workflow = SubscriptionWorkflow(self._mlist)
379 new_workflow.token = token
380 self.assertRaises(LookupError, new_workflow.restore)
382 def test_moderator_approves(self):
383 # The workflow runs until moderator approval is required, at which
384 # point the workflow is saved. Once the moderator approves, the
385 # workflow resumes and the user is subscribed.
386 self._mlist.subscription_policy = SubscriptionPolicy.moderate
387 anne = self._user_manager.create_address(self._anne)
388 workflow = SubscriptionWorkflow(self._mlist, anne,
389 pre_verified=True,
390 pre_confirmed=True)
391 # Consume the entire state machine.
392 list(workflow)
393 # The user is not currently subscribed to the mailing list.
394 member = self._mlist.regular_members.get_member(self._anne)
395 self.assertIsNone(member)
396 self.assertIsNone(workflow.member)
397 # The token is owned by the moderator.
398 self.assertIsNotNone(workflow.token)
399 self.assertEqual(workflow.token_owner, TokenOwner.moderator)
400 # Create a new workflow with the previous workflow's save token, and
401 # restore its state. This models an approved subscription and should
402 # result in the user getting subscribed.
403 approved_workflow = SubscriptionWorkflow(self._mlist)
404 approved_workflow.token = workflow.token
405 approved_workflow.restore()
406 list(approved_workflow)
407 # Now the user is subscribed to the mailing list.
408 member = self._mlist.regular_members.get_member(self._anne)
409 self.assertEqual(member.address, anne)
410 self.assertEqual(approved_workflow.member, member)
411 # No further token is needed.
412 self.assertIsNone(approved_workflow.token)
413 self.assertEqual(approved_workflow.token_owner, TokenOwner.no_one)
415 def test_get_moderator_approval_log_on_hold(self):
416 # When the subscription is held for moderator approval, a message is
417 # logged.
418 mark = LogFileMark('mailman.subscribe')
419 self._mlist.subscription_policy = SubscriptionPolicy.moderate
420 anne = self._user_manager.create_address(self._anne)
421 workflow = SubscriptionWorkflow(self._mlist, anne,
422 pre_verified=True,
423 pre_confirmed=True)
424 # Consume the entire state machine.
425 list(workflow)
426 self.assertIn(
427 'test@example.com: held subscription request from anne@example.com',
428 mark.readline()
431 def test_get_moderator_approval_notifies_moderators(self):
432 # When the subscription is held for moderator approval, and the list
433 # is so configured, a notification is sent to the list moderators.
434 self._mlist.admin_immed_notify = True
435 self._mlist.subscription_policy = SubscriptionPolicy.moderate
436 anne = self._user_manager.create_address(self._anne)
437 workflow = SubscriptionWorkflow(self._mlist, anne,
438 pre_verified=True,
439 pre_confirmed=True)
440 # Consume the entire state machine.
441 list(workflow)
442 items = get_queue_messages('virgin', expected_count=1)
443 message = items[0].msg
444 self.assertEqual(message['From'], 'test-owner@example.com')
445 self.assertEqual(message['To'], 'test-owner@example.com')
446 self.assertEqual(
447 message['Subject'],
448 'New subscription request to Test from anne@example.com')
449 self.assertEqual(message.get_payload(), """\
450 Your authorization is required for a mailing list subscription request
451 approval:
453 For: anne@example.com
454 List: test@example.com""")
456 def test_get_moderator_approval_no_notifications(self):
457 # When the subscription is held for moderator approval, and the list
458 # is so configured, a notification is sent to the list moderators.
459 self._mlist.admin_immed_notify = False
460 self._mlist.subscription_policy = SubscriptionPolicy.moderate
461 anne = self._user_manager.create_address(self._anne)
462 workflow = SubscriptionWorkflow(self._mlist, anne,
463 pre_verified=True,
464 pre_confirmed=True)
465 # Consume the entire state machine.
466 list(workflow)
467 get_queue_messages('virgin', expected_count=0)
469 def test_send_confirmation(self):
470 # A confirmation message gets sent when the address is not verified.
471 anne = self._user_manager.create_address(self._anne)
472 self.assertIsNone(anne.verified_on)
473 # Run the workflow to model the confirmation step.
474 workflow = SubscriptionWorkflow(self._mlist, anne)
475 list(workflow)
476 items = get_queue_messages('virgin', expected_count=1)
477 message = items[0].msg
478 token = workflow.token
479 self.assertEqual(message['Subject'], 'confirm {}'.format(token))
480 self.assertEqual(
481 message['From'], 'test-confirm+{}@example.com'.format(token))
482 # The confirmation message is not `Precedence: bulk`.
483 self.assertIsNone(message['precedence'])
485 def test_send_confirmation_pre_confirmed(self):
486 # A confirmation message gets sent when the address is not verified
487 # but the subscription is pre-confirmed.
488 anne = self._user_manager.create_address(self._anne)
489 self.assertIsNone(anne.verified_on)
490 # Run the workflow to model the confirmation step.
491 workflow = SubscriptionWorkflow(self._mlist, anne, pre_confirmed=True)
492 list(workflow)
493 items = get_queue_messages('virgin', expected_count=1)
494 message = items[0].msg
495 token = workflow.token
496 self.assertEqual(
497 message['Subject'], 'confirm {}'.format(workflow.token))
498 self.assertEqual(
499 message['From'], 'test-confirm+{}@example.com'.format(token))
501 def test_send_confirmation_pre_verified(self):
502 # A confirmation message gets sent even when the address is verified
503 # when the subscription must be confirmed.
504 self._mlist.subscription_policy = SubscriptionPolicy.confirm
505 anne = self._user_manager.create_address(self._anne)
506 self.assertIsNone(anne.verified_on)
507 # Run the workflow to model the confirmation step.
508 workflow = SubscriptionWorkflow(self._mlist, anne, pre_verified=True)
509 list(workflow)
510 items = get_queue_messages('virgin', expected_count=1)
511 message = items[0].msg
512 token = workflow.token
513 self.assertEqual(
514 message['Subject'], 'confirm {}'.format(workflow.token))
515 self.assertEqual(
516 message['From'], 'test-confirm+{}@example.com'.format(token))
518 def test_do_confirm_verify_address(self):
519 # The address is not yet verified, nor are we pre-verifying. A
520 # confirmation message will be sent. When the user confirms their
521 # subscription request, the address will end up being verified.
522 anne = self._user_manager.create_address(self._anne)
523 self.assertIsNone(anne.verified_on)
524 # Run the workflow to model the confirmation step.
525 workflow = SubscriptionWorkflow(self._mlist, anne)
526 list(workflow)
527 # The address is still not verified.
528 self.assertIsNone(anne.verified_on)
529 confirm_workflow = SubscriptionWorkflow(self._mlist)
530 confirm_workflow.token = workflow.token
531 confirm_workflow.restore()
532 confirm_workflow.run_thru('do_confirm_verify')
533 # The address is now verified.
534 self.assertIsNotNone(anne.verified_on)
536 def test_do_confirm_verify_user(self):
537 # A confirmation step is necessary when a user subscribes with their
538 # preferred address, and we are not pre-confirming.
539 anne = self._user_manager.create_user(self._anne)
540 set_preferred(anne)
541 # Run the workflow to model the confirmation step. There is no
542 # subscriber attribute yet.
543 workflow = SubscriptionWorkflow(self._mlist, anne)
544 list(workflow)
545 self.assertEqual(workflow.subscriber, anne)
546 # Do a confirmation workflow, which should now set the subscriber.
547 confirm_workflow = SubscriptionWorkflow(self._mlist)
548 confirm_workflow.token = workflow.token
549 confirm_workflow.restore()
550 confirm_workflow.run_thru('do_confirm_verify')
551 # The address is now verified.
552 self.assertEqual(confirm_workflow.subscriber, anne)
554 def test_do_confirmation_subscribes_user(self):
555 # Subscriptions to the mailing list must be confirmed. Once that's
556 # done, the user's address (which is not initially verified) gets
557 # subscribed to the mailing list.
558 self._mlist.subscription_policy = SubscriptionPolicy.confirm
559 anne = self._user_manager.create_address(self._anne)
560 self.assertIsNone(anne.verified_on)
561 workflow = SubscriptionWorkflow(self._mlist, anne)
562 list(workflow)
563 # Anne is not yet a member.
564 member = self._mlist.regular_members.get_member(self._anne)
565 self.assertIsNone(member)
566 self.assertIsNone(workflow.member)
567 # The token is owned by the subscriber.
568 self.assertIsNotNone(workflow.token)
569 self.assertEqual(workflow.token_owner, TokenOwner.subscriber)
570 # Confirm.
571 confirm_workflow = SubscriptionWorkflow(self._mlist)
572 confirm_workflow.token = workflow.token
573 confirm_workflow.restore()
574 list(confirm_workflow)
575 self.assertIsNotNone(anne.verified_on)
576 # Anne is now a member.
577 member = self._mlist.regular_members.get_member(self._anne)
578 self.assertEqual(member.address, anne)
579 self.assertEqual(confirm_workflow.member, member)
580 # No further token is needed.
581 self.assertIsNone(confirm_workflow.token)
582 self.assertEqual(confirm_workflow.token_owner, TokenOwner.no_one)
584 def test_prevent_confirmation_replay_attacks(self):
585 # Ensure that if the workflow requires two confirmations, e.g. first
586 # the user confirming their subscription, and then the moderator
587 # approving it, that different tokens are used in these two cases.
588 self._mlist.subscription_policy = (
589 SubscriptionPolicy.confirm_then_moderate)
590 anne = self._user_manager.create_address(self._anne)
591 workflow = SubscriptionWorkflow(self._mlist, anne, pre_verified=True)
592 # Run the state machine up to the first confirmation, and cache the
593 # confirmation token.
594 list(workflow)
595 token = workflow.token
596 # Anne is not yet a member of the mailing list.
597 member = self._mlist.regular_members.get_member(self._anne)
598 self.assertIsNone(member)
599 self.assertIsNone(workflow.member)
600 # The token is owned by the subscriber.
601 self.assertIsNotNone(workflow.token)
602 self.assertEqual(workflow.token_owner, TokenOwner.subscriber)
603 # The old token will not work for moderator approval.
604 moderator_workflow = SubscriptionWorkflow(self._mlist)
605 moderator_workflow.token = token
606 moderator_workflow.restore()
607 list(moderator_workflow)
608 # The token is owned by the moderator.
609 self.assertIsNotNone(moderator_workflow.token)
610 self.assertEqual(moderator_workflow.token_owner, TokenOwner.moderator)
611 # While we wait for the moderator to approve the subscription, note
612 # that there's a new token for the next steps.
613 self.assertNotEqual(token, moderator_workflow.token)
614 # The old token won't work.
615 final_workflow = SubscriptionWorkflow(self._mlist)
616 final_workflow.token = token
617 self.assertRaises(LookupError, final_workflow.restore)
618 # Running this workflow will fail.
619 self.assertRaises(AssertionError, list, final_workflow)
620 # Anne is still not subscribed.
621 member = self._mlist.regular_members.get_member(self._anne)
622 self.assertIsNone(member)
623 self.assertIsNone(final_workflow.member)
624 # However, if we use the new token, her subscription request will be
625 # approved by the moderator.
626 final_workflow.token = moderator_workflow.token
627 final_workflow.restore()
628 list(final_workflow)
629 # And now Anne is a member.
630 member = self._mlist.regular_members.get_member(self._anne)
631 self.assertEqual(member.address.email, self._anne)
632 self.assertEqual(final_workflow.member, member)
633 # No further token is needed.
634 self.assertIsNone(final_workflow.token)
635 self.assertEqual(final_workflow.token_owner, TokenOwner.no_one)
637 def test_confirmation_needed_and_pre_confirmed(self):
638 # The subscription policy is 'confirm' but the subscription is
639 # pre-confirmed so the moderation checks can be skipped.
640 self._mlist.subscription_policy = SubscriptionPolicy.confirm
641 anne = self._user_manager.create_address(self._anne)
642 workflow = SubscriptionWorkflow(
643 self._mlist, anne,
644 pre_verified=True, pre_confirmed=True, pre_approved=True)
645 list(workflow)
646 # Anne was subscribed.
647 self.assertIsNone(workflow.token)
648 self.assertEqual(workflow.token_owner, TokenOwner.no_one)
649 self.assertEqual(workflow.member.address, anne)