Skip to content

Commit

Permalink
feat(cookie): Add support for tenant-aware cookies (#49)
Browse files Browse the repository at this point in the history
  • Loading branch information
ollieread authored Sep 30, 2024
1 parent fc47ecf commit 3ea2180
Show file tree
Hide file tree
Showing 7 changed files with 379 additions and 6 deletions.
38 changes: 37 additions & 1 deletion resources/config/sprout.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,46 @@

return [

/*
|--------------------------------------------------------------------------
| Should Sprout listen for routing?
|--------------------------------------------------------------------------
|
| This value decides whether Sprout listens for the RouteMatched event to
| identify tenants.
|
| Setting it to false will disable the pre-middleware identification,
| which in turn will make tenant-aware dependency injection no longer
| functionality.
|
*/

'listen_for_routing' => true,

/*
|--------------------------------------------------------------------------
| Service Overrides
|--------------------------------------------------------------------------
|
| This value sets which core Laravel services Sprout should override.
|
| Setting a service to false will disable its tenant-specific
| configuration/settings, and leave them using the default.
|
*/

'services' => [
'storage' => true,
// This will enable the 'sprout' driver for the filesystem disks,
// allowing for the creation of tenant scoped disks.
'storage' => true,

// This will enable the overwriting of the default settings for cookies.
// Each identity resolver may have effect different settings.
'cookies' => true,

// This will enable the overwriting of the default settings for sessions.
// Each identity resolver may have effect different settings.
'sessions' => true,
],

];
7 changes: 7 additions & 0 deletions src/Contracts/Tenancy.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ public function getName(): string;
* tenant.
*
* @return bool
*
* @phpstan-assert-if-true \Sprout\Contracts\Tenant $this->tenant()
* @phpstan-assert-if-true string $this->identifier()
* @phpstan-assert-if-true string|int $this->key()
* @phpstan-assert-if-false null $this->tenant()
* @phpstan-assert-if-false null $this->identifier()
* @phpstan-assert-if-false null $this->key()
*/
public function check(): bool;

Expand Down
77 changes: 75 additions & 2 deletions src/Http/Resolvers/PathIdentityResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@
use Illuminate\Routing\Router;
use Illuminate\Routing\RouteRegistrar;
use Sprout\Concerns\FindsIdentityInRouteParameter;
use Sprout\Contracts\Tenancy;
use Sprout\Contracts\IdentityResolverUsesParameters;
use Sprout\Contracts\Tenancy;
use Sprout\Contracts\Tenant;
use Sprout\Exceptions\TenantMissing;
use Sprout\Http\Middleware\TenantRoutes;
use Sprout\Support\BaseIdentityResolver;
use Sprout\Support\CookieHelper;
use function Sprout\sprout;

final class PathIdentityResolver extends BaseIdentityResolver implements IdentityResolverUsesParameters
{
Expand Down Expand Up @@ -75,9 +79,78 @@ public function routes(Router $router, Closure $groupRoutes, Tenancy $tenancy):
{
return $this->applyParameterPattern(
$router->middleware([TenantRoutes::ALIAS . ':' . $this->getName() . ',' . $tenancy->getName()])
->prefix($this->getRouteParameter($tenancy))
->prefix($this->getRoutePrefix($tenancy))
->group($groupRoutes),
$tenancy
);
}

/**
* Get the route prefix including the tenant parameter
*
* @template TenantClass of \Sprout\Contracts\Tenant
*
* @param \Sprout\Contracts\Tenancy<TenantClass> $tenancy
*
* @return string
*/
public function getRoutePrefix(Tenancy $tenancy): string
{
return $this->getRouteParameter($tenancy);
}

/**
* Get the route prefix with the parameter replaced with the tenant identifier
*
* @template TenantClass of \Sprout\Contracts\Tenant
*
* @param \Sprout\Contracts\Tenancy<TenantClass> $tenancy
*
* @return string
*
* @throws \Sprout\Exceptions\TenantMissing
*/
public function getTenantRoutePrefix(Tenancy $tenancy): string
{
if (! $tenancy->check()) {
throw TenantMissing::make($tenancy->getName()); // @codeCoverageIgnore
}

/** @var string $identifier */
$identifier = $tenancy->identifier();

return str_replace(
'{' . $this->getRouteParameterName($tenancy) . '}',
$identifier,
$this->getRoutePrefix($tenancy)
);
}

/**
* Perform setup actions for the tenant
*
* When a tenant is marked as the current tenant within a tenancy, this
* method will be called to perform any necessary setup actions.
* This method is also called if there is no current tenant, as there may
* be actions needed.
*
* @template TenantClass of \Sprout\Contracts\Tenant
*
* @param \Sprout\Contracts\Tenancy<TenantClass> $tenancy
* @param \Sprout\Contracts\Tenant|null $tenant
*
* @phpstan-param TenantClass|null $tenant
*
* @return void
*
* @throws \Sprout\Exceptions\TenantMissing
*/
public function setup(Tenancy $tenancy, ?Tenant $tenant): void
{
parent::setup($tenancy, $tenant);

if ($tenant !== null && sprout()->config('services.cookies', false) === true) {
CookieHelper::setDefaults(path: $this->getTenantRoutePrefix($tenancy));
}
}
}
65 changes: 62 additions & 3 deletions src/Http/Resolvers/SubdomainIdentityResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@
use Illuminate\Routing\Router;
use Illuminate\Routing\RouteRegistrar;
use Sprout\Concerns\FindsIdentityInRouteParameter;
use Sprout\Contracts\Tenancy;
use Sprout\Contracts\IdentityResolverUsesParameters;
use Sprout\Contracts\Tenancy;
use Sprout\Contracts\Tenant;
use Sprout\Exceptions\TenantMissing;
use Sprout\Http\Middleware\TenantRoutes;
use Sprout\Support\BaseIdentityResolver;
use Sprout\Support\CookieHelper;
use function Sprout\sprout;

final class SubdomainIdentityResolver extends BaseIdentityResolver implements IdentityResolverUsesParameters
{
Expand Down Expand Up @@ -58,7 +62,7 @@ public function resolveFromRequest(Request $request, Tenancy $tenancy): ?string
*
* @return string
*/
protected function getDomainParameter(Tenancy $tenancy): string
protected function getRouteDomain(Tenancy $tenancy): string
{
return $this->getRouteParameter($tenancy) . '.' . $this->domain;
}
Expand All @@ -80,10 +84,65 @@ protected function getDomainParameter(Tenancy $tenancy): string
public function routes(Router $router, Closure $groupRoutes, Tenancy $tenancy): RouteRegistrar
{
return $this->applyParameterPattern(
$router->domain($this->getDomainParameter($tenancy))
$router->domain($this->getRouteDomain($tenancy))
->middleware([TenantRoutes::ALIAS . ':' . $this->getName() . ',' . $tenancy->getName()])
->group($groupRoutes),
$tenancy
);
}

/**
* Get the route domain with the parameter replaced with the tenant identifier
*
* @template TenantClass of \Sprout\Contracts\Tenant
*
* @param \Sprout\Contracts\Tenancy<TenantClass> $tenancy
*
* @return string
*
* @throws \Sprout\Exceptions\TenantMissing
*/
public function getTenantRouteDomain(Tenancy $tenancy): string
{
if (! $tenancy->check()) {
throw TenantMissing::make($tenancy->getName()); // @codeCoverageIgnore
}

/** @var string $identifier */
$identifier = $tenancy->identifier();

return str_replace(
'{' . $this->getRouteParameterName($tenancy) . '}',
$identifier,
$this->getRouteDomain($tenancy)
);
}

/**
* Perform setup actions for the tenant
*
* When a tenant is marked as the current tenant within a tenancy, this
* method will be called to perform any necessary setup actions.
* This method is also called if there is no current tenant, as there may
* be actions needed.
*
* @template TenantClass of \Sprout\Contracts\Tenant
*
* @param \Sprout\Contracts\Tenancy<TenantClass> $tenancy
* @param \Sprout\Contracts\Tenant|null $tenant
*
* @phpstan-param TenantClass|null $tenant
*
* @return void
*
* @throws \Sprout\Exceptions\TenantMissing
*/
public function setup(Tenancy $tenancy, ?Tenant $tenant): void
{
parent::setup($tenancy, $tenant);

if ($tenant !== null && sprout()->config('services.cookies', false) === true) {
CookieHelper::setDefaults(domain: $this->getTenantRouteDomain($tenancy));
}
}
}
36 changes: 36 additions & 0 deletions src/Support/CookieHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);

namespace Sprout\Support;

use Illuminate\Cookie\CookieJar;

final class CookieHelper
{
public static function setDefaults(?string $path = null, ?string $domain = null, ?bool $secure = null, ?string $sameSite = null): void
{
// Collect the defaults for the values
self::collectDefaults($path, $domain, $secure, $sameSite);

/**
* This is here, so PHPStan doesn't get upset
*
* @var string $path
* @var string|null $domain
* @var bool $secure
* @var string|null $sameSite
*/

// Set the defaults
app(CookieJar::class)->setDefaultPathAndDomain($path, $domain, $secure, $sameSite);
}

public static function collectDefaults(?string &$path = null, ?string &$domain = null, ?bool &$secure = null, ?string &$sameSite = null): void
{
// Collect the defaults for the values
$path ??= config('session.path');
$domain ??= config('session.domain');
$secure ??= config('session.secure', false);
$sameSite ??= config('session.same_site');
}
}
10 changes: 10 additions & 0 deletions src/helpers.php
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
<?php

namespace Sprout;

/**
* Get the core sprout class
*
* @return \Sprout\Sprout
*/
function sprout(): Sprout
{
return app()->make(Sprout::class);
}
Loading

0 comments on commit 3ea2180

Please sign in to comment.