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