Don't use `flake8: noqa`.
[mailman.git] / src / mailman / commands / eml_membership.py
blob8168e98fa92a34dc27f80bc19ca090db5261d4bc
1 # Copyright (C) 2002-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 """The email commands 'join' and 'subscribe'."""
20 from email.utils import formataddr, parseaddr
21 from mailman import public
22 from mailman.core.i18n import _
23 from mailman.interfaces.command import ContinueProcessing, IEmailCommand
24 from mailman.interfaces.member import DeliveryMode, MemberRole
25 from mailman.interfaces.registrar import IRegistrar
26 from mailman.interfaces.subscriptions import ISubscriptionService
27 from mailman.interfaces.usermanager import IUserManager
28 from zope.component import getUtility
29 from zope.interface import implementer
32 def match_subscriber(email, display_name):
33 # Return something matching the email which should be used as the
34 # subscriber by the IRegistrar interface.
35 manager = getUtility(IUserManager)
36 # Is there a user with a preferred address matching the email?
37 user = manager.get_user(email)
38 if user is not None:
39 preferred = user.preferred_address
40 if preferred is not None and preferred.email == email.lower():
41 return user
42 # Is there an address matching the email?
43 address = manager.get_address(email)
44 if address is not None:
45 return address
46 # Make a new user and subscribe their first (and only) address. We can't
47 # make the first address their preferred address because it hasn't been
48 # verified yet.
49 user = manager.make_user(email, display_name)
50 return list(user.addresses)[0]
53 @public
54 @implementer(IEmailCommand)
55 class Join:
56 """The email 'join' command."""
58 name = 'join'
59 # XXX 2012-02-29 BAW: DeliveryMode.summary is not yet supported.
60 argument_description = '[digest=<no|mime|plain>]'
61 description = _("""\
62 You will be asked to confirm your subscription request and you may be issued a
63 provisional password.
65 By using the 'digest' option, you can specify whether you want digest delivery
66 or not. If not specified, the mailing list's default delivery mode will be
67 used.
68 """)
69 short_description = _('Join this mailing list.')
71 def process(self, mlist, msg, msgdata, arguments, results):
72 """See `IEmailCommand`."""
73 # Parse the arguments.
74 delivery_mode = self._parse_arguments(arguments, results)
75 if delivery_mode is ContinueProcessing.no:
76 return ContinueProcessing.no
77 display_name, email = parseaddr(msg['from'])
78 # Address could be None or the empty string.
79 if not email:
80 email = msg.sender
81 if not email:
82 print(_('$self.name: No valid address found to subscribe'),
83 file=results)
84 return ContinueProcessing.no
85 if isinstance(email, bytes):
86 email = email.decode('ascii')
87 # Have we already seen one join request from this user during the
88 # processing of this email?
89 joins = getattr(results, 'joins', set())
90 if email in joins:
91 # Do not register this join.
92 return ContinueProcessing.yes
93 joins.add(email)
94 results.joins = joins
95 person = formataddr((display_name, email)) # noqa
96 # Is this person already a member of the list? Search for all
97 # matching memberships.
98 members = getUtility(ISubscriptionService).find_members(
99 email, mlist.list_id, MemberRole.member)
100 if len(members) > 0:
101 print(_('$person is already a member'), file=results)
102 return ContinueProcessing.yes
103 subscriber = match_subscriber(email, display_name)
104 IRegistrar(mlist).register(subscriber)
105 print(_('Confirmation email sent to $person'), file=results)
106 return ContinueProcessing.yes
108 def _parse_arguments(self, arguments, results):
109 """Parse command arguments.
111 :param arguments: The sequences of arguments as given to the
112 `process()` method.
113 :param results: The results object.
114 :return: The delivery mode, None, or ContinueProcessing.no on error.
116 mode = DeliveryMode.regular
117 for argument in arguments:
118 parts = argument.split('=', 1)
119 if len(parts) != 2 or parts[0] != 'digest':
120 print(self.name, _('bad argument: $argument'),
121 file=results)
122 return ContinueProcessing.no
123 mode = {
124 'no': DeliveryMode.regular,
125 'plain': DeliveryMode.plaintext_digests,
126 'mime': DeliveryMode.mime_digests,
127 }.get(parts[1])
128 if mode is None:
129 print(self.name, _('bad argument: $argument'),
130 file=results)
131 return ContinueProcessing.no
132 return mode
135 @public
136 class Subscribe(Join):
137 """The email 'subscribe' command (an alias for 'join')."""
139 name = 'subscribe'
140 description = _("An alias for 'join'.")
141 short_description = description
144 @public
145 @implementer(IEmailCommand)
146 class Leave:
147 """The email 'leave' command."""
149 name = 'leave'
150 argument_description = ''
151 description = _("""Leave this mailing list.
153 You may be asked to confirm your request.""")
154 short_description = _('Leave this mailing list.')
156 def process(self, mlist, msg, msgdata, arguments, results):
157 """See `IEmailCommand`."""
158 email = msg.sender
159 if not email:
160 print(_('$self.name: No valid email address found to unsubscribe'),
161 file=results)
162 return ContinueProcessing.no
163 user_manager = getUtility(IUserManager)
164 user = user_manager.get_user(email)
165 if user is None:
166 print(_('No registered user for email address: $email'),
167 file=results)
168 return ContinueProcessing.no
169 # The address that the -leave command was sent from, must be verified.
170 # Otherwise you could link a bogus address to anyone's account, and
171 # then send a leave command from that address.
172 if user_manager.get_address(email).verified_on is None:
173 print(_('Invalid or unverified email address: $email'),
174 file=results)
175 return ContinueProcessing.no
176 for user_address in user.addresses:
177 # Only recognize verified addresses.
178 if user_address.verified_on is None:
179 continue
180 member = mlist.members.get_member(user_address.email)
181 if member is not None:
182 break
183 else:
184 # None of the user's addresses are subscribed to this mailing list.
185 print(_(
186 '$self.name: $email is not a member of $mlist.fqdn_listname'),
187 file=results)
188 return ContinueProcessing.no
189 member.unsubscribe()
190 person = formataddr((user.display_name, email)) # noqa
191 print(_('$person left $mlist.fqdn_listname'), file=results)
192 return ContinueProcessing.yes
195 @public
196 class Unsubscribe(Leave):
197 """The email 'unsubscribe' command (an alias for 'leave')."""
199 name = 'unsubscribe'
200 description = _("An alias for 'leave'.")
201 short_description = description