diff options
| author | J08nY | 2017-07-04 19:45:11 +0200 |
|---|---|---|
| committer | J08nY | 2017-08-30 13:18:11 +0200 |
| commit | 53ce68a20c349749e4a8c70e2ec254351b5d2477 (patch) | |
| tree | a333bc165fe9b79d52921a85b9d2987d6d9faef4 /src/mailman/workflows/common.py | |
| parent | d773e0fe42213a73ca0aacbfa2a665440f7b7e4d (diff) | |
| download | mailman-53ce68a20c349749e4a8c70e2ec254351b5d2477.tar.gz mailman-53ce68a20c349749e4a8c70e2ec254351b5d2477.tar.zst mailman-53ce68a20c349749e4a8c70e2ec254351b5d2477.zip | |
Diffstat (limited to 'src/mailman/workflows/common.py')
| -rw-r--r-- | src/mailman/workflows/common.py | 121 |
1 files changed, 119 insertions, 2 deletions
diff --git a/src/mailman/workflows/common.py b/src/mailman/workflows/common.py index 79d57d0ae..d6c372b1e 100644 --- a/src/mailman/workflows/common.py +++ b/src/mailman/workflows/common.py @@ -23,15 +23,19 @@ import logging from datetime import timedelta from email.utils import formataddr from enum import Enum + +from mailman.app.membership import delete_member from mailman.core.i18n import _ from mailman.email.message import UserNotification from mailman.interfaces.address import IAddress from mailman.interfaces.bans import IBanManager from mailman.interfaces.member import (AlreadySubscribedError, MemberRole, - MembershipIsBannedError) + MembershipIsBannedError, + NotAMemberError) from mailman.interfaces.pending import IPendable, IPendings from mailman.interfaces.subscriptions import ( - SubscriptionConfirmationNeededEvent, SubscriptionPendingError, TokenOwner) + SubscriptionConfirmationNeededEvent, SubscriptionPendingError, TokenOwner, + UnsubscriptionConfirmationNeededEvent) from mailman.interfaces.template import ITemplateLoader from mailman.interfaces.user import IUser from mailman.interfaces.usermanager import IUserManager @@ -331,3 +335,116 @@ class ModerationMixin: else: assert self.which is WhichSubscriber.user self.subscriber = self.user + + +class UnsubscriptionBase(SubscriptionWorkflowCommon): + + def __init__(self, mlist, subscriber): + super().__init__(mlist, subscriber) + if IAddress.providedBy(subscriber) or IUser.providedBy(subscriber): + self.member = self.mlist.regular_members.get_member( + self.address.email) + + def _step_subscription_checks(self): + assert self.mlist.is_subscribed(self.subscriber) + + def _step_do_unsubscription(self): + try: + delete_member(self.mlist, self.address.email) + except NotAMemberError: + # The member has already been unsubscribed. + pass + self.member = None + assert self.token is None and self.token_owner is TokenOwner.no_one, ( + 'Unexpected active token at end of subscription workflow') + + +class UnRequestMixin: + + def _step_send_confirmation(self): + self._set_token(TokenOwner.subscriber) + self.push('do_confirm_verify') + self.save() + notify(UnsubscriptionConfirmationNeededEvent( + self.mlist, self.token, self.address.email)) + raise StopIteration + + def _step_do_confirm_verify(self): + # Restore a little extra state that can't be stored in the database + # (because the order of setattr() on restore is indeterminate), then + # continue with the confirmation/verification step. + if self.which is WhichSubscriber.address: + self.subscriber = self.address + else: + assert self.which is WhichSubscriber.user + self.subscriber = self.user + # Reset the token so it can't be used in a replay attack. + self._set_token(TokenOwner.no_one) + # Restore the member object. + self.member = self.mlist.regular_members.get_member(self.address.email) + # It's possible the member was already unsubscribed while we were + # waiting for the confirmation. + if self.member is None: + return + # The user has confirmed their unsubscription request + self.confirmed = True + + +class UnConfirmationMixin(UnRequestMixin): + + def __init__(self, pre_confirmed=False): + self.confirmed = pre_confirmed + + def _step_confirmation_checks(self): + # If the unsubscription has been pre-confirmed, then we can skip the + # confirmation check can be skipped. + if self.confirmed: + return + # The user must confirm their unsubscription. + self.push('send_confirmation') + + +class UnModerationMixin(UnRequestMixin): + + def __init__(self, pre_approved=False): + self.approved = pre_approved + + def _step_moderation_checks(self): + # Does the moderator need to approve the unsubscription request? + if not self.approved: + self.push('get_moderator_approval') + + def _step_get_moderator_approval(self): + self._set_token(TokenOwner.moderator) + self.push('unsubscribe_from_restored') + self.save() + log.info('{}: held unsubscription request from {}'.format( + self.mlist.fqdn_listname, self.address.email)) + if self.mlist.admin_immed_notify: + subject = _( + 'New unsubscription request to $self.mlist.display_name ' + 'from $self.address.email') + username = formataddr( + (self.subscriber.display_name, self.address.email)) + template = getUtility(ITemplateLoader).get( + 'list:admin:action:unsubscribe', self.mlist) + text = wrap(expand(template, self.mlist, dict( + member=username, + ))) + # 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) + # The workflow must stop running here + raise StopIteration + + def _step_unsubscribe_from_restored(self): + # Prevent replay attacks. + self._set_token(TokenOwner.no_one) + if self.which is WhichSubscriber.address: + self.subscriber = self.address + else: + assert self.which is WhichSubscriber.user + self.subscriber = self.user |
