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)
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
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."""
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
):
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
)
61 workflow
.run_thru('send_confirmation')
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
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
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
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',
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
:
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
:
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
:
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
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
:
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
:
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
,
218 workflow
.run_thru('confirmation_checks')
219 with patch
.object(workflow
, '_step_do_subscription') as step
:
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
,
233 workflow
.run_thru('confirmation_checks')
234 with patch
.object(workflow
, '_step_moderation_checks') as step
:
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
,
247 workflow
.run_thru('confirmation_checks')
248 with patch
.object(workflow
, '_step_moderation_checks') as step
:
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
:
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
:
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
,
282 workflow
.run_thru('moderation_checks')
283 with patch
.object(workflow
, '_step_do_subscription') as step
:
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
:
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.
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
,
323 # Consume the entire state machine.
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
,
344 # Consume the entire state machine.
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
,
364 token
= workflow
.token
365 # Consume the entire state machine.
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
,
391 # Consume the entire state machine.
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
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
,
424 # Consume the entire state machine.
427 'test@example.com: held subscription request from anne@example.com',
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
,
440 # Consume the entire state machine.
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')
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
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
,
465 # Consume the entire state machine.
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
)
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
))
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)
493 items
= get_queue_messages('virgin', expected_count
=1)
494 message
= items
[0].msg
495 token
= workflow
.token
497 message
['Subject'], 'confirm {}'.format(workflow
.token
))
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)
510 items
= get_queue_messages('virgin', expected_count
=1)
511 message
= items
[0].msg
512 token
= workflow
.token
514 message
['Subject'], 'confirm {}'.format(workflow
.token
))
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
)
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
)
541 # Run the workflow to model the confirmation step. There is no
542 # subscriber attribute yet.
543 workflow
= SubscriptionWorkflow(self
._mlist
, anne
)
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
)
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
)
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.
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()
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(
644 pre_verified
=True, pre_confirmed
=True, pre_approved
=True)
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
)