1# Copyright (C) 2009-2020 by the Free Software Foundation, Inc.
2#
3# This file is part of GNU Mailman.
4#
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.
9#
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.
14#
15# You should have received a copy of the GNU General Public License along with
16# GNU Mailman.  If not, see <https://www.gnu.org/licenses/>.
17
18"""Subscription management."""
19
20from collections import namedtuple
21from enum import Enum
22from mailman.interfaces.errors import MailmanError
23from mailman.interfaces.member import DeliveryMode, MembershipError
24from public import public
25from zope.interface import Interface
26
27
28@public
29class MissingUserError(MailmanError):
30    """A an invalid user id was given."""
31
32    def __init__(self, user_id):
33        super().__init__()
34        self.user_id = user_id
35
36    def __str__(self):
37        return self.user_id
38
39
40@public
41class SubscriptionPendingError(MailmanError):
42    def __init__(self, mlist, email):
43        super().__init__()
44        self.mlist = mlist
45        self.email = email
46
47
48@public
49class TooManyMembersError(MembershipError):
50    def __init__(self, subscriber, list_id, role):
51        super().__init__()
52        self.subscriber = subscriber
53        self.list_id = list_id
54        self.role = role
55
56
57_RequestRecord = namedtuple(
58    'RequestRecord',
59    'email display_name delivery_mode, language')
60
61
62@public
63def RequestRecord(email, display_name='',
64                  delivery_mode=DeliveryMode.regular,
65                  language=None):
66    if language is None:
67        from mailman.core.constants import system_preferences
68        language = system_preferences.preferred_language
69    return _RequestRecord(email, display_name, delivery_mode, language)
70
71
72@public
73class TokenOwner(Enum):
74    """Who 'owns' the token returned from the registrar?"""
75    no_one = 0
76    subscriber = 1
77    moderator = 2
78
79
80@public
81class SubscriptionConfirmationNeededEvent:
82    """Triggered when a subscription needs confirmation.
83
84    Addresses must be verified before they can receive messages or post
85    to mailing list.  The confirmation message is sent to the user when
86    this event is triggered.
87    """
88    def __init__(self, mlist, token, email):
89        self.mlist = mlist
90        self.token = token
91        self.email = email
92
93
94@public
95class UnsubscriptionConfirmationNeededEvent:
96    """Triggered when an unsubscription request needs confirmation.
97
98    The confirmation message is sent to the user when this event is
99    triggered.
100    """
101    def __init__(self, mlist, token, email):
102        self.mlist = mlist
103        self.token = token
104        self.email = email
105
106
107@public
108class ISubscriptionService(Interface):
109    """General subscription services."""
110
111    def get_members():
112        """Return a sequence of all members of all mailing lists.
113
114        The members are sorted first by fully-qualified mailing list name,
115        then by subscribed email address, then by role.  Because the user may
116        be a member of the list under multiple roles (e.g. as an owner and as
117        a digest member), the member can appear multiple times in this list.
118        Roles are sorted by: owner, moderator, member.
119
120        :return: The list of all members.
121        :rtype: list of `IMember`
122        """
123
124    def get_member(member_id):
125        """Return a member record matching the member id.
126
127        :param member_id: A member id.
128        :type member_id: int
129        :return: The matching member, or None if no matching member is found.
130        :rtype: `IMember`
131        """
132
133    def find_members(subscriber=None, list_id=None, role=None):
134        """Search for members matching some criteria.
135
136        The members are sorted first by list-id, then by subscribed
137        email address, then by role.  Because the user may be a member
138        of the list under multiple roles (e.g. as an owner and as a
139        digest member), the member can appear multiple times in this
140        list.
141
142        :param subscriber: The email address or user id of the user getting
143            subscribed.  This argument may contain asterisks, which will be
144            interpreted as wildcards in the search pattern.
145        :type subscriber: string or int
146        :param list_id: The list id of the mailing list to search for the
147            subscriber's memberships on.
148        :type list_id: string
149        :param role: The member role.
150        :type role: `MemberRole`
151        :return: A sequence of all memberships, which may be empty.
152        :rtype: A `QuerySequence` of `IMember`
153        """
154
155    def find_member(subscriber=None, list_id=None, role=None):
156        """Search for a member matching some criteria.
157
158        This is like find_members() but is guaranteed to return exactly
159        one member.
160
161        :param subscriber: The email address or user id of the user getting
162            subscribed.
163        :type subscriber: string or int
164        :param list_id: The list id of the mailing list to search for the
165            subscriber's memberships on.
166        :type list_id: string
167        :param role: The member role.
168        :type role: `MemberRole`
169        :return: The member matching the given criteria or None if no
170            members match the criteria.
171        :rtype: `IMember` or None
172        :raises TooManyMembersError: when the given criteria matches
173            more than one membership.
174        """
175
176    def __iter__():
177        """See `get_members()`."""
178
179    def leave(list_id, email):
180        """Unsubscribe from a mailing list.
181
182        :param list_id: The list id of the mailing list the user is
183            unsubscribing from.
184        :type list_id: string
185        :param email: The email address of the user getting unsubscribed.
186        :type email: string
187        :raises InvalidEmailAddressError: if the email address is not valid.
188        :raises NoSuchListError: if the named mailing list does not exist.
189        :raises NotAMemberError: if the given address is not a member of the
190            mailing list.
191        """
192
193    def unsubscribe_members(list_id, emails):
194        """Unsubscribe a batch of members from a mailing list.
195
196        :param list_id: The list id to operate on.
197        :type list_id: string
198        :param emails: A list of email addresses of the members getting
199            unsubscribed.  Only list members with a role of `member` can be
200            unsubscribed via this interface.
201        :type emails: list of strings
202        :return: A two item tuple whose first item is a set of all the
203            successfully unsubscribed email addresses and second item is
204            a set of all unsuccessful email addresses.
205        :rtype: 2-tuple of (set-of-strings, set-of-strings)
206        :raises NoSuchListError: if the named mailing list does not exist.
207        """
208
209
210@public
211class ISubscriptionManager(Interface):
212    """Handling subscription and unsubscription of addresses and users.
213
214    This is a higher level interface to user registration and
215    unregistration, email address confirmation, etc. than the
216    `IUserManager`.  The latter does no validation, syntax checking, or
217    confirmation, while this interface does.
218
219    To use this, adapt an ``IMailingList`` to this interface.
220    """
221    def register(subscriber=None, *,
222                 pre_verified=False, pre_confirmed=False, pre_approved=False,
223                 send_welcome_message=None):
224        """Subscribe an address or user according to subscription policies.
225
226        The mailing list's subscription policy is used to subscribe
227        `subscriber` to the given mailing list.  The subscriber can be
228        an ``IUser``, in which case the user must have a preferred
229        address, and that preferred address will be subscribed.  The
230        subscriber can also be an ``IAddress``, in which case the
231        address will be subscribed.
232
233        The workflow may pause (i.e. be serialized, saved, and
234        suspended) when some out-of-band confirmation step is required.
235        For example, if the user must confirm, or the moderator must
236        approve the subscription.  Use the ``confirm(token)`` method to
237        resume the workflow.
238
239        :param subscriber: The user or address to subscribe.
240        :type email: ``IUser`` or ``IAddress``
241        :param pre_verified: A flag indicating whether the subscriber's email
242            address should be considered pre-verified.  Normally a never
243            before seen email address must be verified by mail-back
244            confirmation.  Setting this flag to True automatically verifies
245            such addresses without the mail-back.  (A confirmation message may
246            still be sent under other conditions.)
247        :type pre_verified: bool
248        :param pre_confirmed: A flag indicating whether, when required by the
249            subscription policy, a subscription request should be considered
250            pre-confirmed.  Normally in such cases, a mail-back confirmation
251            message is sent to the subscriber, which must be positively
252            acknowledged by some manner.  Setting this flag to True
253            automatically confirms the subscription request.  (A confirmation
254            message may still be sent under other conditions.)
255        :type pre_confirmed: bool
256        :param pre_approved: A flag indicating whether, when required by the
257            subscription policy, a subscription request should be considered
258            pre-approved.  Normally in such cases, the list administrator is
259            notified that an approval is necessary, which must be positively
260            acknowledged in some manner.  Setting this flag to True
261            automatically approves the subscription request.
262        :type pre_approved: bool
263        :param send_welcome_message: A flag indicating whether the new member
264            should receive a welcome message. This overrides the list's
265            configuration of send_welcome_message if it is specified.
266        :type send_welcome_message: bool
267        :return: A 3-tuple is returned where the first element is the token
268            hash, the second element is a ``TokenOwner`, and the third element
269            is the subscribed member.  If the subscriber got subscribed
270            immediately, the token will be None and the member will be
271            an ``IMember``.  If the subscription got held, the token
272            will be a hash and the member will be None.
273        :rtype: (str-or-None, ``TokenOwner``, ``IMember``-or-None)
274        :raises MembershipIsBannedError: when the address being subscribed
275            appears in the global or list-centric bans.
276        :raises InvalidEmailAddressError: If the address being subscribed is
277            the list's posting address.
278
279        """
280
281    def unregister(subscriber=None, *,
282                   pre_confirmed=False, pre_approved=False):
283        """Unsubscribe an address or user according to subscription policies.
284
285        The mailing list's unsubscription policy is used to unsubscribe
286        `subscriber` from the given mailing list.  The subscriber can be
287        an ``IUser`` or an ``IAddress``, and must already be subscribed to the
288        mailing list.
289
290        The workflow may pause (i.e. be serialized, saved, and
291        suspended) when some out-of-band confirmation step is required.
292        For example, if the user must confirm, or the moderator must
293        approve the unsubscription.  Use the ``confirm(token)`` method to
294        resume the workflow.
295
296        :param subscriber: The user or address to unsubscribe.
297        :type email: ``IUser`` or ``IAddress``
298        :param pre_confirmed: A flag indicating whether, when required by the
299            unsubscription policy, an unsubscription request should be
300            considered pre-confirmed.  Normally in such cases, a mail-back
301            confirmation message is sent to the subscriber, which must be
302            positively acknowledged by some manner.  Setting this flag to True
303            automatically confirms the unsubscription request.  (A confirmation
304            message may still be sent under other conditions.)
305        :type pre_confirmed: bool
306        :param pre_approved: A flag indicating whether, when required by the
307            unsubscription policy, an unsubscription request should be
308            considered pre-approved.  Normally in such cases, the list
309            administrator is notified that an approval is necessary, which
310            must be positively acknowledged in some manner.  Setting this flag
311            to True automatically approves the unsubscription request.
312        :type pre_approved: bool
313        :return: A 3-tuple is returned where the first element is the token
314            hash, the second element is a ``TokenOwner`, and the third element
315            is the unsubscribing member.  If the subscriber got unsubscribed
316            immediately, the token will be None and the member will be
317            an ``IMember``.  If the unsubscription got held, the token
318            will be a hash and the member will be None.
319        :rtype: (str-or-None, ``TokenOwner``, ``IMember``-or-None)
320        """
321
322    def confirm(token):
323        """Continue any paused workflow.
324
325        Confirmation may occur after the user confirms their
326        subscription request, or their email address must be verified,
327        or the moderator must approve the subscription request.
328
329        :param token: A token matching a workflow.
330        :type token: string
331        :return: A 3-tuple is returned where the first element is the token
332            hash, the second element is a ``TokenOwner`, and the third element
333            is the subscribed member.  If the subscriber got subscribed
334            immediately, the token will be None and the member will be
335            an ``IMember``.  If the subscription is still being held, the token
336            will be a hash and the member will be None.
337        :rtype: (str-or-None, ``TokenOwner``, ``IMember``-or-None)
338        :raises LookupError: when no workflow is associated with the token.
339        """
340
341    def discard(token):
342        """Discard the workflow matched to the given `token`.
343
344        :param token: A token matching a pending event with a type of
345            'registration'.
346        :raises LookupError: when no workflow is associated with the token.
347        """
348
349    def evict():
350        """Evict all saved workflows which have expired."""
351