mirror of
https://github.com/archtechx/tenancy.git
synced 2025-12-12 21:54:03 +00:00
Make tenants able to have custom mail credentials (#989)
* Replace MailManager singleton with an instance of a custom mail manager which always resolves the mailers instead of getting the cached ones
* Fix code style (php-cs-fixer)
* Add MailTenancyBootstrapper
* Add MailTenancyBootstrapper to tenancy.bootstrappers config (commented out)
* Fix code style (php-cs-fixer)
* Make credentials map a public static property
* Always resolve only the mailers specified in the mailersToNotCache public static property
* Fix typo in comment
* Update TenancyServiceProvider comment
* add todo
* Add comments to TenancyMailManager, rename property
* Remove the configKey array check
* Simplify bootstrap method
* Change $credentialsMap so that config keys are the keys, and the tenant property names are the values
* Rename $mailersToAlwaysResolve to $tenantMailers
* Update comment
* Update comment
* Rename variable in TenancyServiceProvider comment
* Scaffold tests
* Update comments after review
* Uncomment MailTenancyBootstrapper in config
* Use array_key_exists instead of null check
* Split config logic into methods
* Update mapping credentials
* Add tests for the added logic
* Fix code style (php-cs-fixer)
* Delete default 'smtp' mailer in $tenantMailers
* Add separate method to pick the appropriate mail credentials map preset
* Specify test name
* Move mail bootstrapper tests to BootstrapperTest
* Depend less on the default mailer by adding a static `$mailer` property
* Use static property for map presets
* Comment out MailTenancyBootstrapper from config
* Add return types to MailTenancyBootstrapper methods
* Update test name
* Move MailManager extension to MailTenancyBootstrapper
* Fix code style (php-cs-fixer)
* Update config reverting test
* Use `invade()` instead of ReflectionClass
* Fix constructor parameter formatting
* Delete TenancyMailManager, update tests
* Add return type
* Update comment
* Update MailTest
* Delete `group('mailer')`
* Delete bindNewMailManagerInstance()
* Delete remaining `group('mailer')`
* Fix comment
* Fix comment
Co-authored-by: PHP CS Fixer <phpcsfixer@example.com>
Co-authored-by: Samuel Štancl <samuel.stancl@gmail.com>
This commit is contained in:
parent
f42f08cb87
commit
0f892f1585
5 changed files with 200 additions and 0 deletions
|
|
@ -102,6 +102,7 @@ return [
|
|||
Stancl\Tenancy\Bootstrappers\FilesystemTenancyBootstrapper::class,
|
||||
Stancl\Tenancy\Bootstrappers\QueueTenancyBootstrapper::class,
|
||||
Stancl\Tenancy\Bootstrappers\BatchTenancyBootstrapper::class,
|
||||
// Stancl\Tenancy\Bootstrappers\MailTenancyBootstrapper::class, // Queueing mail requires using QueueTenancyBootstrapper with $forceRefresh set to true
|
||||
// Stancl\Tenancy\Bootstrappers\RedisTenancyBootstrapper::class, // Note: phpredis is needed
|
||||
],
|
||||
|
||||
|
|
|
|||
79
src/Bootstrappers/MailTenancyBootstrapper.php
Normal file
79
src/Bootstrappers/MailTenancyBootstrapper.php
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Stancl\Tenancy\Bootstrappers;
|
||||
|
||||
use Illuminate\Config\Repository;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
|
||||
use Stancl\Tenancy\Contracts\Tenant;
|
||||
|
||||
class MailTenancyBootstrapper implements TenancyBootstrapper
|
||||
{
|
||||
/**
|
||||
* Tenant properties to be mapped to config (similarly to the TenantConfig feature).
|
||||
*
|
||||
* For example:
|
||||
* [
|
||||
* 'config.key.name' => 'tenant_property',
|
||||
* ]
|
||||
*/
|
||||
public static array $credentialsMap = [];
|
||||
|
||||
public static string|null $mailer = null;
|
||||
|
||||
protected array $originalConfig = [];
|
||||
|
||||
public static array $mapPresets = [
|
||||
'smtp' => [
|
||||
'mail.mailers.smtp.host' => 'smtp_host',
|
||||
'mail.mailers.smtp.port' => 'smtp_port',
|
||||
'mail.mailers.smtp.username' => 'smtp_username',
|
||||
'mail.mailers.smtp.password' => 'smtp_password',
|
||||
],
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
protected Repository $config,
|
||||
protected Application $app
|
||||
) {
|
||||
static::$mailer ??= $config->get('mail.default');
|
||||
static::$credentialsMap = array_merge(static::$credentialsMap, static::$mapPresets[static::$mailer] ?? []);
|
||||
}
|
||||
|
||||
public function bootstrap(Tenant $tenant): void
|
||||
{
|
||||
// Forget the mail manager instance to clear the cached mailers
|
||||
$this->app->forgetInstance('mail.manager');
|
||||
|
||||
$this->setConfig($tenant);
|
||||
}
|
||||
|
||||
public function revert(): void
|
||||
{
|
||||
$this->unsetConfig();
|
||||
|
||||
$this->app->forgetInstance('mail.manager');
|
||||
}
|
||||
|
||||
protected function setConfig(Tenant $tenant): void
|
||||
{
|
||||
foreach (static::$credentialsMap as $configKey => $storageKey) {
|
||||
$override = $tenant->$storageKey;
|
||||
|
||||
if (array_key_exists($storageKey, $tenant->getAttributes())) {
|
||||
$this->originalConfig[$configKey] ??= $this->config->get($configKey);
|
||||
|
||||
$this->config->set($configKey, $override);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function unsetConfig(): void
|
||||
{
|
||||
foreach ($this->originalConfig as $key => $value) {
|
||||
$this->config->set($key, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Mail\MailManager;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Stancl\JobPipeline\JobPipeline;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
|
@ -23,6 +24,7 @@ use Stancl\Tenancy\Jobs\RemoveStorageSymlinks;
|
|||
use Stancl\Tenancy\Listeners\BootstrapTenancy;
|
||||
use Stancl\Tenancy\Listeners\DeleteTenantStorage;
|
||||
use Stancl\Tenancy\Listeners\RevertToCentralContext;
|
||||
use Stancl\Tenancy\Bootstrappers\MailTenancyBootstrapper;
|
||||
use Stancl\Tenancy\Bootstrappers\CacheTenancyBootstrapper;
|
||||
use Stancl\Tenancy\Bootstrappers\RedisTenancyBootstrapper;
|
||||
use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
|
||||
|
|
@ -326,6 +328,49 @@ test('local storage public urls are generated correctly', function() {
|
|||
expect(File::isDirectory($tenantStoragePath))->toBeFalse();
|
||||
});
|
||||
|
||||
test('MailTenancyBootstrapper maps tenant mail credentials to config as specified in the $credentialsMap property and makes the mailer use tenant credentials', function() {
|
||||
MailTenancyBootstrapper::$credentialsMap = [
|
||||
'mail.mailers.smtp.username' => 'smtp_username',
|
||||
'mail.mailers.smtp.password' => 'smtp_password'
|
||||
];
|
||||
|
||||
config([
|
||||
'mail.default' => 'smtp',
|
||||
'mail.mailers.smtp.username' => $defaultUsername = 'default username',
|
||||
'mail.mailers.smtp.password' => 'no password'
|
||||
]);
|
||||
|
||||
$tenant = Tenant::create(['smtp_password' => $password = 'testing password']);
|
||||
|
||||
tenancy()->initialize($tenant);
|
||||
|
||||
expect(array_key_exists('smtp_password', tenant()->getAttributes()))->toBeTrue();
|
||||
expect(array_key_exists('smtp_host', tenant()->getAttributes()))->toBeFalse();
|
||||
expect(config('mail.mailers.smtp.username'))->toBe($defaultUsername);
|
||||
expect(config('mail.mailers.smtp.password'))->toBe(tenant()->smtp_password);
|
||||
|
||||
// Assert that the current mailer uses tenant's smtp_password
|
||||
assertMailerTransportUsesPassword($password);
|
||||
});
|
||||
|
||||
test('MailTenancyBootstrapper reverts the config and mailer credentials to default when tenancy ends', function() {
|
||||
MailTenancyBootstrapper::$credentialsMap = ['mail.mailers.smtp.password' => 'smtp_password'];
|
||||
config(['mail.default' => 'smtp', 'mail.mailers.smtp.password' => $defaultPassword = 'no password']);
|
||||
|
||||
tenancy()->initialize(Tenant::create(['smtp_password' => $tenantPassword = 'testing password']));
|
||||
|
||||
expect(config('mail.mailers.smtp.password'))->toBe($tenantPassword);
|
||||
|
||||
assertMailerTransportUsesPassword($tenantPassword);
|
||||
|
||||
tenancy()->end();
|
||||
|
||||
expect(config('mail.mailers.smtp.password'))->toBe($defaultPassword);
|
||||
|
||||
// Assert that the current mailer uses the default SMTP password
|
||||
assertMailerTransportUsesPassword($defaultPassword);
|
||||
});
|
||||
|
||||
function getDiskPrefix(string $disk): string
|
||||
{
|
||||
/** @var FilesystemAdapter $disk */
|
||||
|
|
|
|||
72
tests/MailTest.php
Normal file
72
tests/MailTest.php
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Mail\MailManager;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Stancl\Tenancy\Events\TenancyEnded;
|
||||
use Stancl\Tenancy\Events\TenancyInitialized;
|
||||
use Stancl\Tenancy\Listeners\BootstrapTenancy;
|
||||
use Stancl\Tenancy\Listeners\RevertToCentralContext;
|
||||
use Stancl\Tenancy\Bootstrappers\MailTenancyBootstrapper;
|
||||
|
||||
beforeEach(function() {
|
||||
config(['mail.default' => 'smtp']);
|
||||
|
||||
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
|
||||
Event::listen(TenancyEnded::class, RevertToCentralContext::class);
|
||||
});
|
||||
|
||||
// Initialize tenancy as $tenant and assert that the smtp mailer's transport has the correct password
|
||||
function assertMailerTransportUsesPassword(string|null $password) {
|
||||
$manager = app(MailManager::class);
|
||||
$mailer = invade($manager)->get('smtp');
|
||||
$mailerPassword = invade($mailer->getSymfonyTransport())->password;
|
||||
|
||||
expect($mailerPassword)->toBe((string) $password);
|
||||
};
|
||||
|
||||
test('mailer transport uses the correct credentials', function() {
|
||||
config(['mail.default' => 'smtp', 'mail.mailers.smtp.password' => $defaultPassword = 'DEFAULT']);
|
||||
MailTenancyBootstrapper::$credentialsMap = ['mail.mailers.smtp.password' => 'smtp_password'];
|
||||
|
||||
tenancy()->initialize($tenant = Tenant::create());
|
||||
assertMailerTransportUsesPassword($defaultPassword); // $tenant->smtp_password is not set, so the default password should be used
|
||||
tenancy()->end();
|
||||
|
||||
// Assert mailer uses the updated password
|
||||
$tenant->update(['smtp_password' => $newPassword = 'changed']);
|
||||
|
||||
tenancy()->initialize($tenant);
|
||||
assertMailerTransportUsesPassword($newPassword);
|
||||
tenancy()->end();
|
||||
|
||||
// Assert mailer uses the correct password after switching to a different tenant
|
||||
tenancy()->initialize(Tenant::create(['smtp_password' => $newTenantPassword = 'updated']));
|
||||
assertMailerTransportUsesPassword($newTenantPassword);
|
||||
tenancy()->end();
|
||||
|
||||
// Assert mailer uses the default password after tenancy ends
|
||||
assertMailerTransportUsesPassword($defaultPassword);
|
||||
});
|
||||
|
||||
|
||||
test('initializing and ending tenancy binds a fresh MailManager instance without cached mailers', function() {
|
||||
$mailers = fn() => invade(app(MailManager::class))->mailers;
|
||||
|
||||
app(MailManager::class)->mailer('smtp');
|
||||
|
||||
expect($mailers())->toHaveCount(1);
|
||||
|
||||
tenancy()->initialize(Tenant::create());
|
||||
|
||||
expect($mailers())->toHaveCount(0);
|
||||
|
||||
app(MailManager::class)->mailer('smtp');
|
||||
|
||||
expect($mailers())->toHaveCount(1);
|
||||
|
||||
tenancy()->end();
|
||||
|
||||
expect($mailers())->toHaveCount(0);
|
||||
});
|
||||
|
|
@ -14,6 +14,7 @@ use Stancl\Tenancy\Facades\GlobalCache;
|
|||
use Stancl\Tenancy\Facades\Tenancy;
|
||||
use Stancl\Tenancy\TenancyServiceProvider;
|
||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||
use Stancl\Tenancy\Bootstrappers\MailTenancyBootstrapper;
|
||||
|
||||
abstract class TestCase extends \Orchestra\Testbench\TestCase
|
||||
{
|
||||
|
|
@ -104,6 +105,7 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
|
|||
'--force' => true,
|
||||
],
|
||||
'tenancy.bootstrappers.redis' => RedisTenancyBootstrapper::class, // todo1 change this to []? two tests in TenantDatabaseManagerTest are failing with that
|
||||
'tenancy.bootstrappers.mail' => MailTenancyBootstrapper::class,
|
||||
'queue.connections.central' => [
|
||||
'driver' => 'sync',
|
||||
'central' => true,
|
||||
|
|
@ -113,6 +115,7 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
|
|||
]);
|
||||
|
||||
$app->singleton(RedisTenancyBootstrapper::class); // todo (Samuel) use proper approach eg config for singleton registration
|
||||
$app->singleton(MailTenancyBootstrapper::class);
|
||||
}
|
||||
|
||||
protected function getPackageProviders($app)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue