@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Allow mailers to be explicitly marked as inbound or outbound

Summary:
See PHI785. Ref T13164. In this case, an install wants to receive mail via Mailgun, but not configure it (DKIM + SPF) for outbound mail.

Allow individual mailers to be marked as not supporting inbound or outbound mail.

Test Plan:
- Added and ran unit tests.
- Went through some mail pathways locally, but I don't have every inbound/outbound configured so this isn't totally conclusive.
- Hit `bin/mail send-test` with a no-outbound mailer.
- I'll hold this until after the release cut so it can soak on `secure` for a bit.

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13164

Differential Revision: https://secure.phabricator.com/D19546

+185 -25
+21
src/applications/metamta/adapter/PhabricatorMailImplementationAdapter.php
··· 6 6 private $priority; 7 7 private $options = array(); 8 8 9 + private $supportsInbound = true; 10 + private $supportsOutbound = true; 11 + 9 12 final public function getAdapterType() { 10 13 return $this->getPhobjectClassConstant('ADAPTERTYPE'); 11 14 } ··· 65 68 66 69 final public function getPriority() { 67 70 return $this->priority; 71 + } 72 + 73 + final public function setSupportsInbound($supports_inbound) { 74 + $this->supportsInbound = $supports_inbound; 75 + return $this; 76 + } 77 + 78 + final public function getSupportsInbound() { 79 + return $this->supportsInbound; 80 + } 81 + 82 + final public function setSupportsOutbound($supports_outbound) { 83 + $this->supportsOutbound = $supports_outbound; 84 + return $this; 85 + } 86 + 87 + final public function getSupportsOutbound() { 88 + return $this->supportsOutbound; 68 89 } 69 90 70 91 final public function getOption($key) {
+5 -2
src/applications/metamta/controller/PhabricatorMetaMTAMailgunReceiveController.php
··· 17 17 // inbound mail from any of them. Test the signature to see if it matches 18 18 // any configured Mailgun mailer. 19 19 20 - $mailers = PhabricatorMetaMTAMail::newMailersWithTypes( 20 + $mailers = PhabricatorMetaMTAMail::newMailers( 21 21 array( 22 - PhabricatorMailImplementationMailgunAdapter::ADAPTERTYPE, 22 + 'inbound' => true, 23 + 'types' => array( 24 + PhabricatorMailImplementationMailgunAdapter::ADAPTERTYPE, 25 + ), 23 26 )); 24 27 foreach ($mailers as $mailer) { 25 28 $api_key = $mailer->getOption('api-key');
+5 -2
src/applications/metamta/controller/PhabricatorMetaMTAPostmarkReceiveController.php
··· 12 12 */ 13 13 public function handleRequest(AphrontRequest $request) { 14 14 // Don't process requests if we don't have a configured Postmark adapter. 15 - $mailers = PhabricatorMetaMTAMail::newMailersWithTypes( 15 + $mailers = PhabricatorMetaMTAMail::newMailers( 16 16 array( 17 - PhabricatorMailImplementationPostmarkAdapter::ADAPTERTYPE, 17 + 'inbound' => true, 18 + 'types' => array( 19 + PhabricatorMailImplementationPostmarkAdapter::ADAPTERTYPE, 20 + ), 18 21 )); 19 22 if (!$mailers) { 20 23 return new Aphront404Response();
+5 -2
src/applications/metamta/controller/PhabricatorMetaMTASendGridReceiveController.php
··· 11 11 // SendGrid doesn't sign payloads so we can't be sure that SendGrid 12 12 // actually sent this request, but require a configured SendGrid mailer 13 13 // before we activate this endpoint. 14 - $mailers = PhabricatorMetaMTAMail::newMailersWithTypes( 14 + $mailers = PhabricatorMetaMTAMail::newMailers( 15 15 array( 16 - PhabricatorMailImplementationSendGridAdapter::ADAPTERTYPE, 16 + 'inbound' => true, 17 + 'types' => array( 18 + PhabricatorMailImplementationSendGridAdapter::ADAPTERTYPE, 19 + ), 17 20 )); 18 21 if (!$mailers) { 19 22 return new Aphront404Response();
+9 -1
src/applications/metamta/management/PhabricatorMailManagementSendTestWorkflow.php
··· 168 168 169 169 $mailer_key = $args->getArg('mailer'); 170 170 if ($mailer_key !== null) { 171 - $mailers = PhabricatorMetaMTAMail::newMailers(); 171 + $mailers = PhabricatorMetaMTAMail::newMailers(array()); 172 + 172 173 $mailers = mpull($mailers, null, 'getKey'); 173 174 if (!isset($mailers[$mailer_key])) { 174 175 throw new PhutilArgumentUsageException( ··· 176 177 'Mailer key ("%s") is not configured. Available keys are: %s.', 177 178 $mailer_key, 178 179 implode(', ', array_keys($mailers)))); 180 + } 181 + 182 + if (!$mailers[$mailer_key]->getSupportsOutbound()) { 183 + throw new PhutilArgumentUsageException( 184 + pht( 185 + 'Mailer ("%s") is not configured to support outbound mail.', 186 + $mailer_key)); 179 187 } 180 188 181 189 $mail->setTryMailers(array($mailer_key));
+63 -16
src/applications/metamta/storage/PhabricatorMetaMTAMail.php
··· 494 494 throw new Exception(pht('Trying to send an already-sent mail!')); 495 495 } 496 496 497 - $mailers = self::newMailers(); 497 + $mailers = self::newMailers( 498 + array( 499 + 'outbound' => true, 500 + )); 498 501 499 502 $try_mailers = $this->getParam('mailers.try'); 500 503 if ($try_mailers) { ··· 505 508 return $this->sendWithMailers($mailers); 506 509 } 507 510 508 - public static function newMailersWithTypes(array $types) { 509 - $mailers = self::newMailers(); 510 - $types = array_fuse($types); 511 + public static function newMailers(array $constraints) { 512 + PhutilTypeSpec::checkMap( 513 + $constraints, 514 + array( 515 + 'types' => 'optional list<string>', 516 + 'inbound' => 'optional bool', 517 + 'outbound' => 'optional bool', 518 + )); 511 519 512 - foreach ($mailers as $key => $mailer) { 513 - $mailer_type = $mailer->getAdapterType(); 514 - if (!isset($types[$mailer_type])) { 515 - unset($mailers[$key]); 516 - } 517 - } 518 - 519 - return array_values($mailers); 520 - } 521 - 522 - public static function newMailers() { 523 520 $mailers = array(); 524 521 525 522 $config = PhabricatorEnv::getEnvConfig('cluster.mailers'); ··· 565 562 $options = idx($spec, 'options', array()) + $defaults; 566 563 $mailer->setOptions($options); 567 564 565 + $mailer->setSupportsInbound(idx($spec, 'inbound', true)); 566 + $mailer->setSupportsOutbound(idx($spec, 'outbound', true)); 567 + 568 568 $mailers[] = $mailer; 569 569 } 570 570 } 571 571 572 + // Remove mailers with the wrong types. 573 + if (isset($constraints['types'])) { 574 + $types = $constraints['types']; 575 + $types = array_fuse($types); 576 + foreach ($mailers as $key => $mailer) { 577 + $mailer_type = $mailer->getAdapterType(); 578 + if (!isset($types[$mailer_type])) { 579 + unset($mailers[$key]); 580 + } 581 + } 582 + } 583 + 584 + // If we're only looking for inbound mailers, remove mailers with inbound 585 + // support disabled. 586 + if (!empty($constraints['inbound'])) { 587 + foreach ($mailers as $key => $mailer) { 588 + if (!$mailer->getSupportsInbound()) { 589 + unset($mailers[$key]); 590 + } 591 + } 592 + } 593 + 594 + // If we're only looking for outbound mailers, remove mailers with outbound 595 + // support disabled. 596 + if (!empty($constraints['outbound'])) { 597 + foreach ($mailers as $key => $mailer) { 598 + if (!$mailer->getSupportsOutbound()) { 599 + unset($mailers[$key]); 600 + } 601 + } 602 + } 603 + 572 604 $sorted = array(); 573 605 $groups = mgroup($mailers, 'getPriority'); 574 606 krsort($groups); ··· 589 621 590 622 public function sendWithMailers(array $mailers) { 591 623 if (!$mailers) { 624 + $any_mailers = self::newMailers(); 625 + 626 + // NOTE: We can end up here with some custom list of "$mailers", like 627 + // from a unit test. In that case, this message could be misleading. We 628 + // can't really tell if the caller made up the list, so just assume they 629 + // aren't tricking us. 630 + 631 + if ($any_mailers) { 632 + $void_message = pht( 633 + 'No configured mailers support sending outbound mail.'); 634 + } else { 635 + $void_message = pht( 636 + 'No mailers are configured.'); 637 + } 638 + 592 639 return $this 593 640 ->setStatus(PhabricatorMailOutboundStatus::STATUS_VOID) 594 - ->setMessage(pht('No mailers are configured.')) 641 + ->setMessage($void_message) 595 642 ->save(); 596 643 } 597 644
+71 -2
src/applications/metamta/storage/__tests__/PhabricatorMailConfigTestCase.php
··· 118 118 $this->assertTrue($saw_a1 && $saw_a2); 119 119 } 120 120 121 - private function newMailersWithConfig(array $config) { 121 + public function testMailerConstraints() { 122 + $config = array( 123 + array( 124 + 'key' => 'X1', 125 + 'type' => 'test', 126 + ), 127 + array( 128 + 'key' => 'X1-in', 129 + 'type' => 'test', 130 + 'outbound' => false, 131 + ), 132 + array( 133 + 'key' => 'X1-out', 134 + 'type' => 'test', 135 + 'inbound' => false, 136 + ), 137 + array( 138 + 'key' => 'X1-void', 139 + 'type' => 'test', 140 + 'inbound' => false, 141 + 'outbound' => false, 142 + ), 143 + ); 144 + 145 + $mailers = $this->newMailersWithConfig( 146 + $config, 147 + array()); 148 + $this->assertEqual(4, count($mailers)); 149 + 150 + $mailers = $this->newMailersWithConfig( 151 + $config, 152 + array( 153 + 'inbound' => true, 154 + )); 155 + $this->assertEqual(2, count($mailers)); 156 + 157 + $mailers = $this->newMailersWithConfig( 158 + $config, 159 + array( 160 + 'outbound' => true, 161 + )); 162 + $this->assertEqual(2, count($mailers)); 163 + 164 + $mailers = $this->newMailersWithConfig( 165 + $config, 166 + array( 167 + 'inbound' => true, 168 + 'outbound' => true, 169 + )); 170 + $this->assertEqual(1, count($mailers)); 171 + 172 + $mailers = $this->newMailersWithConfig( 173 + $config, 174 + array( 175 + 'types' => array('test'), 176 + )); 177 + $this->assertEqual(4, count($mailers)); 178 + 179 + $mailers = $this->newMailersWithConfig( 180 + $config, 181 + array( 182 + 'types' => array('duck'), 183 + )); 184 + $this->assertEqual(0, count($mailers)); 185 + } 186 + 187 + private function newMailersWithConfig( 188 + array $config, 189 + array $constraints = array()) { 190 + 122 191 $env = PhabricatorEnv::beginScopedEnv(); 123 192 $env->overrideEnvConfig('cluster.mailers', $config); 124 193 125 - $mailers = PhabricatorMetaMTAMail::newMailers(); 194 + $mailers = PhabricatorMetaMTAMail::newMailers($constraints); 126 195 127 196 unset($env); 128 197 return $mailers;
+4
src/docs/user/configuration/configuring_outbound_email.diviner
··· 82 82 - `priority`: Optional string. Advanced option which controls load balancing 83 83 and failover behavior. See below for details. 84 84 - `options`: Optional map. Additional options for the mailer type. 85 + - `inbound`: Optional bool. Use `false` to prevent this mailer from being 86 + used to receive inbound mail. 87 + - `outbound`: Optional bool. Use `false` to prevent this mailer from being 88 + used to send outbound mail. 85 89 86 90 The `type` field can be used to select these third-party mailers: 87 91
+2
src/infrastructure/cluster/config/PhabricatorClusterMailersConfigType.php
··· 43 43 'type' => 'string', 44 44 'priority' => 'optional int', 45 45 'options' => 'optional wild', 46 + 'inbound' => 'optional bool', 47 + 'outbound' => 'optional bool', 46 48 )); 47 49 } catch (Exception $ex) { 48 50 throw $this->newException(