check()) { $errorResponse = $request->accepts(['application/json']) ? response()->json(config('palie.route_guard.json_error'), 403); : response(config('palie.route_guard.text_error'), 403); return $errorResponse; } $loggedInUser = auth()->user(); /* @NOTE: HTTP request cookie MUST be Http-Only and Same-Site: `strict` */ /* @HINT: This specific cookie MUST also be encrypted before including it in a HTTP response */ if ($loggedInUser->getTenantId() === $request->cookie($loggedInUser->getTenantColumnKey())) { $this->guardCallback($request); return $next($request); } return $errorResponse; } } /* namespace App\Http\Middleware; use Parlie\Traits\Routing\Middleware\Guard\ApplyTenantRouteGuard; use Illuminate\Http\Request; use App\Models\Scopes\MyCustomScope; use Illuminate\Database\Eloquent\Builder; use App\Models\Organization; class MyMiddleware { use ApplyTenantRouteGuard; public function guardCallback (Request $request): void { Organization::addGlobalScope(new MyCustomScope($request)); Organization::addGlobalScope('country', function(Builder $builder) { $country = session('location.from.ip.as.country'); $builder->where('country', $country); }); } } */ ?> pattern( config('palie.tenants.mirrors.id_name'), config('palie.tenants.mirrors.id_type', 'cuid_v2') === 'cuid_v2' ? '[a-z0-9]+' : '[-a-fA-F0-9]+' ); foreach ($MODELS_MAP as $parameter => $fullQualifiedClassNameOrCallback) { /*Route::model( $parameter, $fullQualifiedClassNameOrCallback, array($this, 'fallbackExitCall') );*/ $router->model( $parameter, $fullQualifiedClassNameOrCallback, array($this, 'fallbackExitCallback') ); } } } ?> request = $request !== null ? $request : request(); } public function __destruct () { $this->request = null; } public function applyTenantFilterClause (string $tenantColumnName, string $tenantTableName, Builer $builder):void { /* if (!Auth::hasUser()) {*/ if (auth()->guest()) { return; } $tenantId = $this->request->cookie($tenantColumnName); if ($tenantId) { $builder->where($tenantColumnName, $tenantId); return; } throw new \Exception("'tenant id' not found to scope model query for table: `" . $tenantTableName . "`"); } public function apply (Builder $builder, TenantModel $model): void { $tenantColumnName = $model->getTenantColumnKey(); $tenantTableName = $model->getTable(); $this->applyTenantFilterClause($tenantColumnName, $tenantTableName, $builder); } } ?> getTenantOwnerColumnKey(); } final protected function getTenantColumnName (): string { return $this->getTenantColumnKey(); } public function getTenantId(): string { $tenantId = $this->{$this->getTenantColumnName()}; $tenantId = (string) $tenantId; return $tenantId; } } abstract class TenantModel extends Model { use HasTenantHelpers; protected static function boot(): void { parent::boot(); } protected static function booting(): void { /* @TODO: ... */ } protected static function booted(): void { if (!static::hasGlobalScope('Parlie\Models\Scopes\TenantScope')) { /* @NOTE: The below won't work in Laravel 5.x+, 6.x+, 7.x+ and 8.x as it is required to pass the instance of the `Scope`interface in these versions of Laravel. We can't pass the instance of `Scope` interface here because it is going to be a circular dependency!!! */ static::addGlobalScope('Parlie\Models\Scopes\TenantScope'); } static::creating(function (self $tenantModel) { $tenantOwnerIdColumnName = $tenantModel->getTenantOwnerColumnName(); if ($tenantOwnerIdColumnName !== '_') { if (!array_key_exists($tenantOwnerIdColumnName, $tenantModel->attributes)) { $tenantModel->setAuthUserId(auth()->id()); } } }); static::saving(function (self $tenantModel) { $tenantIdColumnName = $tenantModel->getTenantColumnName(); $tenantSlugIdColumnName = $tenantModel->getRouteKeyName(); if (!isset($tenantModel->{$tenantIdColumnName})) { $tenantId = auth()-user()->getTenantId(); $tenantModel->attributes[$tenantIdColumnName] = $tenantId; } if (!isset($tenantModel->{$tenantSlugIdColumnName})) { $cuid = new Cuid2(20); $tenantModel->attributes[$tenantSlugIdColumnName] = $cuid->toString(); } }); // @TODO: do things with Laravel config/stuff here... } final public static function builderWithoutTenantScoping () { return static::withoutGlobalScope('Parlie\Models\Scopes\TenantScope'); } final public function setAuthUserId($id): bool { $ownerColumnName = $this->getTenantOwnerColumnName(); if (isset($id) and $ownerColumnName !== '_') { $this->attributes[$ownerColumnName] = $id; return true; } return false; } /* @Override (from Laravels' `Model` abstract class) */ public function getRouteKeyName () { /* @HINT: Value is generated using either cuid_v2 or uuid_v4 */ return config('palie.tenants.mirrors.id_name'); } public function relatedToTenantWithId($id): bool { return isset($id) and is_string($id) ? $id === $this->{$this->getTenantColumnName()} : false; } /* @Override (from Laravels' `Model` abstract class) */ public function resolveRouteBinding($value, $field = null) { return $this->where($this->getRouteKeyName(), $value)->firstOrFail(); } public function getTenantOwnerColumnKey(): string { return '_'; } } ?> domain('my.{account}.saavylytics.com')->prefix('/api/{tenant}')->group(function() { Route::get('/students', function(Organization $org) {})->name('')->withoutScopedBindings(); Route::get('/courses', function(Organization $org) {})->name(''); }); ?> where($this->getUserActiveColumnName(), $this->getValue()); } final protected function scopeAsNotSuspended (Builder $builder) { return $builder->where($this->getUserActiveColumnName(), $this->getValue(true)); } private function getValue ($switchOff = false) { $value = $switchOff ? 0 : 1; $flagDataType = $this->getUserActiveColumnType(); if ($dataType === "bool") { $value = $switchOff ? false : true; } return $value; } public function suspend(): bool { $this->{$this->getUserActiveColumnType()} = $this->getValue(); return $this->save(); } public function restoreFromSuspension(): bool { $this->{$this->getUserActiveColumnType()} = $this->getValue(true); return $this->save(); } public function isSuspended() { return $this->{$this->getUserActiveColumnName()}; } } ?> EfficientUuid::class, 'tenant_id' => custom::BinaryToUlidString::class, 'id' => custom::BinaryToUlidString::class ]; public function getTenantColumnKey(): string { return config('palie.tenants.column_key'); } public function getTenantOwnerColumnKey(): string { return $this->getKeyName(); } public function getUserActiveColumnName(): string { return 'is_active'; } public function getUserActiveColumnType(): string { return 'bool'; } public function uuidColumn(): string { return config('palie.tenants.mirrors.id_name'); } public function tenant(): BelongsTo { return $this->belongsTo(Organization::class); } } ?> getEagerModelKeys($models); if ($this->parent->getKeyType() === 'binary') { $keys = array_map(function($key) { /* @NOTE: each `$key` is already binary here from `->getRawOriginal(..)` > within `->getEagerModelKeys(..)` */ return $key; }, $keys); } $whereIn = $this->whereInMethod($this->parent, $this->localKey); if (!empty($keys)) { $this->whereInEager($whereIn, $this->foreignKey, $keys, $this->getRelationQuery()); } } protected function getEagerModelKeys(array $models) { $keys = []; foreach ($models as $model) { if (!is_null($value = $model->getRawOriginal($this->localKey))) { /* @HINT: Fetch raw binary value before casting */ $keys[] = $value; } } return array_values(array_unique($keys)); } } ?> EfficientUuid::class, 'id' => custom::BinaryToUlidString::class ]; public function getTenantColumnKey(): string { return $this->getKeyName(); } public function users(): BinaryHasMany { return $this->hasMany(User::class, config('palie.tenants.column_key'), $this->getKeyName()); } } ?>