diff --git a/phpBB/phpbb/messenger/method/email.php b/phpBB/phpbb/messenger/method/email.php index 6fb3e20e68..3b580b6a69 100644 --- a/phpBB/phpbb/messenger/method/email.php +++ b/phpBB/phpbb/messenger/method/email.php @@ -13,6 +13,8 @@ namespace phpbb\messenger\method; +use Symfony\Component\Mailer\Exception\TransportExceptionInterface; +use Symfony\Component\Mailer\MailerInterface; use Symfony\Component\Mailer\Transport; use Symfony\Component\Mailer\Mailer; use Symfony\Component\Mailer\Transport\AbstractTransport; @@ -275,7 +277,6 @@ class email extends base */ protected function build_headers(): void { - $board_contact = trim($this->config['board_contact']); $contact_name = html_entity_decode($this->config['board_contact_name'], ENT_COMPAT); @@ -317,7 +318,6 @@ class email extends base { $this->header($header, $value); } - } /** @@ -408,7 +408,7 @@ class email extends base $package_size = $queue_data[$queue_object_name]['package_size'] ?? 0; $num_items = (!$package_size || $messages_count < $package_size) ? $messages_count : $package_size; - $mailer = new Mailer($this->transport); + $mailer = $this->get_mailer(); for ($i = 0; $i < $num_items; $i++) { @@ -437,7 +437,7 @@ class email extends base { $mailer->send($email); } - catch (\Symfony\Component\Mailer\Exception\TransportExceptionInterface $e) + catch (TransportExceptionInterface $e) { $this->error($e->getDebug()); continue; @@ -452,6 +452,16 @@ class email extends base } } + /** + * Get mailer object + * + * @return MailerInterface Symfony Mailer object + */ + protected function get_mailer(): MailerInterface + { + return new Mailer($this->transport); + } + /** * Get mailer transport object * @@ -506,7 +516,7 @@ class email extends base // Send message ... if (!$this->use_queue) { - $mailer = new Mailer($this->transport); + $mailer = $this->get_mailer(); $subject = $this->subject; $msg = $this->msg; @@ -540,7 +550,7 @@ class email extends base { $mailer->send($this->email); } - catch (\Symfony\Component\Mailer\Exception\TransportExceptionInterface $e) + catch (TransportExceptionInterface $e) { $this->error($e->getDebug()); return false; diff --git a/tests/messenger/method_email_test.php b/tests/messenger/method_email_test.php index a1d8256070..e0dfae93b5 100644 --- a/tests/messenger/method_email_test.php +++ b/tests/messenger/method_email_test.php @@ -19,45 +19,65 @@ use phpbb\messenger\queue; use phpbb\path_helper; use phpbb\symfony_request; use phpbb\template\assets_bag; +use Symfony\Component\Mime\RawMessage; class phpbb_messenger_method_email_test extends \phpbb_test_case { + protected $assets_bag; + protected $cache_path; protected config $config; + protected $dispatcher; + protected $extension_manager; + protected $language; + protected $log; + protected $path_helper; protected queue $queue; protected $request; + protected $twig_extensions_collection; + protected $twig_lexer; + protected $user; public function setUp(): void { - global $phpbb_root_path, $phpEx; + global $config, $request, $symfony_request, $user, $phpbb_root_path, $phpEx; - $assets_bag = new assets_bag(); - $cache_path = $phpbb_root_path . 'cache/' . PHPBB_ENVIRONMENT . '/twig'; - $this->config = new config([]); - $dispatcher = new \phpbb_mock_event_dispatcher(); - $filesystem = new \phpbb\filesystem\filesystem(); - $language = new language(new language_file_loader($phpbb_root_path, $phpEx)); + $this->assets_bag = new assets_bag(); + $this->cache_path = $phpbb_root_path . 'cache/' . PHPBB_ENVIRONMENT . '/twig'; + $this->config = new config([ + 'force_server_vars' => false, + ]); + $config = $this->config; + $this->dispatcher = $this->getMockBuilder('\phpbb\event\dispatcher') + ->disableOriginalConstructor() + ->getMock(); + $this->filesystem = new \phpbb\filesystem\filesystem(); + $this->language = new language(new language_file_loader($phpbb_root_path, $phpEx)); $this->queue = $this->createMock(queue::class); $this->request = new phpbb_mock_request(); - $user = new \phpbb\user($language, '\phpbb\datetime'); - $path_helper = new path_helper( - new symfony_request( - new phpbb_mock_request() - ), + $request = $this->request; + $this->symfony_request = new symfony_request(new phpbb_mock_request()); + $symfony_request = $this->symfony_request; + $this->user = new \phpbb\user($this->language, '\phpbb\datetime'); + $user = $this->user; + $user->page['root_script_path'] = 'phpbb/'; + $this->user->host = 'yourdomain.com'; + $this->path_helper = new path_helper( + $this->symfony_request, $this->request, $phpbb_root_path, $phpEx ); $phpbb_container = new phpbb_mock_container_builder; - $twig_extensions_collection = new \phpbb\di\service_collection($phpbb_container); + $this->twig_extensions_collection = new \phpbb\di\service_collection($phpbb_container); $twig = new \phpbb\template\twig\environment( - $assets_bag, + $this->assets_bag, $this->config, - $filesystem, - $path_helper, - $cache_path, + $this->filesystem, + $this->path_helper, + $this->cache_path, null, new \phpbb\template\twig\loader(''), - $dispatcher, + $this->dispatcher, array( 'cache' => false, 'debug' => false, @@ -65,8 +85,8 @@ class phpbb_messenger_method_email_test extends \phpbb_test_case 'autoescape' => false, ) ); - $twig_lexer = new \phpbb\template\twig\lexer($twig); - $extension_manager = new phpbb_mock_extension_manager( + $this->twig_lexer = new \phpbb\template\twig\lexer($twig); + $this->extension_manager = new phpbb_mock_extension_manager( __DIR__ . '/', array( 'vendor2/foo' => array( @@ -76,23 +96,23 @@ class phpbb_messenger_method_email_test extends \phpbb_test_case ), ) ); - $log = $this->createMock(\phpbb\log\log_interface::class); + $this->log = $this->createMock(\phpbb\log\log_interface::class); $this->method_email = new email( - $assets_bag, + $this->assets_bag, $this->config, - $dispatcher, - $language, + $this->dispatcher, + $this->language, $this->queue, - $path_helper, + $this->path_helper, $this->request, - $twig_extensions_collection, - $twig_lexer, - $user, + $this->twig_extensions_collection, + $this->twig_lexer, + $this->user, $phpbb_root_path, - $cache_path, - $extension_manager, - $log + $this->cache_path, + $this->extension_manager, + $this->log ); } @@ -200,6 +220,17 @@ class phpbb_messenger_method_email_test extends \phpbb_test_case $this->assertEmpty($email->getTo()); } + public function test_get_mailer() + { + $email_reflection = new \ReflectionClass($this->method_email); + $this->method_email->init(); + $this->method_email->set_transport(); + $mailer_method = $email_reflection->getMethod('get_mailer'); + + $mailer = $mailer_method->invoke($this->method_email); + $this->assertInstanceOf(\Symfony\Component\Mailer\Mailer::class, $mailer); + } + public function test_set_addresses() { $email_reflection = new \ReflectionClass($this->method_email); @@ -384,4 +415,375 @@ class phpbb_messenger_method_email_test extends \phpbb_test_case $this->assertNotEmpty($email->getSubject()); $this->assertEquals('Reply to test email', $email->getSubject()); } + + public function test_anti_abuse_headers() + { + $email_reflection = new \ReflectionClass($this->method_email); + $headers_property = $email_reflection->getProperty('headers'); + $this->method_email->init(); + + /** @var \Symfony\Component\Mime\Header\Headers $headers */ + $headers = $headers_property->getValue($this->method_email); + + $this->config->set('server_name', 'yourdomain.com'); + $this->user->data['user_id'] = 2; + $this->user->data['username'] = 'admin'; + $this->user->ip = '127.0.0.1'; + + $this->assertEmpty($headers->toArray()); + $this->method_email->anti_abuse_headers($this->config, $this->user); + + $this->assertEquals( + [ + 'X-AntiAbuse: Board servername - yourdomain.com', + 'X-AntiAbuse: User_id - 2', + 'X-AntiAbuse: Username - admin', + 'X-AntiAbuse: User IP - 127.0.0.1', + ], + $headers->toArray() + ); + } + + public function test_set_mail_priority() + { + $email_reflection = new \ReflectionClass($this->method_email); + $email_property = $email_reflection->getProperty('email'); + $this->method_email->init(); + /** @var \Symfony\Component\Mime\Email $email */ + $email = $email_property->getValue($this->method_email); + $this->assertNotNull($email); + + // Default priority + $this->assertEquals(\Symfony\Component\Mime\Email::PRIORITY_NORMAL, $email->getPriority()); + + // Highest priority + $this->method_email->set_mail_priority(\Symfony\Component\Mime\Email::PRIORITY_HIGHEST); + $this->assertEquals(\Symfony\Component\Mime\Email::PRIORITY_HIGHEST, $email->getPriority()); + } + + public function test_process_queue_not_enabled() + { + $this->method_email->init(); + + $queue_data = [ + 'email' => [ + 'data' => [ + 'message_one', + 'message_two', + ] + ] + ]; + + // Process queue will remove emails if email method is not enabled + $this->method_email->process_queue($queue_data); + $this->assertEmpty($queue_data); + } + + public function test_process_queue() + { + global $phpbb_root_path; + + $this->config->set('email_enable', true); + $this->user->data['user_id'] = 2; + $this->user->session_id = 'abcdef'; + + $this->method_email = $this->getMockBuilder($this->method_email::class) + ->setConstructorArgs([$this->assets_bag, + $this->config, + $this->dispatcher, + $this->language, + $this->queue, + $this->path_helper, + $this->request, + $this->twig_extensions_collection, + $this->twig_lexer, + $this->user, + $phpbb_root_path, + $this->cache_path, + $this->extension_manager, + $this->log + ]) + ->onlyMethods(['get_mailer']) + ->getMock(); + + $mailer_mock = $this->getMockBuilder(\Symfony\Component\Mailer\MailerInterface::class) + ->disableOriginalConstructor() + ->onlyMethods(['send']) + ->getMock(); + $sent_emails = 0; + $mailer_mock->method('send') + ->willReturnCallback(function(RawMessage $mail) use(&$sent_emails) { + if ($mail->toString() === 'throw_exception') + { + throw new \Symfony\Component\Mailer\Exception\TransportException('exception'); + } + + $sent_emails++; + }); + $this->method_email->method('get_mailer')->willReturn($mailer_mock); + + $this->method_email->init(); + $errors = []; + $this->log->method('add') + ->willReturnCallback(function($mode, $user_id, $log_ip, $log_operation, $log_time = false, $additional_data = []) use (&$errors) { + $errors[] = $additional_data[0]; + }); + + $this->dispatcher + ->expects($this->atLeastOnce()) + ->method('trigger_event') + ->willReturnCallback(function($event_name, $value_array) { + if ($event_name === 'core.notification_message_process' && $value_array['email']->toString() == 'message_three') + { + $value_array['break'] = true; + } + + return $value_array; + }); + + $queue_data = [ + 'email' => [ + 'data' => [ + ['email' => new RawMessage('message_one')], + ['email' => new RawMessage('message_two')], + ['email' => new RawMessage('message_three')], + ['email' => new RawMessage('throw_exception')], + ['email' => new RawMessage('message_four')], + ] + ] + ]; + + $this->assertEmpty($errors); + $this->method_email->process_queue($queue_data); + $this->assertEmpty($queue_data); + $this->assertEquals(3, $sent_emails); + + $this->assertEquals(['EMAIL



'], $errors); + } + + public function test_send_break() + { + $this->dispatcher + ->expects($this->atLeastOnce()) + ->method('trigger_event') + ->willReturnCallback(function($event_name, $value_array) { + if ($event_name !== 'core.notification_message_email') + { + return $value_array; + } + + $value_array['break'] = true; + return $value_array; + }); + + $this->config->set('board_email', 'admin@yourdomain.com'); + + $this->method_email->init(); + $this->method_email->to('foo@bar.com'); + $this->method_email->subject('Test email'); + $this->method_email->template('test', 'en'); + + $this->method_email->send(); + } + + public function test_send_no_queue() + { + global $phpbb_root_path; + + $this->config->set('email_enable', true); + $this->user->data['user_id'] = 2; + $this->user->session_id = 'abcdef'; + + $this->method_email = $this->getMockBuilder($this->method_email::class) + ->setConstructorArgs([$this->assets_bag, + $this->config, + $this->dispatcher, + $this->language, + $this->queue, + $this->path_helper, + $this->request, + $this->twig_extensions_collection, + $this->twig_lexer, + $this->user, + $phpbb_root_path, + $this->cache_path, + $this->extension_manager, + $this->log + ]) + ->onlyMethods(['get_mailer']) + ->getMock(); + + $mailer_mock = $this->getMockBuilder(\Symfony\Component\Mailer\MailerInterface::class) + ->disableOriginalConstructor() + ->onlyMethods(['send']) + ->getMock(); + $sent_emails = 0; + $mailer_mock->method('send') + ->willReturnCallback(function(RawMessage $mail) use(&$sent_emails) { + $sent_emails++; + }); + $this->method_email->method('get_mailer')->willReturn($mailer_mock); + + $this->config->set('board_email', 'admin@yourdomain.com'); + + $this->method_email->init(); + $errors = []; + $this->log->method('add') + ->willReturnCallback(function($mode, $user_id, $log_ip, $log_operation, $log_time = false, $additional_data = []) use (&$errors) { + $errors[] = $additional_data[0]; + }); + + $this->dispatcher + ->expects($this->atLeastOnce()) + ->method('trigger_event') + ->willReturnCallback(function($event_name, $value_array) { + return $value_array; + }); + + $this->method_email->to('foo@bar.com'); + $this->method_email->subject('Test email'); + $this->method_email->template('test', 'en'); + + $this->assertTrue($this->method_email->send()); + $this->assertEquals(1, $sent_emails); + + $this->assertEmpty($errors); + } + + public function test_send_exception() + { + global $phpbb_root_path; + + $this->config->set('email_enable', true); + $this->user->data['user_id'] = 2; + $this->user->session_id = 'abcdef'; + + $this->method_email = $this->getMockBuilder($this->method_email::class) + ->setConstructorArgs([$this->assets_bag, + $this->config, + $this->dispatcher, + $this->language, + $this->queue, + $this->path_helper, + $this->request, + $this->twig_extensions_collection, + $this->twig_lexer, + $this->user, + $phpbb_root_path, + $this->cache_path, + $this->extension_manager, + $this->log + ]) + ->onlyMethods(['get_mailer']) + ->getMock(); + + $mailer_mock = $this->getMockBuilder(\Symfony\Component\Mailer\MailerInterface::class) + ->disableOriginalConstructor() + ->onlyMethods(['send']) + ->getMock(); + $mailer_mock->method('send') + ->willReturnCallback(function(RawMessage $mail) use(&$sent_emails) { + throw new \Symfony\Component\Mailer\Exception\TransportException('exception'); + }); + $this->method_email->method('get_mailer')->willReturn($mailer_mock); + + $this->config->set('board_email', 'admin@yourdomain.com'); + + $this->method_email->init(); + $errors = []; + $this->log->method('add') + ->willReturnCallback(function($mode, $user_id, $log_ip, $log_operation, $log_time = false, $additional_data = []) use (&$errors) { + $errors[] = $additional_data[0]; + }); + + $this->dispatcher + ->expects($this->atLeastOnce()) + ->method('trigger_event') + ->willReturnCallback(function($event_name, $value_array) { + return $value_array; + }); + + $this->method_email->to('foo@bar.com'); + $this->method_email->subject('Test email'); + $this->method_email->template('test', 'en'); + + $this->assertFalse($this->method_email->send()); + + $this->assertEquals(['EMAIL



'], $errors); + } + + public function test_send_queue() + { + global $phpbb_root_path; + + $this->config->set('email_enable', true); + $this->config->set('email_package_size', 100); + $this->user->data['user_id'] = 2; + $this->user->session_id = 'abcdef'; + + $this->method_email = $this->getMockBuilder($this->method_email::class) + ->setConstructorArgs([$this->assets_bag, + $this->config, + $this->dispatcher, + $this->language, + $this->queue, + $this->path_helper, + $this->request, + $this->twig_extensions_collection, + $this->twig_lexer, + $this->user, + $phpbb_root_path, + $this->cache_path, + $this->extension_manager, + $this->log + ]) + ->onlyMethods(['get_mailer']) + ->getMock(); + + $mailer_mock = $this->getMockBuilder(\Symfony\Component\Mailer\MailerInterface::class) + ->disableOriginalConstructor() + ->onlyMethods(['send']) + ->getMock(); + $mailer_mock->method('send') + ->willReturnCallback(function(RawMessage $mail) use(&$sent_emails) { + throw new \Symfony\Component\Mailer\Exception\TransportException('exception'); + }); + $this->method_email->method('get_mailer')->willReturn($mailer_mock); + + $this->config->set('board_email', 'admin@yourdomain.com'); + + $this->method_email->init(); + $errors = []; + $this->log->method('add') + ->willReturnCallback(function($mode, $user_id, $log_ip, $log_operation, $log_time = false, $additional_data = []) use (&$errors) { + $errors[] = $additional_data[0]; + }); + + $this->dispatcher + ->expects($this->atLeastOnce()) + ->method('trigger_event') + ->willReturnCallback(function($event_name, $value_array) { + return $value_array; + }); + + // Mock queue methods + $this->queue->method('init') + ->willReturnCallback(function(string $object, int $package_size) { + $this->assertEquals('email', $object); + $this->assertEquals($this->config['email_package_size'], $package_size); + }); + $this->queue->method('put') + ->willReturnCallback(function(string $object, array $message_data) { + $this->assertEquals('email', $object); + $this->assertStringContainsString('phpBB is correctly configured to send emails', $message_data['email']->getSubject()); + }); + + $this->method_email->to('foo@bar.com'); + $this->method_email->subject('Test email'); + $this->method_email->template('test', 'en'); + + $this->assertTrue($this->method_email->send()); + + $this->assertEmpty($errors); + } }