1
0
Fork 0
mirror of https://github.com/archtechx/tenancy.git synced 2025-12-12 18:04:03 +00:00

Merge branch 'master' of github.com:tenancy-for-laravel/v4

This commit is contained in:
Samuel Štancl 2022-11-29 10:19:12 +01:00
commit b2dc0844eb
24 changed files with 581 additions and 166 deletions

View file

@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Bootstrappers;
use Illuminate\Config\Repository;
use Illuminate\Contracts\Container\Container;
use Illuminate\Session\DatabaseSessionHandler;
use Illuminate\Session\SessionManager;
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
use Stancl\Tenancy\Contracts\Tenant;
/**
* This resets the database connection used by the database session driver.
*
* It runs each time tenancy is initialized or ended.
* That way the session driver always uses the current DB connection.
*/
class SessionTenancyBootstrapper implements TenancyBootstrapper
{
public function __construct(
protected Repository $config,
protected Container $container,
protected SessionManager $session,
) {
}
public function bootstrap(Tenant $tenant): void
{
$this->resetDatabaseHandler();
}
public function revert(): void
{
// When ending tenancy, this runs *before* the DatabaseTenancyBootstrapper, so DB tenancy
// is still bootstrapped. For that reason, we have to explicitly use the central connection
$this->resetDatabaseHandler(config('tenancy.database.central_connection'));
}
protected function resetDatabaseHandler(string $defaultConnection = null): void
{
$sessionDrivers = $this->session->getDrivers();
if (isset($sessionDrivers['database'])) {
/** @var \Illuminate\Session\Store $databaseDriver */
$databaseDriver = $sessionDrivers['database'];
$databaseDriver->setHandler($this->createDatabaseHandler($defaultConnection));
}
}
protected function createDatabaseHandler(string $defaultConnection = null): DatabaseSessionHandler
{
// Typically returns null, so this falls back to the default DB connection
$connection = $this->config->get('session.connection') ?? $defaultConnection;
// Based on SessionManager::createDatabaseDriver
return new DatabaseSessionHandler(
$this->container->make('db')->connection($connection),
$this->config->get('session.table'),
$this->config->get('session.lifetime'),
$this->container,
);
}
}

View file

@ -1,64 +0,0 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Features;
use Closure;
use Illuminate\Routing\Route;
use Illuminate\Support\Facades\Route as Router;
use Stancl\Tenancy\Contracts\Feature;
use Stancl\Tenancy\Middleware;
use Stancl\Tenancy\Tenancy;
class UniversalRoutes implements Feature
{
public static string $middlewareGroup = 'universal';
// todo docblock
/** @var array<class-string<\Stancl\Tenancy\Middleware\IdentificationMiddleware>> */
public static array $identificationMiddlewares = [
Middleware\InitializeTenancyByDomain::class,
Middleware\InitializeTenancyBySubdomain::class,
];
public function bootstrap(Tenancy $tenancy): void
{
foreach (static::$identificationMiddlewares as $middleware) {
$originalOnFail = $middleware::$onFail;
$middleware::$onFail = function ($exception, $request, $next) use ($originalOnFail) {
if (static::routeHasMiddleware($request->route(), static::$middlewareGroup)) {
return $next($request);
}
if ($originalOnFail) {
return $originalOnFail($exception, $request, $next);
}
throw $exception;
};
}
}
public static function routeHasMiddleware(Route $route, string $middleware): bool
{
/** @var array $routeMiddleware */
$routeMiddleware = $route->middleware();
if (in_array($middleware, $routeMiddleware, true)) {
return true;
}
// Loop one level deep and check if the route's middleware
// groups have the searched middleware group inside them
$middlewareGroups = Router::getMiddlewareGroups();
foreach ($route->gatherMiddleware() as $inner) {
if (! $inner instanceof Closure && isset($middlewareGroups[$inner]) && in_array($middleware, $middlewareGroups[$inner], true)) {
return true;
}
}
return false;
}
}

View file

