11
from operator import attrgetter
14
11
from lazr.lifecycle.event import ObjectCreatedEvent
15
12
from lazr.lifecycle.interfaces import IObjectCreatedEvent
17
13
from zope.component import getUtility
18
14
from zope.event import notify
19
15
from zope.interface import implements
21
from lp.bugs.interfaces.bug import (
23
CreatedBugWithNoBugTasksError,
25
from lp.bugs.interfaces.bugattachment import (
29
from lp.bugs.interfaces.bugmessage import IBugMessageSet
30
from lp.bugs.mail.commands import BugEmailCommands
31
from lp.services.identity.interfaces.emailaddress import IEmailAddressSet
32
from lp.services.mail.helpers import (
17
from canonical.database.sqlbase import rollback
18
from canonical.launchpad.helpers import get_email_template
19
from canonical.launchpad.interfaces.mail import (
23
IBugTaskEditEmailCommand,
27
from lp.services.messages.interfaces.message import IMessageSet
28
from canonical.launchpad.mail.commands import (
32
from canonical.launchpad.mail.helpers import (
33
33
ensure_not_weakly_authenticated,
38
36
IncomingEmailError,
40
38
reformat_wiki_text,
42
from lp.services.mail.interfaces import (
46
IBugTaskEditEmailCommand,
50
from lp.services.mail.mailwrapper import MailWrapper
51
from lp.services.mail.notification import send_process_error_notification
40
from canonical.launchpad.mailnotification import (
42
send_process_error_notification,
44
from canonical.launchpad.webapp.interfaces import ILaunchBag
45
from lp.bugs.interfaces.bug import CreatedBugWithNoBugTasksError
46
from lp.bugs.interfaces.bugattachment import (
50
from lp.bugs.interfaces.bugmessage import IBugMessageSet
52
51
from lp.services.mail.sendmail import simple_sendmail
53
from lp.services.messages.interfaces.message import IMessageSet
54
from lp.services.webapp.interfaces import ILaunchBag
57
error_templates = os.path.join(os.path.dirname(__file__), 'errortemplates')
60
class BugTaskCommandGroup:
62
def __init__(self, command=None):
64
if command is not None:
65
self._commands.append(command)
67
def __nonzero__(self):
68
return len(self._commands) > 0
71
text_commands = [str(cmd) for cmd in self.commands]
72
return '\n'.join(text_commands).strip()
76
"Return the `EmailCommand`s ordered by their rank."
77
return sorted(self._commands, key=attrgetter('RANK'))
79
def add(self, command):
80
"Add an `EmailCommand` to the commands."
81
self._commands.append(command)
84
class BugCommandGroup(BugTaskCommandGroup):
86
def __init__(self, command=None):
87
super(BugCommandGroup, self).__init__(command=command)
90
def __nonzero__(self):
91
if len(self._groups) > 0:
94
return super(BugCommandGroup, self).__nonzero__()
97
text_commands = [super(BugCommandGroup, self).__str__()]
98
for group in self.groups:
99
text_commands += [str(group)]
100
return '\n'.join(text_commands).strip()
104
"Return the `BugTaskCommandGroup`s."
106
len(self.commands) > 0
107
and self.commands[0].RANK == 0
108
and self.commands[0].string_args == ['new'])
109
has_split_affects = (
110
len(self._groups) == 2
111
and self._groups[0].commands[0].RANK != 0
112
and self._groups[1].commands[0].RANK == 0)
113
if is_new_bug and has_split_affects:
114
# The affects line was in the wrong position and this
115
# exact case can be fixed.
116
self._groups[0]._commands += self._groups[1]._commands
118
return list(self._groups)
120
def add(self, command_or_group):
121
"""Add an `EmailCommand` or `BugTaskCommandGroup` to the commands.
123
Empty BugTaskCommandGroup are ignored.
125
if isinstance(command_or_group, BugTaskCommandGroup):
127
self._groups.append(command_or_group)
129
super(BugCommandGroup, self).add(command_or_group)
132
class BugCommandGroups(BugCommandGroup):
134
def __init__(self, commands):
135
super(BugCommandGroups, self).__init__(command=None)
137
this_bug = BugCommandGroup()
138
this_bugtask = BugTaskCommandGroup()
139
for command in commands:
140
if IBugEmailCommand.providedBy(command) and command.RANK == 0:
141
# Multiple bugs are being edited.
142
this_bug.add(this_bugtask)
144
this_bug = BugCommandGroup(command)
145
this_bugtask = BugTaskCommandGroup()
146
elif IBugEditEmailCommand.providedBy(command):
147
this_bug.add(command)
148
elif (IBugTaskEmailCommand.providedBy(command)
149
and command.RANK == 0):
150
# Multiple or explicit bugtasks are being edited.
151
this_bug.add(this_bugtask)
152
this_bugtask = BugTaskCommandGroup(command)
153
elif IBugTaskEditEmailCommand.providedBy(command):
154
this_bugtask.add(command)
155
this_bug.add(this_bugtask)
159
for bug_group in self.groups:
160
for command in bug_group.commands:
162
for bugtask_group in bug_group.groups:
163
for command in bugtask_group.commands:
166
def add(self, command_or_group):
167
"""Add a `BugCommandGroup` to the groups of commands.
169
Empty BugCommandGroups are ignored.
171
if isinstance(command_or_group, BugCommandGroup):
173
self._groups.append(command_or_group)
176
54
class MaloneHandler:
210
88
commands = self.getCommands(signed_msg)
211
89
to_user, to_host = to_addr.split('@')
212
90
add_comment_to_bug = False
213
from_user = getUtility(ILaunchBag).user
214
if to_user.lower() == 'help' or from_user is None:
215
if from_user is not None and from_user.preferredemail is not None:
216
to_address = str(from_user.preferredemail.email)
218
to_address = signed_msg['From']
219
address = getUtility(IEmailAddressSet).getByEmail(to_address)
222
if to_address is not None:
223
self.sendHelpEmail(to_address)
224
return True, False, None
225
91
# If there are any commands, we must have strong authentication.
226
92
# We send a different failure message for attempts to create a new
228
elif to_user.lower() == 'new':
94
if to_user.lower() == 'new':
229
95
ensure_not_weakly_authenticated(signed_msg, CONTEXT,
230
'unauthenticated-bug-creation.txt',
231
error_templates=error_templates)
96
'unauthenticated-bug-creation.txt')
232
97
elif len(commands) > 0:
233
98
ensure_not_weakly_authenticated(signed_msg, CONTEXT)
234
99
if to_user.lower() == 'new':
242
107
add_comment_to_bug = True
243
108
commands.insert(0, BugEmailCommands.get('bug', [to_user]))
109
elif to_user.lower() == 'help':
110
from_user = getUtility(ILaunchBag).user
111
if from_user is not None:
112
preferredemail = from_user.preferredemail
113
if preferredemail is not None:
114
to_address = str(preferredemail.email)
115
self.sendHelpEmail(to_address)
116
return True, False, None
244
117
elif to_user.lower() != 'edit':
245
118
# Indicate that we didn't handle the mail.
246
119
return False, False, None
247
bug_commands = list(BugCommandGroups(commands))
248
return None, add_comment_to_bug, bug_commands
120
return None, add_comment_to_bug, commands
250
122
def process(self, signed_msg, to_addr, filealias=None, log=None):
251
123
"""See IMailHandler."""
267
139
command = commands.pop(0)
269
141
if IBugEmailCommand.providedBy(command):
270
# Finish outstanding work from the previous bug.
271
self.notify_bug_event(bug_event)
272
self.notify_bugtask_event(bugtask_event, bug_event)
142
if bug_event is not None:
145
except CreatedBugWithNoBugTasksError:
147
raise IncomingEmailError(
149
'no-affects-target-on-submit.txt'))
150
if (bugtask_event is not None and
151
not IObjectCreatedEvent.providedBy(bug_event)):
152
notify(bugtask_event)
274
154
bugtask_event = None
275
# Get or start building a new bug.
276
156
bug, bug_event = command.execute(
277
157
signed_msg, filealias)
278
158
if add_comment_to_bug:
279
message = self.appendBugComment(
280
bug, signed_msg, filealias)
159
messageset = getUtility(IMessageSet)
160
message = messageset.fromEmail(
161
signed_msg.as_string(),
162
owner=getUtility(ILaunchBag).user,
164
parsed_message=signed_msg,
165
fallback_parent=bug.initial_message)
167
# If the new message's parent is linked to
168
# a bug watch we also link this message to
170
bug_message_set = getUtility(IBugMessageSet)
171
parent_bug_message = (
172
bug_message_set.getByBugAndMessage(
173
bug, message.parent))
175
if (parent_bug_message is not None and
176
parent_bug_message.bugwatch):
177
bug_watch = parent_bug_message.bugwatch
181
bugmessage = bug.linkMessage(
184
notify(ObjectCreatedEvent(bugmessage))
281
185
add_comment_to_bug = False
282
self.processAttachments(bug, message, signed_msg)
187
message = bug.initial_message
188
self.processAttachments(bug, message, signed_msg)
283
189
elif IBugTaskEmailCommand.providedBy(command):
284
self.notify_bugtask_event(bugtask_event, bug_event)
285
bugtask, bugtask_event, bug_event = command.execute(
287
if isinstance(bug, CreateBugParams):
289
message = bug.initial_message
290
self.processAttachments(bug, message, signed_msg)
190
if bugtask_event is not None:
191
if not IObjectCreatedEvent.providedBy(bug_event):
192
notify(bugtask_event)
194
bugtask, bugtask_event = command.execute(bug)
291
195
elif IBugEditEmailCommand.providedBy(command):
292
196
bug, bug_event = command.execute(bug, bug_event)
293
197
elif IBugTaskEditEmailCommand.providedBy(command):
294
198
if bugtask is None:
295
if isinstance(bug, CreateBugParams):
296
self.handleNoAffectsTarget()
199
if len(bug.bugtasks) == 0:
201
raise IncomingEmailError(
203
'no-affects-target-on-submit.txt'))
297
204
bugtask = guess_bugtask(
298
205
bug, getUtility(ILaunchBag).user)
299
206
if bugtask is None:
300
self.handleNoDefaultAffectsTarget(bug)
207
raise IncomingEmailError(get_error_message(
208
'no-default-affects.txt',
210
nr_of_bugtasks=len(bug.bugtasks)))
301
211
bugtask, bugtask_event = command.execute(
302
212
bugtask, bugtask_event)
395
311
getUtility(IBugAttachmentSet).create(
396
312
bug=bug, filealias=blob, attach_type=attach_type,
397
313
title=blob.filename, message=message, send_notifications=True)
399
def appendBugComment(self, bug, signed_msg, filealias=None):
400
"""Append the message text to the bug comments."""
401
messageset = getUtility(IMessageSet)
402
message = messageset.fromEmail(
403
signed_msg.as_string(),
404
owner=getUtility(ILaunchBag).user,
406
parsed_message=signed_msg,
407
fallback_parent=bug.initial_message)
408
# If the new message's parent is linked to
409
# a bug watch we also link this message to
411
bug_message_set = getUtility(IBugMessageSet)
412
parent_bug_message = (
413
bug_message_set.getByBugAndMessage(bug, message.parent))
414
if (parent_bug_message is not None and
415
parent_bug_message.bugwatch):
416
bug_watch = parent_bug_message.bugwatch
419
bugmessage = bug.linkMessage(
421
notify(ObjectCreatedEvent(bugmessage))
424
def notify_bug_event(self, bug_event):
425
if bug_event is None:
429
except CreatedBugWithNoBugTasksError:
430
self.handleNoAffectsTarget()
432
def notify_bugtask_event(self, bugtask_event, bug_event):
433
if bugtask_event is None:
435
if not IObjectCreatedEvent.providedBy(bug_event):
436
notify(bugtask_event)
438
def handleNoAffectsTarget(self):
440
raise IncomingEmailError(
442
'no-affects-target-on-submit.txt',
443
error_templates=error_templates))
445
def handleNoDefaultAffectsTarget(self, bug):
446
raise IncomingEmailError(get_error_message(
447
'no-default-affects.txt',
448
error_templates=error_templates,
450
nr_of_bugtasks=len(bug.bugtasks)))