Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/metamta/replyhandler/PhabricatorMailReplyHandler.php
12256 views
1
<?php
2
3
abstract class PhabricatorMailReplyHandler extends Phobject {
4
5
private $mailReceiver;
6
private $applicationEmail;
7
private $actor;
8
private $excludePHIDs = array();
9
private $unexpandablePHIDs = array();
10
11
final public function setMailReceiver($mail_receiver) {
12
$this->validateMailReceiver($mail_receiver);
13
$this->mailReceiver = $mail_receiver;
14
return $this;
15
}
16
17
final public function getMailReceiver() {
18
return $this->mailReceiver;
19
}
20
21
public function setApplicationEmail(
22
PhabricatorMetaMTAApplicationEmail $email) {
23
$this->applicationEmail = $email;
24
return $this;
25
}
26
27
public function getApplicationEmail() {
28
return $this->applicationEmail;
29
}
30
31
final public function setActor(PhabricatorUser $actor) {
32
$this->actor = $actor;
33
return $this;
34
}
35
36
final public function getActor() {
37
return $this->actor;
38
}
39
40
final public function setExcludeMailRecipientPHIDs(array $exclude) {
41
$this->excludePHIDs = $exclude;
42
return $this;
43
}
44
45
final public function getExcludeMailRecipientPHIDs() {
46
return $this->excludePHIDs;
47
}
48
49
public function setUnexpandablePHIDs(array $phids) {
50
$this->unexpandablePHIDs = $phids;
51
return $this;
52
}
53
54
public function getUnexpandablePHIDs() {
55
return $this->unexpandablePHIDs;
56
}
57
58
abstract public function validateMailReceiver($mail_receiver);
59
abstract public function getPrivateReplyHandlerEmailAddress(
60
PhabricatorUser $user);
61
62
public function getReplyHandlerDomain() {
63
return PhabricatorEnv::getEnvConfig('metamta.reply-handler-domain');
64
}
65
66
abstract protected function receiveEmail(
67
PhabricatorMetaMTAReceivedMail $mail);
68
69
public function processEmail(PhabricatorMetaMTAReceivedMail $mail) {
70
return $this->receiveEmail($mail);
71
}
72
73
public function supportsPrivateReplies() {
74
return (bool)$this->getReplyHandlerDomain() &&
75
!$this->supportsPublicReplies();
76
}
77
78
public function supportsPublicReplies() {
79
if (!PhabricatorEnv::getEnvConfig('metamta.public-replies')) {
80
return false;
81
}
82
83
if (!$this->getReplyHandlerDomain()) {
84
return false;
85
}
86
87
return (bool)$this->getPublicReplyHandlerEmailAddress();
88
}
89
90
final public function supportsReplies() {
91
return $this->supportsPrivateReplies() ||
92
$this->supportsPublicReplies();
93
}
94
95
public function getPublicReplyHandlerEmailAddress() {
96
return null;
97
}
98
99
protected function getDefaultPublicReplyHandlerEmailAddress($prefix) {
100
101
$receiver = $this->getMailReceiver();
102
$receiver_id = $receiver->getID();
103
$domain = $this->getReplyHandlerDomain();
104
105
// We compute a hash using the object's own PHID to prevent an attacker
106
// from blindly interacting with objects that they haven't ever received
107
// mail about by just sending to D1@, D2@, etc...
108
109
$mail_key = PhabricatorMetaMTAMailProperties::loadMailKey($receiver);
110
111
$hash = PhabricatorObjectMailReceiver::computeMailHash(
112
$mail_key,
113
$receiver->getPHID());
114
115
$address = "{$prefix}{$receiver_id}+public+{$hash}@{$domain}";
116
return $this->getSingleReplyHandlerPrefix($address);
117
}
118
119
protected function getSingleReplyHandlerPrefix($address) {
120
$single_handle_prefix = PhabricatorEnv::getEnvConfig(
121
'metamta.single-reply-handler-prefix');
122
return ($single_handle_prefix)
123
? $single_handle_prefix.'+'.$address
124
: $address;
125
}
126
127
protected function getDefaultPrivateReplyHandlerEmailAddress(
128
PhabricatorUser $user,
129
$prefix) {
130
131
$receiver = $this->getMailReceiver();
132
$receiver_id = $receiver->getID();
133
$user_id = $user->getID();
134
135
$mail_key = PhabricatorMetaMTAMailProperties::loadMailKey($receiver);
136
137
$hash = PhabricatorObjectMailReceiver::computeMailHash(
138
$mail_key,
139
$user->getPHID());
140
$domain = $this->getReplyHandlerDomain();
141
142
$address = "{$prefix}{$receiver_id}+{$user_id}+{$hash}@{$domain}";
143
return $this->getSingleReplyHandlerPrefix($address);
144
}
145
146
final protected function enhanceBodyWithAttachments(
147
$body,
148
array $attachments) {
149
150
if (!$attachments) {
151
return $body;
152
}
153
154
$files = id(new PhabricatorFileQuery())
155
->setViewer($this->getActor())
156
->withPHIDs($attachments)
157
->execute();
158
159
$output = array();
160
$output[] = $body;
161
162
// We're going to put all the non-images first in a list, then embed
163
// the images.
164
$head = array();
165
$tail = array();
166
foreach ($files as $file) {
167
if ($file->isViewableImage()) {
168
$tail[] = $file;
169
} else {
170
$head[] = $file;
171
}
172
}
173
174
if ($head) {
175
$list = array();
176
foreach ($head as $file) {
177
$list[] = ' - {'.$file->getMonogram().', layout=link}';
178
}
179
$output[] = implode("\n", $list);
180
}
181
182
if ($tail) {
183
$list = array();
184
foreach ($tail as $file) {
185
$list[] = '{'.$file->getMonogram().'}';
186
}
187
$output[] = implode("\n\n", $list);
188
}
189
190
$output = implode("\n\n", $output);
191
192
return rtrim($output);
193
}
194
195
196
/**
197
* Produce a list of mail targets for a given to/cc list.
198
*
199
* Each target should be sent a separate email, and contains the information
200
* required to generate it with appropriate permissions and configuration.
201
*
202
* @param list<phid> List of "To" PHIDs.
203
* @param list<phid> List of "CC" PHIDs.
204
* @return list<PhabricatorMailTarget> List of targets.
205
*/
206
final public function getMailTargets(array $raw_to, array $raw_cc) {
207
list($to, $cc) = $this->expandRecipientPHIDs($raw_to, $raw_cc);
208
list($to, $cc) = $this->loadRecipientUsers($to, $cc);
209
list($to, $cc) = $this->filterRecipientUsers($to, $cc);
210
211
if (!$to && !$cc) {
212
return array();
213
}
214
215
$template = id(new PhabricatorMailTarget())
216
->setRawToPHIDs($raw_to)
217
->setRawCCPHIDs($raw_cc);
218
219
// Set the public reply address as the default, if one exists. We
220
// might replace this with a private address later.
221
if ($this->supportsPublicReplies()) {
222
$reply_to = $this->getPublicReplyHandlerEmailAddress();
223
if ($reply_to) {
224
$template->setReplyTo($reply_to);
225
}
226
}
227
228
$supports_private_replies = $this->supportsPrivateReplies();
229
$mail_all = !PhabricatorEnv::getEnvConfig('metamta.one-mail-per-recipient');
230
$targets = array();
231
if ($mail_all) {
232
$target = id(clone $template)
233
->setViewer(PhabricatorUser::getOmnipotentUser())
234
->setToMap($to)
235
->setCCMap($cc);
236
237
$targets[] = $target;
238
} else {
239
$map = $to + $cc;
240
241
foreach ($map as $phid => $user) {
242
// Preserve the original To/Cc information on the target.
243
if (isset($to[$phid])) {
244
$target_to = array($phid => $user);
245
$target_cc = array();
246
} else {
247
$target_to = array();
248
$target_cc = array($phid => $user);
249
}
250
251
$target = id(clone $template)
252
->setViewer($user)
253
->setToMap($target_to)
254
->setCCMap($target_cc);
255
256
if ($supports_private_replies) {
257
$reply_to = $this->getPrivateReplyHandlerEmailAddress($user);
258
if ($reply_to) {
259
$target->setReplyTo($reply_to);
260
}
261
}
262
263
$targets[] = $target;
264
}
265
}
266
267
return $targets;
268
}
269
270
271
/**
272
* Expand lists of recipient PHIDs.
273
*
274
* This takes any compound recipients (like projects) and looks up all their
275
* members.
276
*
277
* @param list<phid> List of To PHIDs.
278
* @param list<phid> List of CC PHIDs.
279
* @return pair<list<phid>, list<phid>> Expanded PHID lists.
280
*/
281
private function expandRecipientPHIDs(array $to, array $cc) {
282
$to_result = array();
283
$cc_result = array();
284
285
// "Unexpandable" users have disengaged from an object (for example,
286
// by resigning from a revision).
287
288
// If such a user is still a direct recipient (for example, they're still
289
// on the Subscribers list) they're fair game, but group targets (like
290
// projects) will no longer include them when expanded.
291
292
$unexpandable = $this->getUnexpandablePHIDs();
293
$unexpandable = array_fuse($unexpandable);
294
295
$all_phids = array_merge($to, $cc);
296
if ($all_phids) {
297
$map = id(new PhabricatorMetaMTAMemberQuery())
298
->setViewer(PhabricatorUser::getOmnipotentUser())
299
->withPHIDs($all_phids)
300
->execute();
301
foreach ($to as $phid) {
302
foreach ($map[$phid] as $expanded) {
303
if ($expanded !== $phid) {
304
if (isset($unexpandable[$expanded])) {
305
continue;
306
}
307
}
308
$to_result[$expanded] = $expanded;
309
}
310
}
311
foreach ($cc as $phid) {
312
foreach ($map[$phid] as $expanded) {
313
if ($expanded !== $phid) {
314
if (isset($unexpandable[$expanded])) {
315
continue;
316
}
317
}
318
$cc_result[$expanded] = $expanded;
319
}
320
}
321
}
322
323
// Remove recipients from "CC" if they're also present in "To".
324
$cc_result = array_diff_key($cc_result, $to_result);
325
326
return array(array_values($to_result), array_values($cc_result));
327
}
328
329
330
/**
331
* Load @{class:PhabricatorUser} objects for each recipient.
332
*
333
* Invalid recipients are dropped from the results.
334
*
335
* @param list<phid> List of To PHIDs.
336
* @param list<phid> List of CC PHIDs.
337
* @return pair<wild, wild> Maps from PHIDs to users.
338
*/
339
private function loadRecipientUsers(array $to, array $cc) {
340
$to_result = array();
341
$cc_result = array();
342
343
$all_phids = array_merge($to, $cc);
344
if ($all_phids) {
345
// We need user settings here because we'll check translations later
346
// when generating mail.
347
$users = id(new PhabricatorPeopleQuery())
348
->setViewer(PhabricatorUser::getOmnipotentUser())
349
->withPHIDs($all_phids)
350
->needUserSettings(true)
351
->execute();
352
$users = mpull($users, null, 'getPHID');
353
354
foreach ($to as $phid) {
355
if (isset($users[$phid])) {
356
$to_result[$phid] = $users[$phid];
357
}
358
}
359
foreach ($cc as $phid) {
360
if (isset($users[$phid])) {
361
$cc_result[$phid] = $users[$phid];
362
}
363
}
364
}
365
366
return array($to_result, $cc_result);
367
}
368
369
370
/**
371
* Remove recipients who do not have permission to view the mail receiver.
372
*
373
* @param map<string, PhabricatorUser> Map of "To" users.
374
* @param map<string, PhabricatorUser> Map of "CC" users.
375
* @return pair<wild, wild> Filtered user maps.
376
*/
377
private function filterRecipientUsers(array $to, array $cc) {
378
$to_result = array();
379
$cc_result = array();
380
381
$all_users = $to + $cc;
382
if ($all_users) {
383
$can_see = array();
384
$object = $this->getMailReceiver();
385
foreach ($all_users as $phid => $user) {
386
$visible = PhabricatorPolicyFilter::hasCapability(
387
$user,
388
$object,
389
PhabricatorPolicyCapability::CAN_VIEW);
390
if ($visible) {
391
$can_see[$phid] = true;
392
}
393
}
394
395
foreach ($to as $phid => $user) {
396
if (!empty($can_see[$phid])) {
397
$to_result[$phid] = $all_users[$phid];
398
}
399
}
400
401
foreach ($cc as $phid => $user) {
402
if (!empty($can_see[$phid])) {
403
$cc_result[$phid] = $all_users[$phid];
404
}
405
}
406
}
407
408
return array($to_result, $cc_result);
409
}
410
411
}
412
413