@ -22,6 +22,11 @@ class InitializeTenancyByDomain extends IdentificationMiddleware
/** @return \Illuminate\Http\Response|mixed */
public function handle(Request $request, Closure $next): mixed
{
if (in_array($request->getHost(), config('tenancy.central_domains', []), true)) {
// Always bypass tenancy initialization when host is in central domains
return $next($request);
}
return $this->initializeTenancy(
$request,
$next,

View file

@ -28,14 +28,13 @@ class InitializeTenancyByPath extends IdentificationMiddleware
/** @return \Illuminate\Http\Response|mixed */
public function handle(Request $request, Closure $next): mixed
{
/** @var Route $route */
$route = $request->route();
$route = $this->route($request);
// Only initialize tenancy if tenant is the first parameter
// We don't want to initialize tenancy if the tenant is
// simply injected into some route controller action.
if ($route->parameterNames()[0] === PathTenantResolver::tenantParameterName()) {
$this->setDefaultTenantForRouteParametersWhenTenancyIsInitialized();
$this->setDefaultTenantForRouteParametersWhenInitializingTenancy();
return $this->initializeTenancy(
$request,
@ -47,7 +46,26 @@ class InitializeTenancyByPath extends IdentificationMiddleware
}
}
protected function setDefaultTenantForRouteParametersWhenTenancyIsInitialized(): void
protected function route(Request $request): Route
{
/** @var Route $route */
$route = $request->route();
if (! $route) {
// Create a fake $route instance that has enough information for this middleware's needs
$route = new Route($request->method(), $request->getUri(), []);
/**
* getPathInfo() returns the path except the root domain.
* We fetch the first parameter because tenant parameter is *always* first.
*/
$route->parameters[PathTenantResolver::tenantParameterName()] = explode('/', ltrim($request->getPathInfo(), '/'))[0];
$route->parameterNames[] = PathTenantResolver::tenantParameterName();
}
return $route;
}
protected function setDefaultTenantForRouteParametersWhenInitializingTenancy(): void
{
Event::listen(InitializingTenancy::class, function (InitializingTenancy $event) {
/** @var Tenant $tenant */

View file

@ -27,6 +27,11 @@ class InitializeTenancyBySubdomain extends InitializeTenancyByDomain
/** @return Response|mixed */
public function handle(Request $request, Closure $next): mixed
{
if (in_array($request->getHost(), config('tenancy.central_domains', []), true)) {
// Always bypass tenancy initialization when host is in central domains
return $next($request);
}
$subdomain = $this->makeSubdomain($request->getHost());
if (is_object($subdomain) && $subdomain instanceof Exception) {

View file

@ -1,30 +0,0 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Middleware;
use Closure;
use Illuminate\Http\Request;
class PreventAccessFromCentralDomains
{
/**
* Set this property if you want to customize the on-fail behavior.
*/
public static ?Closure $abortRequest;
/** @return \Illuminate\Http\Response|mixed */
public function handle(Request $request, Closure $next): mixed
{
if (in_array($request->getHost(), config('tenancy.central_domains'))) {
$abortRequest = static::$abortRequest ?? function () {
abort(404);
};
return $abortRequest($request, $next);
}
return $next($request);
}
}

View file

@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Routing\Route;
use Illuminate\Support\Facades\Route as Router;
// todo come up with a better name
class PreventAccessFromUnwantedDomains
{
/**
* Set this property if you want to customize the on-fail behavior.
*/
public static ?Closure $abortRequest;
/** @return \Illuminate\Http\Response|mixed */
public function handle(Request $request, Closure $next): mixed
{
if ($this->routeHasMiddleware($request->route(), 'universal')) {
return $next($request);
}
if (in_array($request->getHost(), config('tenancy.central_domains'), true)) {
$abortRequest = static::$abortRequest ?? function () {
abort(404);
};
return $abortRequest($request, $next);
}
return $next($request);
}
protected function routeHasMiddleware(Route $route, string $middleware): bool
{
/** @var array $routeMiddleware */
$routeMiddleware = $route->middleware();
if (in_array($middleware, $routeMiddleware, true)) {
return true;
}
// Loop one level deep and check if the route's middleware
// groups have the searched middleware group inside them
$middlewareGroups = Router::getMiddlewareGroups();
foreach ($route->gatherMiddleware() as $inner) {
if (! $inner instanceof Closure && isset($middlewareGroups[$inner]) && in_array($middleware, $middlewareGroups[$inner], true)) {
return true;
}
}
return false;
}
}