Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"symfony/yaml": "5.4.*"
},
"require-dev": {
"ext-openssl": "*",
"codeception/codeception": "^5.3",
"codeception/module-asserts": "^3.2",
"codeception/module-doctrine": "^3.3",
Expand Down
4 changes: 4 additions & 0 deletions config/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,8 @@
$routes->add('send_email', '/send-email')
->controller(App\Controller\SendEmailController::class)
->methods(['GET']);

$routes->add('send_encrypted_email', '/send-encrypted-email')
->controller(App\Controller\SendEncryptedEmailController::class)
->methods(['GET']);
};
25 changes: 25 additions & 0 deletions src/Controller/SendEncryptedEmailController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace App\Controller;

use App\Entity\User;
use App\Utils\Mailer;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;

final class SendEncryptedEmailController extends AbstractController
{
public function __construct(
private readonly Mailer $mailer,
) {
}

public function __invoke(): Response
{
$this->mailer->sendEncryptedEmail((new User())->setEmail('jane_doe@example.com'));

return $this->json(['message' => 'Encrypted email sent successfully']);
}
}
37 changes: 37 additions & 0 deletions src/Utils/Crypto.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types=1);

namespace App\Utils;

final readonly class Crypto
{
public function generateSslCertificate(): string
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This right here is a bit of a heavy overhead for testing this but necessary for the SMimeEncrypter to generate an encrypted e-mail. Maybe adding a test certificate directly into the tests/_data directory would be better. Any opinions on that?

Another option would be to fake "encryption" and just turn an Email object into a plain Message object.

{
if (!extension_loaded('openssl')) {
throw new \RuntimeException('OpenSSL extension is required.');
}

$privateKey = openssl_pkey_new();
$certSignRequest = openssl_csr_new([], $privateKey);
$certificate = openssl_csr_sign($certSignRequest, null, $privateKey, 1);

$tmpDir = sys_get_temp_dir();
$privateKeyPath = tempnam($tmpDir, 'pkey');
$certificatePath = tempnam($tmpDir, 'cert');

if (!openssl_pkey_export_to_file($privateKey, $privateKeyPath, 'powercloud')) {
throw new \RuntimeException('Private key generation failed: ' . openssl_error_string());
}
if (!openssl_x509_export_to_file($certificate, $certificatePath)) {
throw new \RuntimeException('OpenSSL certificate generation failed: ' . openssl_error_string());
}

register_shutdown_function(static function () use ($privateKeyPath, $certificatePath): void {
unlink($privateKeyPath);
unlink($certificatePath);
});

return $certificatePath;
}
}
25 changes: 23 additions & 2 deletions src/Utils/Mailer.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,16 @@
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\Crypto\SMimeEncrypter;
use Symfony\Component\Mime\Email;
use Symfony\Component\Mime\Message;

final readonly class Mailer
{
public function __construct(private MailerInterface $mailer)
{
public function __construct(
private MailerInterface $mailer,
private Crypto $crypto,
) {
}

public function sendConfirmationEmail(User $user): TemplatedEmail
Expand All @@ -30,4 +35,20 @@ public function sendConfirmationEmail(User $user): TemplatedEmail

return $email;
}

public function sendEncryptedEmail(User $user): Message
{
$encrypter = new SMimeEncrypter($this->crypto->generateSslCertificate());

$email = (new Email())
->from(new Address('jeison_doe@gmail.com', 'No Reply'))
->to(new Address($user->getEmail()))
->subject('Encrypted email')
->text('Encrypted content');

$message = $encrypter->encrypt($email);
$this->mailer->send($message);

return $message;
}
}
14 changes: 14 additions & 0 deletions tests/Functional/IssuesCest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use App\Entity\User;
use App\Tests\Support\FunctionalTester;
use Doctrine\DBAL\Connection;
use Symfony\Component\Mime\Message;

final class IssuesCest
{
Expand Down Expand Up @@ -53,4 +54,17 @@ public function runSymfonyConsoleCommandIgnoresSpecificOptions(FunctionalTester
$numRecords = $I->grabNumRecords(User::class);
$I->assertSame(1, $numRecords);
}

/**
* @see https://github.com/Codeception/module-symfony/pull/232
*/
public function assertEncryptedEmailTests(FunctionalTester $I)
{
$I->amOnPage('/send-encrypted-email');
$I->seeEmailIsSent(1);
$I->assertEmailAddressContains('To', 'jane_doe@example.com');
$I->assertEmailHeaderSame('To', 'jane_doe@example.com');
$I->assertEmailHeaderSame('Subject', 'Encrypted email');
$I->assertInstanceOf(Message::class, $I->grabLastSentEmail());
}
}