mirror of
https://github.com/archtechx/tenancy.git
synced 2026-02-05 01:14:03 +00:00
Add option to provide constraint information in column comment
This commit is contained in:
parent
588d1fcc0d
commit
c80b28cf00
2 changed files with 173 additions and 2 deletions
|
|
@ -6,6 +6,7 @@ namespace Stancl\Tenancy\RLS\PolicyManagers;
|
||||||
|
|
||||||
use Illuminate\Database\DatabaseManager;
|
use Illuminate\Database\DatabaseManager;
|
||||||
use Stancl\Tenancy\Database\Exceptions\RecursiveRelationshipException;
|
use Stancl\Tenancy\Database\Exceptions\RecursiveRelationshipException;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
// todo@samuel logical + structural refactor. the tree generation could use some dynamic programming optimizations
|
// todo@samuel logical + structural refactor. the tree generation could use some dynamic programming optimizations
|
||||||
class TableRLSManager implements RLSPolicyManager
|
class TableRLSManager implements RLSPolicyManager
|
||||||
|
|
@ -80,7 +81,7 @@ class TableRLSManager implements RLSPolicyManager
|
||||||
$table = str($table)->afterLast('.')->toString();
|
$table = str($table)->afterLast('.')->toString();
|
||||||
|
|
||||||
// For each table, we get a list of all foreign key columns
|
// For each table, we get a list of all foreign key columns
|
||||||
$foreignKeys = collect($builder->getForeignKeys($table))->map(function ($foreign) use ($table) {
|
$foreignKeys = collect($builder->getForeignKeys($table))->merge($this->getFakeForeignKeys($table))->map(function ($foreign) use ($table) {
|
||||||
return $this->formatForeignKey($foreign, $table);
|
return $this->formatForeignKey($foreign, $table);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -124,12 +125,50 @@ class TableRLSManager implements RLSPolicyManager
|
||||||
$paths[] = $currentPath;
|
$paths[] = $currentPath;
|
||||||
} else {
|
} else {
|
||||||
// If not, recursively generate paths for the foreign table
|
// If not, recursively generate paths for the foreign table
|
||||||
foreach ($this->database->getSchemaBuilder()->getForeignKeys($foreign['foreignTable']) as $nextConstraint) {
|
foreach (array_merge($this->database->getSchemaBuilder()->getForeignKeys($foreign['foreignTable'], $this->getFakeForeignKeys($table))) as $nextConstraint) {
|
||||||
$this->generatePaths($table, $this->formatForeignKey($nextConstraint, $foreign['foreignTable']), $paths, $currentPath);
|
$this->generatePaths($table, $this->formatForeignKey($nextConstraint, $foreign['foreignTable']), $paths, $currentPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function getFakeForeignKeys(string $tableName): array
|
||||||
|
{
|
||||||
|
$columns = $this->database->getSchemaBuilder()->getColumns($tableName);
|
||||||
|
|
||||||
|
$fakeForeignKeys = array_filter($columns, function ($column) {
|
||||||
|
// Constraint comment should be "rls <foreign_table>.<foreign_column>"
|
||||||
|
if ($column['comment']) {
|
||||||
|
return Str::contains($column['comment'], 'rls ');
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($tableName === 'non_constrained_posts') {
|
||||||
|
dump(array_map(function ($fakeForeignKey) {
|
||||||
|
// Constraint comment is "rls <foreign_table>.<foreign_column>"
|
||||||
|
$constraint = explode('.', Str::after($fakeForeignKey['comment'], 'rls '));
|
||||||
|
|
||||||
|
return [
|
||||||
|
'foreign_table' => $constraint[0],
|
||||||
|
'foreign_columns' => [$constraint[1]],
|
||||||
|
'columns' => [$fakeForeignKey['name']],
|
||||||
|
];
|
||||||
|
}, $fakeForeignKeys));
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_map(function ($fakeForeignKey) {
|
||||||
|
// Constraint comment is "rls <foreign_table>.<foreign_column>"
|
||||||
|
$constraint = explode('.', Str::after($fakeForeignKey['comment'], 'rls '));
|
||||||
|
|
||||||
|
return [
|
||||||
|
'foreign_table' => $constraint[0],
|
||||||
|
'foreign_columns' => [$constraint[1]],
|
||||||
|
'columns' => [$fakeForeignKey['name']],
|
||||||
|
];
|
||||||
|
}, $fakeForeignKeys);
|
||||||
|
}
|
||||||
|
|
||||||
/** Get tree's non-nullable paths. */
|
/** Get tree's non-nullable paths. */
|
||||||
protected function filterNonNullablePaths(array $tree): array
|
protected function filterNonNullablePaths(array $tree): array
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -663,6 +663,138 @@ test('table manager ignores recursive relationship if the foreign key responsibl
|
||||||
expect(fn () => app(TableRLSManager::class)->generateTrees())->not()->toThrow(RecursiveRelationshipException::class);
|
expect(fn () => app(TableRLSManager::class)->generateTrees())->not()->toThrow(RecursiveRelationshipException::class);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('table manager can generate paths leading through non-constrained foreign keys', function() {
|
||||||
|
Schema::create('non_constrained_users', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('tenant_id')->comment('rls tenants.id'); // "fake" constraint
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create('non_constrained_posts', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignId('author_id')->comment('rls non_constrained_users.id'); // another "fake" constraint
|
||||||
|
});
|
||||||
|
|
||||||
|
/** @var TableRLSManager $manager */
|
||||||
|
$manager = app(TableRLSManager::class);
|
||||||
|
|
||||||
|
$expectedTrees = [
|
||||||
|
'authors' => [
|
||||||
|
// Directly related to tenants
|
||||||
|
'tenant_id' => [
|
||||||
|
[
|
||||||
|
[
|
||||||
|
'foreignKey' => 'tenant_id',
|
||||||
|
'foreignTable' => 'tenants',
|
||||||
|
'foreignId' => 'id',
|
||||||
|
'nullable' => false,
|
||||||
|
]
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'comments' => [
|
||||||
|
// Tree starting from the post_id foreign key
|
||||||
|
'post_id' => [
|
||||||
|
[
|
||||||
|
[
|
||||||
|
'foreignKey' => 'post_id',
|
||||||
|
'foreignTable' => 'posts',
|
||||||
|
'foreignId' => 'id',
|
||||||
|
'nullable' => false,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreignKey' => 'author_id',
|
||||||
|
'foreignTable' => 'authors',
|
||||||
|
'foreignId' => 'id',
|
||||||
|
'nullable' => false,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreignKey' => 'tenant_id',
|
||||||
|
'foreignTable' => 'tenants',
|
||||||
|
'foreignId' => 'id',
|
||||||
|
'nullable' => false,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[
|
||||||
|
'foreignKey' => 'post_id',
|
||||||
|
'foreignTable' => 'posts',
|
||||||
|
'foreignId' => 'id',
|
||||||
|
'nullable' => false,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreignKey' => 'tenant_id',
|
||||||
|
'foreignTable' => 'tenants',
|
||||||
|
'foreignId' => 'id',
|
||||||
|
'nullable' => true,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'non_constrained_posts' => [
|
||||||
|
'author_id' => [
|
||||||
|
[
|
||||||
|
[
|
||||||
|
'foreignKey' => 'author_id',
|
||||||
|
'foreignTable' => 'non_constrained_users',
|
||||||
|
'foreignId' => 'id',
|
||||||
|
'nullable' => false,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreignKey' => 'tenant_id',
|
||||||
|
'foreignTable' => 'tenants',
|
||||||
|
'foreignId' => 'id',
|
||||||
|
'nullable' => false,
|
||||||
|
]
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'non_constrained_users' => [
|
||||||
|
// Category tree gets excluded because the category table is related to the tenant table
|
||||||
|
// only through a column with the 'no-rls' comment
|
||||||
|
'tenant_id' => [
|
||||||
|
[
|
||||||
|
[
|
||||||
|
'foreignKey' => 'tenant_id',
|
||||||
|
'foreignTable' => 'tenants',
|
||||||
|
'foreignId' => 'id',
|
||||||
|
'nullable' => false,
|
||||||
|
]
|
||||||
|
]
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'posts' => [
|
||||||
|
'author_id' => [
|
||||||
|
[
|
||||||
|
[
|
||||||
|
'foreignKey' => 'author_id',
|
||||||
|
'foreignTable' => 'authors',
|
||||||
|
'foreignId' => 'id',
|
||||||
|
'nullable' => false,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreignKey' => 'tenant_id',
|
||||||
|
'foreignTable' => 'tenants',
|
||||||
|
'foreignId' => 'id',
|
||||||
|
'nullable' => false,
|
||||||
|
]
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'tenant_id' => [
|
||||||
|
[
|
||||||
|
[
|
||||||
|
'foreignKey' => 'tenant_id',
|
||||||
|
'foreignTable' => 'tenants',
|
||||||
|
'foreignId' => 'id',
|
||||||
|
'nullable' => true,
|
||||||
|
]
|
||||||
|
]
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
expect($manager->generateTrees())->toEqual($expectedTrees);
|
||||||
|
});
|
||||||
|
|
||||||
class Post extends Model
|
class Post extends Model
|
||||||
{
|
{
|
||||||
protected $guarded = [];
|
protected $guarded = [];
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue