From 72d924e8bf3bdb4065187275abade8f1b66da9b7 Mon Sep 17 00:00:00 2001 From: Gautier DELEGLISE Date: Wed, 12 Feb 2025 22:43:15 +0100 Subject: [PATCH 01/13] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20reworked=20package?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/ISSUE_TEMPLATE/Bug_report.yml | 43 ----- .../Question_or_enhancement_proposal.yml | 9 - .github/ISSUE_TEMPLATE/config.yml | 5 - .github/workflows/tests.yml | 4 +- composer.json | 6 +- config/access-control.php | 15 +- src/AccessServiceProvider.php | 30 --- src/Controls/Concerns/HasPolicy.php | 32 ---- src/Controls/Concerns/HasQuery.php | 47 ----- src/Controls/Control.php | 118 +++++++++--- src/Controls/HasControl.php | 42 +++++ .../HasControlScope.php} | 20 +- src/Exceptions/QueryNotImplemented.php | 7 - src/Perimeters/Perimeter.php | 67 ++----- src/Perimeters/PerimeterCollection.php | 130 ------------- src/Perimeters/Perimeters.php | 91 --------- src/PoliciesControlled.php | 70 ------- src/QueriesControlled.php | 39 ---- tests/Feature/PerimetersTest.php | 32 ++++ tests/Feature/QueriesTest.php | 174 ------------------ tests/Feature/TestCase.php | 3 - .../Support/Access/Controls/ModelControl.php | 88 +++------ .../Controls/NotImplementedQueryControl.php | 30 --- .../Access/Perimeters/ClientPerimeter.php | 11 +- .../Access/Perimeters/GlobalPerimeter.php | 15 ++ .../Access/Perimeters/OwnPerimeter.php | 11 +- .../Access/Perimeters/SharedPerimeter.php | 13 +- .../Access/Perimeters/SitePerimeter.php | 12 -- tests/Support/Access/Policies/ModelPolicy.php | 22 --- .../Database/Factories/UserFactory.php | 4 + .../2014_00_00_000000_create_users_table.php | 4 + .../2023_04_00_000000_create_models_table.php | 6 +- tests/Support/Models/Model.php | 23 +-- .../Models/NotImplementedQueryModel.php | 14 -- tests/Support/Models/User.php | 8 + .../Traits/InteractsWithAuthorization.php | 22 --- tests/TestCase.php | 36 ++-- tests/Unit/ControlsTest.php | 61 ------ tests/Unit/PoliciesTest.php | 85 --------- tests/Unit/TestCase.php | 24 --- 40 files changed, 287 insertions(+), 1186 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/Bug_report.yml delete mode 100644 .github/ISSUE_TEMPLATE/Question_or_enhancement_proposal.yml delete mode 100644 .github/ISSUE_TEMPLATE/config.yml delete mode 100644 src/Controls/Concerns/HasPolicy.php delete mode 100644 src/Controls/Concerns/HasQuery.php create mode 100644 src/Controls/HasControl.php rename src/{ControlScope.php => Controls/HasControlScope.php} (72%) delete mode 100644 src/Exceptions/QueryNotImplemented.php delete mode 100644 src/Perimeters/PerimeterCollection.php delete mode 100644 src/Perimeters/Perimeters.php delete mode 100644 src/PoliciesControlled.php delete mode 100644 src/QueriesControlled.php create mode 100644 tests/Feature/PerimetersTest.php delete mode 100644 tests/Feature/QueriesTest.php delete mode 100644 tests/Support/Access/Controls/NotImplementedQueryControl.php create mode 100644 tests/Support/Access/Perimeters/GlobalPerimeter.php delete mode 100644 tests/Support/Access/Perimeters/SitePerimeter.php delete mode 100644 tests/Support/Access/Policies/ModelPolicy.php delete mode 100644 tests/Support/Models/NotImplementedQueryModel.php delete mode 100644 tests/Support/Traits/InteractsWithAuthorization.php delete mode 100644 tests/Unit/ControlsTest.php delete mode 100644 tests/Unit/PoliciesTest.php delete mode 100644 tests/Unit/TestCase.php diff --git a/.github/ISSUE_TEMPLATE/Bug_report.yml b/.github/ISSUE_TEMPLATE/Bug_report.yml deleted file mode 100644 index f307b4c..0000000 --- a/.github/ISSUE_TEMPLATE/Bug_report.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: Bug Report -description: "Report something that's broken." -body: - - type: input - attributes: - label: Laravel Rest Api Version - description: Provide the Laravel Rest Api version that you are using. - placeholder: 1.0.0 - validations: - required: true - - type: input - attributes: - label: Laravel Version - description: Provide the Laravel version that you are using. - placeholder: 10.4.1 - validations: - required: true - - type: input - attributes: - label: PHP Version - description: Provide the PHP version that you are using. - placeholder: 8.1.4 - validations: - required: true - - type: input - attributes: - label: Database Driver & Version - description: If applicable, provide the database driver and version you are using. - placeholder: "MySQL 8.0.31 for macOS 13.0 on arm64 (Homebrew)" - validations: - required: false - - type: textarea - attributes: - label: Description - description: Provide a detailed description of the issue you are facing. - validations: - required: true - - type: textarea - attributes: - label: Steps To Reproduce - description: Provide detailed steps to reproduce your issue. If necessary, please provide a GitHub repository to demonstrate your issue. - validations: - required: true \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/Question_or_enhancement_proposal.yml b/.github/ISSUE_TEMPLATE/Question_or_enhancement_proposal.yml deleted file mode 100644 index 5e64840..0000000 --- a/.github/ISSUE_TEMPLATE/Question_or_enhancement_proposal.yml +++ /dev/null @@ -1,9 +0,0 @@ -name: Question / Enchancement proposal -description: "Ask a question or for a feature" -body: - - type: textarea - attributes: - label: Description - description: Leave a comment - validations: - required: true \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index 1d74663..0000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,5 +0,0 @@ -blank_issues_enabled: false -contact_links: - - name: Documentation issue - url: https://github.com/Lomkit/laravel-rest-api-doc - about: For documentation issues, open a pull request at the lomkit/laravel-rest-api-doc repository \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 531732b..ce16fed 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,8 +14,8 @@ jobs: strategy: fail-fast: false matrix: - php-version: [ '8.1', '8.2', '8.3' ] - laravel-version: [ '^10.0' ] + php-version: [ '8.1', '8.2', '8.3', '8.4' ] + laravel-version: [ '^11.0' ] database: [ 'sqlite', 'mysql', 'pgsql' ] name: Tests on PHP ${{ matrix.php-version }} with Laravel ${{ matrix.laravel-version }} and ${{ matrix.database }} diff --git a/composer.json b/composer.json index bcde619..8c83113 100644 --- a/composer.json +++ b/composer.json @@ -12,12 +12,12 @@ "require": { "php": "^8.1", "ext-json": "*", - "laravel/framework": "^10.0" + "laravel/framework": "^11.0" }, "require-dev": { "guzzlehttp/guzzle": "^6.0|^7.0", - "orchestra/testbench": "^8.5", - "phpunit/phpunit": "^8.0|^9.0|^10.0" + "orchestra/testbench": "^9", + "phpunit/phpunit": "^11.0" }, "autoload": { "psr-4": { diff --git a/config/access-control.php b/config/access-control.php index cb4049b..54318cb 100644 --- a/config/access-control.php +++ b/config/access-control.php @@ -1,19 +1,6 @@ [ - 'path' => app_path('Access/Perimeters'), - ], - /* |-------------------------------------------------------------------------- | Access Control Queries @@ -22,6 +9,6 @@ | */ 'queries' => [ - 'enabled_by_default' => true, + 'enabled_by_default' => false, ], ]; diff --git a/src/AccessServiceProvider.php b/src/AccessServiceProvider.php index 5ee6406..eefcd6b 100644 --- a/src/AccessServiceProvider.php +++ b/src/AccessServiceProvider.php @@ -3,12 +3,9 @@ namespace Lomkit\Access; use Illuminate\Support\ServiceProvider; -use Lomkit\Access\Perimeters\Perimeters; class AccessServiceProvider extends ServiceProvider { - // @TODO: add the ability to remove control scope on certain conditions - /** * Register the service provider. * @@ -20,8 +17,6 @@ public function register() __DIR__.'/../config/access-control.php', 'access-control' ); - - $this->registerServices(); } /** @@ -32,31 +27,6 @@ public function register() public function boot() { $this->registerPublishing(); - - $this->registerPerimeters(); - } - - /** - * Register Access's perimeters. - * - * @return void - */ - protected function registerPerimeters() - { - $this->app->make(Perimeters::class) - ->perimetersIn( - config('access-control.perimeters.path', app_path('Access/Perimeters')) - ); - } - - /** - * Register Access's services in the container. - * - * @return void - */ - protected function registerServices() - { - $this->app->singleton(Perimeters::class); } /** diff --git a/src/Controls/Concerns/HasPolicy.php b/src/Controls/Concerns/HasPolicy.php deleted file mode 100644 index 8852a08..0000000 --- a/src/Controls/Concerns/HasPolicy.php +++ /dev/null @@ -1,32 +0,0 @@ -getConcernedPerimeters(); - - return $concernedPerimeters->contains(function (Perimeter $concernedPerimeter) use ($method, $model, $user) { - return $this->policy($concernedPerimeter, $method, $user, $model); - }); - } - - public function policy(Perimeter $perimeter, string $method, Model $user, Model $model): bool - { - // @TODO: for the "shared" example, implement the fact that for the query you can add multiple query - $policyMethod = Str::camel($perimeter->name).'Policy'; - - if (method_exists($this, $policyMethod)) { - return $this->$policyMethod($method, $user, $model); - } - - return false; - } -} diff --git a/src/Controls/Concerns/HasQuery.php b/src/Controls/Concerns/HasQuery.php deleted file mode 100644 index 3fa38e6..0000000 --- a/src/Controls/Concerns/HasQuery.php +++ /dev/null @@ -1,47 +0,0 @@ -getConcernedPerimeters())->isNotEmpty()) { - return tap($query, function (Builder $query) use ($concernedPerimeters) { - foreach ($concernedPerimeters as $concernedPerimeter) { - $this->query($concernedPerimeter, $query); - if ($concernedPerimeter->final()) { - return; - } - } - }); - - return; - } - - return $this->fallbackQuery($query); - } - - public function query(Perimeter $perimeter, Builder $query): Builder - { - $queryMethod = Str::camel($perimeter->name).'Query'; - - if (method_exists($this, $queryMethod)) { - $this->$queryMethod($query); - - return $query; - } - - throw new QueryNotImplemented(sprintf('The %s method is not implemented in the %s class', $queryMethod, get_class($this))); - } - - public function fallbackQuery(Builder $query): Builder - { - return $query; - } -} diff --git a/src/Controls/Control.php b/src/Controls/Control.php index 668bc7f..bfdfe22 100644 --- a/src/Controls/Control.php +++ b/src/Controls/Control.php @@ -2,52 +2,112 @@ namespace Lomkit\Access\Controls; -use Illuminate\Support\Collection; +use Illuminate\Container\Container; +use Illuminate\Contracts\Foundation\Application; use Illuminate\Support\Str; -use Lomkit\Access\Controls\Concerns\HasPolicy; -use Lomkit\Access\Controls\Concerns\HasQuery; -use Lomkit\Access\Perimeters\Perimeter; -use Lomkit\Access\Perimeters\Perimeters; +use Throwable; class Control { - use HasQuery; - use HasPolicy; + /** + * The control name resolver. + * + * @var callable + */ + protected static $controlNameResolver; - protected Perimeters $perimeters; + /** + * The default namespace where control reside. + * + * @var string + */ + public static $namespace = 'App\\Access\\Controls\\'; - protected Collection $concernedPerimeters; + /** + * Get the perimeters for the current control + * + * @return array + */ + protected function perimeters(): array + { + return []; + } - public function __construct(Perimeters $perimeters) + /** + * Specify the callback that should be invoked to guess control names. + * + * @param callable(class-string<\Illuminate\Database\Eloquent\Model>): class-string<\Lomkit\Access\Controls\Control> $callback + * @return void + */ + public static function guessControlNamesUsing(callable $callback) { - $this->perimeters = $perimeters; + static::$controlNameResolver = $callback; } - public function should(Perimeter $perimeter): bool + + /** + * Get a new control instance for the given model name. + * + * @template TClass of \Illuminate\Database\Eloquent\Model + * + * @param class-string $modelName + * @return \Lomkit\Access\Controls\Control + */ + public static function controlForModel(string $modelName) { - $perimeterMethod = 'should'.Str::studly($perimeter->name); + $control = static::resolveControlName($modelName); - if (method_exists($this, $perimeterMethod)) { - return $this->$perimeterMethod(); - } + return $control::new(); + } + //@TODO: new ClientPerimeter($queryCallback, $policyCallback) ? + // @TODO: shouldCallback déjà définie ? - return false; + /** + * Get a new control instance for the given attributes. + * + * @return static + */ + public static function new() + { + return (new static); } - public function getConcernedPerimeters(): Collection + /** + * Get the control name for the given model name. + * + * @template TClass of \Illuminate\Database\Eloquent\Model + * + * @param class-string $modelName + * @return class-string<\Lomkit\Access\Controls\Control> + */ + public static function resolveControlName(string $modelName) { - if (isset($this->concernedPerimeters)) { - return $this->concernedPerimeters; - } + $resolver = static::$controlNameResolver ?? function (string $modelName) { + $appNamespace = static::appNamespace(); - $perimeters = new Collection(); + $modelName = Str::startsWith($modelName, $appNamespace.'Models\\') + ? Str::after($modelName, $appNamespace.'Models\\') + : Str::after($modelName, $appNamespace); - foreach ($this->perimeters->getPerimeters() as $perimeter) { - if ($this->should($perimeter)) { - $perimeters->push($perimeter); - } - } + return static::$namespace.$modelName.'Control'; + }; + + return $resolver($modelName); + } - return $this->concernedPerimeters = $perimeters; + /** + * Get the application namespace for the application. + * + * @return string + */ + protected static function appNamespace() + { + try { + return Container::getInstance() + ->make(Application::class) + ->getNamespace(); + } catch (Throwable) { + return 'App\\'; + } } -} +} \ No newline at end of file diff --git a/src/Controls/HasControl.php b/src/Controls/HasControl.php new file mode 100644 index 0000000..89bd966 --- /dev/null +++ b/src/Controls/HasControl.php @@ -0,0 +1,42 @@ +withControl(); + $builder->controlled(); } } @@ -47,13 +46,14 @@ public function extend(Builder $builder) * * @return void */ - protected function addWithControl(Builder $builder) + protected function addControlled(Builder $builder) { - $builder->macro('withControl', function (Builder $builder) { + $builder->macro('controlled', function (Builder $builder) { /** @var Control $control */ $control = $builder->getModel()->newControl(); - return $control->runQuery($builder); + // @TODO: + //return $control->runQuery($builder); }); } @@ -64,9 +64,9 @@ protected function addWithControl(Builder $builder) * * @return void */ - protected function addWithoutControl(Builder $builder) + protected function addUncontrolled(Builder $builder) { - $builder->macro('withoutControl', function (Builder $builder) { + $builder->macro('uncontrolled', function (Builder $builder) { return $builder->withoutGlobalScope($this); }); } diff --git a/src/Exceptions/QueryNotImplemented.php b/src/Exceptions/QueryNotImplemented.php deleted file mode 100644 index b52a5ca..0000000 --- a/src/Exceptions/QueryNotImplemented.php +++ /dev/null @@ -1,7 +0,0 @@ -priority ?? 1; + return false; } - /** - * Get the name of the perimeter. - * - * @return string - */ - public function name(): string + public function query(Closure $queryCallback): self { - return $this->name ?? Str::of((new \ReflectionClass($this))->getShortName())->beforeLast('Perimeter')->camel()->toString(); + // @TODO: ok mais pas possible de le déclarer globalement alors, seems ok for me + $this->queryCallback = $queryCallback; + return $this; } /** - * Get the final perimeter status. - * - * @return int - */ - public function final(): int - { - return $this->final ?? true; - } - - /** - * Determine if the perimeter matches a given name. - * - * @param string $name + * Get a new control instance for the given attributes. * - * @return bool + * @return static */ - public function matches(string $name) + public static function new() { - return $name === $this->name(); + return (new static); } -} +} \ No newline at end of file diff --git a/src/Perimeters/PerimeterCollection.php b/src/Perimeters/PerimeterCollection.php deleted file mode 100644 index 2b50621..0000000 --- a/src/Perimeters/PerimeterCollection.php +++ /dev/null @@ -1,130 +0,0 @@ -addToCollections($perimeter); - - return $this; - } - - /** - * Add the given perimeter to the arrays of perimeters. - * - * @param Perimeter $perimeter - * - * @return void - */ - protected function addToCollections(Perimeter $perimeter) - { - $this->perimeters[$perimeter->priority][] = $perimeter; - - $this->allPerimeters = collect($this->allPerimeters) - ->push($perimeter) - ->sortBy('priority') - ->all(); - } - - /** - * Find the first perimeter matching a given name. - * - * @param string $name - * - * @throws \RuntimeException - * - * @return Perimeter - */ - public function match(string $name) - { - $perimeters = $this->get(); - - $perimeter = $this->matchAgainstPerimeters($perimeters, $name); - - return $this->handleMatchedPerimeter($name, $perimeter); - } - - /** - * Determine if a perimeter in the array matches the name. - * - * @param Perimeter[] $perimeters - * @param \Illuminate\Http\Request $request - * - * @return Perimeter|null - */ - protected function matchAgainstPerimeters(array $perimeters, string $name) - { - return collect($perimeters)->first( - fn (Perimeter $perimeter) => $perimeter->matches($name) - ); - } - - /** - * Handle the matched perimeter. - * - * @param string $name - * @param Perimeter|null $perimeter - * - * @throws \RuntimeException - * - * @return Perimeter - */ - protected function handleMatchedPerimeter(string $name, $perimeter) - { - if (!is_null($perimeter)) { - return $perimeter; - } - - throw new \RuntimeException(sprintf( - 'The perimeter %s could not be found.', - $name - )); - } - - /** - * Get perimeters from the collection by priority. - * - * @param int|null $priority - * - * @return Perimeter[] - */ - public function get(int $priority = null) - { - return is_null($priority) ? $this->getPerimeters() : Arr::get($this->perimeters, $priority, []); - } - - /** - * Get all of the perimeters in the collection. - * - * @return Perimeter[] - */ - public function getPerimeters() - { - return array_values($this->allPerimeters); - } -} diff --git a/src/Perimeters/Perimeters.php b/src/Perimeters/Perimeters.php deleted file mode 100644 index 304382a..0000000 --- a/src/Perimeters/Perimeters.php +++ /dev/null @@ -1,91 +0,0 @@ -perimeters = new PerimeterCollection(); - } - - /** - * Add a route to the underlying route collection. - * - * @param Perimeter $perimeter - * - * @return PerimeterCollection - */ - public function addPerimeter(Perimeter $perimeter): PerimeterCollection - { - return $this->perimeters->add($perimeter); - } - - /** - * Find the perimeter matching a given name. - * - * @param \Illuminate\Http\Request $request - * - * @return Perimeter - */ - public function findPerimeter(string $name) - { - $perimeter = $this->perimeters->match($name); - - return $perimeter; - } - - /** - * Get the plain perimeters. - * - * @return Perimeter[] - */ - public function getPerimeters() - { - return $this->perimeters->getPerimeters(); - } - - /** - * Register all the perimeter classes in the given directory. - * - * @param string $directory - * - * @return void - */ - public function perimetersIn($directory) - { - $namespace = app()->getNamespace(); - - foreach ((new Finder())->in($directory)->files() as $perimeter) { - $perimeter = $namespace.str_replace( - ['/', '.php'], - ['\\', ''], - Str::after($perimeter->getPathname(), app_path().DIRECTORY_SEPARATOR) - ); - - if ( - is_subclass_of($perimeter, \Lomkit\Access\Perimeters\Perimeter::class) && - !(new ReflectionClass($perimeter))->isAbstract() - ) { - $this->addPerimeter($perimeter); - } - } - } -} diff --git a/src/PoliciesControlled.php b/src/PoliciesControlled.php deleted file mode 100644 index e6f35b2..0000000 --- a/src/PoliciesControlled.php +++ /dev/null @@ -1,70 +0,0 @@ - - */ - public function getControl(): string - { - return ''; - } - - /** - * Return a new control instance. - * - * @return Control - */ - public function newControl(): Control - { - return App::make($this->getControl()); - } - - /** - * Determine if any model can be viewed by the user. - */ - public function viewAny(Model $user) - { - return $this->newControl()->getConcernedPerimeters()->isNotEmpty(); - } - - /** - * Determine if the given model can be viewed by the user. - */ - public function view(Model $user, Model $model) - { - return $this->newControl()->runPolicy(__FUNCTION__, $user, $model); - } - - /** - * Determine if the model can be created by the user. - */ - public function create(Model $user) - { - return $this->newControl()->getConcernedPerimeters()->isNotEmpty(); - } - - /** - * Determine if the given model can be updated by the user. - */ - public function update(Model $user, Model $model) - { - return $this->newControl()->runPolicy(__FUNCTION__, $user, $model); - } - - /** - * Determine if the given model can be deleted by the user. - */ - public function delete(Model $user, Model $model) - { - return $this->newControl()->runPolicy(__FUNCTION__, $user, $model); - } -} diff --git a/src/QueriesControlled.php b/src/QueriesControlled.php deleted file mode 100644 index 69cc9cf..0000000 --- a/src/QueriesControlled.php +++ /dev/null @@ -1,39 +0,0 @@ - - */ - public function getControl(): string - { - return ''; - } - - /** - * Return a new control instance. - * - * @return Control - */ - public function newControl(): Control - { - return App::make($this->getControl()); - } - - /** - * Boot the access controlled trait for a model. - * - * @return void - */ - public static function bootQueriesControlled() - { - static::addGlobalScope(new ControlScope()); - } -} diff --git a/tests/Feature/PerimetersTest.php b/tests/Feature/PerimetersTest.php new file mode 100644 index 0000000..9a8acfa --- /dev/null +++ b/tests/Feature/PerimetersTest.php @@ -0,0 +1,32 @@ +update(['should_client' => true]); + + $this->assertTrue((new ClientPerimeter())->should(Auth::user(), 'create', new Model)); + } + + public function test_should_not_client_perimeter(): void + { + Auth::user()->update(['should_client' => false]); + + $this->assertFalse((new ClientPerimeter())->should(Auth::user(), 'create', new Model)); + } + + // @TODO: other perimeters +} diff --git a/tests/Feature/QueriesTest.php b/tests/Feature/QueriesTest.php deleted file mode 100644 index eef29ed..0000000 --- a/tests/Feature/QueriesTest.php +++ /dev/null @@ -1,174 +0,0 @@ -create(['is_client' => true]); - - $this->assertThrows( - fn () => NotImplementedQueryModel::query()->get(), - QueryNotImplemented::class - ); - } - - public function test_should_first_perimeter(): void - { - Cache::set('model-should-client', true); - Cache::set('model-should-site', true); - Cache::set('model-should-own', true); - - $model = ModelFactory::new()->create(['is_client' => true]); - ModelFactory::new()->create(['is_site' => true]); - ModelFactory::new()->create(['is_own' => true]); - ModelFactory::new()->create(); - - $this->assertEquals( - [$model->fresh()->toArray()], - Model::query()->get()->toArray() - ); - } - - public function test_should_second_perimeter(): void - { - Cache::set('model-should-client', false); - Cache::set('model-should-site', true); - Cache::set('model-should-own', true); - - ModelFactory::new()->create(['is_client' => true]); - $model = ModelFactory::new()->create(['is_site' => true]); - ModelFactory::new()->create(['is_own' => true]); - - $this->assertEquals( - [$model->fresh()->toArray()], - Model::query()->get()->toArray() - ); - } - - public function test_should_third_perimeter(): void - { - Cache::set('model-should-client', false); - Cache::set('model-should-site', false); - Cache::set('model-should-own', true); - - ModelFactory::new()->create(['is_client' => true]); - ModelFactory::new()->create(['is_site' => true]); - $model = ModelFactory::new()->create(['is_own' => true]); - - $this->assertEquals( - [$model->fresh()->toArray()], - Model::query()->get()->toArray() - ); - } - - public function test_should_not_final_perimeter(): void - { - Cache::set('model-should-shared', true); - Cache::set('model-should-client', false); - Cache::set('model-should-site', false); - Cache::set('model-should-own', true); - - ModelFactory::new()->create(['is_client' => true]); - ModelFactory::new()->create(['is_site' => true]); - $modelOwn = ModelFactory::new()->create(['is_own' => true]); - $modelOwnAndShared = ModelFactory::new()->create(['is_own' => true, 'is_shared' => true]); - $modelShared = ModelFactory::new()->create(['is_shared' => true]); - - $this->assertEquals( - [$modelOwn->fresh()->toArray(), $modelOwnAndShared->fresh()->toArray(), $modelShared->fresh()->toArray()], - Model::query()->get()->toArray() - ); - } - - public function test_should_not_final_perimeter_with_no_other_perimeter(): void - { - Cache::set('model-should-shared', true); - Cache::set('model-should-client', false); - Cache::set('model-should-site', false); - Cache::set('model-should-own', false); - - ModelFactory::new()->create(['is_client' => true]); - ModelFactory::new()->create(['is_site' => true]); - ModelFactory::new()->create(['is_own' => true]); - $modelOwnAndShared = ModelFactory::new()->create(['is_own' => true, 'is_shared' => true]); - $modelShared = ModelFactory::new()->create(['is_shared' => true]); - - $this->assertEquals( - [$modelOwnAndShared->fresh()->toArray(), $modelShared->fresh()->toArray()], - Model::query()->get()->toArray() - ); - } - - public function test_without_control_scope(): void - { - Cache::set('model-should-client', true); - Cache::set('model-should-site', true); - Cache::set('model-should-own', true); - - $models = - ModelFactory::new() - ->count(3) - ->create( - new Sequence( - ['is_client' => true], - ['is_site' => true], - ['is_own' => true], - ) - ); - - $this->assertEquals( - $models->fresh()->toArray(), - Model::query()->withoutControl()->get()->toArray() - ); - } - - public function test_unauthenticated(): void - { - Auth::logout(); - - ModelFactory::new() - ->count(3) - ->create( - new Sequence( - ['is_client' => true], - ['is_site' => true], - ['is_own' => true], - ) - ); - - $this->assertEquals( - [], - Model::query()->get()->toArray() - ); - } - - public function test_default_query(): void - { - ModelFactory::new() - ->count(3) - ->create( - new Sequence( - ['is_client' => true], - ['is_site' => true], - ['is_own' => true], - ) - ); - - $this->assertEquals( - [], - Model::query()->get()->toArray() - ); - } -} diff --git a/tests/Feature/TestCase.php b/tests/Feature/TestCase.php index 1a07760..ccad5cc 100644 --- a/tests/Feature/TestCase.php +++ b/tests/Feature/TestCase.php @@ -3,13 +3,10 @@ namespace Lomkit\Access\Tests\Feature; use Lomkit\Access\Tests\Support\Database\Factories\UserFactory; -use Lomkit\Access\Tests\Support\Traits\InteractsWithAuthorization; use Lomkit\Access\Tests\TestCase as BaseTestCase; class TestCase extends BaseTestCase { - use InteractsWithAuthorization; - protected function setUp(): void { parent::setUp(); diff --git a/tests/Support/Access/Controls/ModelControl.php b/tests/Support/Access/Controls/ModelControl.php index 6db803d..d7de697 100644 --- a/tests/Support/Access/Controls/ModelControl.php +++ b/tests/Support/Access/Controls/ModelControl.php @@ -3,74 +3,28 @@ namespace Lomkit\Access\Tests\Support\Access\Controls; use Illuminate\Database\Eloquent\Builder; -use Illuminate\Database\Eloquent\Model; -use Illuminate\Support\Facades\Cache; use Lomkit\Access\Controls\Control; +use Lomkit\Access\Tests\Support\Access\Perimeters\ClientPerimeter; +use Lomkit\Access\Tests\Support\Access\Perimeters\GlobalPerimeter; +use Lomkit\Access\Tests\Support\Access\Perimeters\OwnPerimeter; class ModelControl extends Control { - protected function shouldShared() - { - return Cache::get('model-should-shared', false); - } - - protected function shouldClient() - { - return Cache::get('model-should-client', false); - } - - protected function shouldSite() - { - return Cache::get('model-should-site', false); - } - - protected function shouldOwn() - { - return Cache::get('model-should-own', false); - } - - public function sharedQuery(Builder $query) - { - $query->orWhere('is_shared', true); - } - - public function clientQuery(Builder $query) - { - $query->orWhere('is_client', true); - } - - public function siteQuery(Builder $query) - { - $query->orWhere('is_site', true); - } - - public function ownQuery(Builder $query) - { - $query->orWhere('is_own', true); - } - - public function fallbackQuery(Builder $query): Builder - { - return $query->whereRaw('0 = 1'); - } - - public function sharedPolicy(string $method, Model $user, Model $model): bool - { - return true; - } - - public function clientPolicy(string $method, Model $user, Model $model): bool - { - return true; - } - - public function sitePolicy(string $method, Model $user, Model $model): bool - { - return true; - } - - public function ownPolicy(string $method, Model $user, Model $model): bool - { - return true; - } -} + protected function perimeters(): array + { + return [ + GlobalPerimeter::new() + ->query(function (Builder $query) { + $query->where('is_global', true); + }), + ClientPerimeter::new() + ->query(function (Builder $query) { + $query->where('is_client', true); + }), + OwnPerimeter::new() + ->query(function (Builder $query) { + $query->where('is_own', true); + }), + ]; + } +} \ No newline at end of file diff --git a/tests/Support/Access/Controls/NotImplementedQueryControl.php b/tests/Support/Access/Controls/NotImplementedQueryControl.php deleted file mode 100644 index 1a45dad..0000000 --- a/tests/Support/Access/Controls/NotImplementedQueryControl.php +++ /dev/null @@ -1,30 +0,0 @@ -whereRaw('0 = 1'); - } -} diff --git a/tests/Support/Access/Perimeters/ClientPerimeter.php b/tests/Support/Access/Perimeters/ClientPerimeter.php index 57c9439..f652625 100644 --- a/tests/Support/Access/Perimeters/ClientPerimeter.php +++ b/tests/Support/Access/Perimeters/ClientPerimeter.php @@ -2,11 +2,14 @@ namespace Lomkit\Access\Tests\Support\Access\Perimeters; +use Illuminate\Contracts\Auth\Authenticatable; +use Illuminate\Database\Eloquent\Model; use Lomkit\Access\Perimeters\Perimeter; class ClientPerimeter extends Perimeter { - public string $name = 'client'; - - public int $priority = 2; -} + public function should(Authenticatable $user, string $method, Model $model): bool + { + return $user->should_client; + } +} \ No newline at end of file diff --git a/tests/Support/Access/Perimeters/GlobalPerimeter.php b/tests/Support/Access/Perimeters/GlobalPerimeter.php new file mode 100644 index 0000000..a57e131 --- /dev/null +++ b/tests/Support/Access/Perimeters/GlobalPerimeter.php @@ -0,0 +1,15 @@ +should_global; + } +} \ No newline at end of file diff --git a/tests/Support/Access/Perimeters/OwnPerimeter.php b/tests/Support/Access/Perimeters/OwnPerimeter.php index 2859276..26b1a4e 100644 --- a/tests/Support/Access/Perimeters/OwnPerimeter.php +++ b/tests/Support/Access/Perimeters/OwnPerimeter.php @@ -2,11 +2,14 @@ namespace Lomkit\Access\Tests\Support\Access\Perimeters; +use Illuminate\Contracts\Auth\Authenticatable; +use Illuminate\Database\Eloquent\Model; use Lomkit\Access\Perimeters\Perimeter; class OwnPerimeter extends Perimeter { - public string $name = 'own'; - - public int $priority = 4; -} + public function should(Authenticatable $user, string $method, Model $model): bool + { + return $user->should_own; + } +} \ No newline at end of file diff --git a/tests/Support/Access/Perimeters/SharedPerimeter.php b/tests/Support/Access/Perimeters/SharedPerimeter.php index 560a53a..849e7d1 100644 --- a/tests/Support/Access/Perimeters/SharedPerimeter.php +++ b/tests/Support/Access/Perimeters/SharedPerimeter.php @@ -2,13 +2,14 @@ namespace Lomkit\Access\Tests\Support\Access\Perimeters; +use Illuminate\Contracts\Auth\Authenticatable; +use Illuminate\Database\Eloquent\Model; use Lomkit\Access\Perimeters\Perimeter; class SharedPerimeter extends Perimeter { - public string $name = 'shared'; - - public bool $final = false; - - public int $priority = 1; -} + public function should(Authenticatable $user, string $method, Model $model): bool + { + return $user->should_shared; + } +} \ No newline at end of file diff --git a/tests/Support/Access/Perimeters/SitePerimeter.php b/tests/Support/Access/Perimeters/SitePerimeter.php deleted file mode 100644 index 6eae5ca..0000000 --- a/tests/Support/Access/Perimeters/SitePerimeter.php +++ /dev/null @@ -1,12 +0,0 @@ - - */ - public function getControl(): string - { - return ModelControl::class; - } -} diff --git a/tests/Support/Database/Factories/UserFactory.php b/tests/Support/Database/Factories/UserFactory.php index c9a5733..eddf69b 100644 --- a/tests/Support/Database/Factories/UserFactory.php +++ b/tests/Support/Database/Factories/UserFactory.php @@ -31,6 +31,10 @@ public function definition(): array 'email_verified_at' => now(), 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 'remember_token' => Str::random(10), + 'should_shared' => false, + 'should_global' => false, + 'should_own' => false, + 'should_client' => false, ]; } diff --git a/tests/Support/Database/migrations/2014_00_00_000000_create_users_table.php b/tests/Support/Database/migrations/2014_00_00_000000_create_users_table.php index 290c232..161a152 100644 --- a/tests/Support/Database/migrations/2014_00_00_000000_create_users_table.php +++ b/tests/Support/Database/migrations/2014_00_00_000000_create_users_table.php @@ -18,6 +18,10 @@ public function up() $table->string('email')->unique(); $table->timestamp('email_verified_at')->nullable(); $table->string('password'); + $table->boolean('should_shared'); + $table->boolean('should_global'); + $table->boolean('should_client'); + $table->boolean('should_own'); $table->rememberToken(); $table->timestamps(); }); diff --git a/tests/Support/Database/migrations/2023_04_00_000000_create_models_table.php b/tests/Support/Database/migrations/2023_04_00_000000_create_models_table.php index 6305a45..ba86a4e 100644 --- a/tests/Support/Database/migrations/2023_04_00_000000_create_models_table.php +++ b/tests/Support/Database/migrations/2023_04_00_000000_create_models_table.php @@ -15,13 +15,9 @@ public function up() Schema::create('models', function (Blueprint $table) { $table->id(); $table->string('name'); - $table->bigInteger('number'); - $table->boolean('is_shared')->default(false); - $table->boolean('is_client')->default(false); - $table->boolean('is_site')->default(false); - $table->boolean('is_own')->default(false); $table->string('string')->nullable(); $table->string('unique')->unique()->nullable(); + $table->bigInteger('number'); $table->timestamps(); }); } diff --git a/tests/Support/Models/Model.php b/tests/Support/Models/Model.php index 6c2677a..6c80c5b 100644 --- a/tests/Support/Models/Model.php +++ b/tests/Support/Models/Model.php @@ -4,25 +4,12 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model as BaseModel; -use Lomkit\Access\Controls\Control; -use Lomkit\Access\QueriesControlled; -use Lomkit\Access\Tests\Support\Access\Controls\ModelControl; +use Lomkit\Access\Controls\HasControl; use Lomkit\Access\Tests\Support\Database\Factories\ModelFactory; class Model extends BaseModel { - use HasFactory; - use QueriesControlled; - - /** - * Return the control instance string. - * - * @return class-string - */ - public function getControl(): string - { - return ModelControl::class; - } + use HasFactory, HasControl; protected static function newFactory() { @@ -32,10 +19,4 @@ protected static function newFactory() protected $fillable = [ 'id', ]; - - protected $casts = [ - 'is_client' => 'bool', - 'is_site' => 'bool', - 'is_own' => 'bool', - ]; } diff --git a/tests/Support/Models/NotImplementedQueryModel.php b/tests/Support/Models/NotImplementedQueryModel.php deleted file mode 100644 index f269c3d..0000000 --- a/tests/Support/Models/NotImplementedQueryModel.php +++ /dev/null @@ -1,14 +0,0 @@ - 'datetime', + 'should_shared' => 'bool', + 'should_global' => 'bool', + 'should_client' => 'bool', + 'should_own' => 'bool', ]; } diff --git a/tests/Support/Traits/InteractsWithAuthorization.php b/tests/Support/Traits/InteractsWithAuthorization.php deleted file mode 100644 index 22af165..0000000 --- a/tests/Support/Traits/InteractsWithAuthorization.php +++ /dev/null @@ -1,22 +0,0 @@ -actingAs($user ?? $this->resolveAuthFactoryClass()::new()->create(), $driver); - } - - protected function resolveAuthFactoryClass() - { - return null; - } - - protected function assertUnauthorizedResponse($response) - { - $response->assertStatus(403); - $response->assertJson(['message' => 'This action is unauthorized.']); - } -} diff --git a/tests/TestCase.php b/tests/TestCase.php index 9b306d1..250dc20 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -7,11 +7,6 @@ use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabaseState; use Lomkit\Access\AccessServiceProvider; -use Lomkit\Access\Perimeters\Perimeters; -use Lomkit\Access\Tests\Support\Access\Perimeters\ClientPerimeter; -use Lomkit\Access\Tests\Support\Access\Perimeters\OwnPerimeter; -use Lomkit\Access\Tests\Support\Access\Perimeters\SharedPerimeter; -use Lomkit\Access\Tests\Support\Access\Perimeters\SitePerimeter; use Orchestra\Testbench\TestCase as BaseTestCase; class TestCase extends BaseTestCase @@ -62,26 +57,11 @@ protected function refreshInMemoryDatabase() */ protected function defineEnvironment($app) { - foreach ( - [ - SharedPerimeter::class, - ClientPerimeter::class, - OwnPerimeter::class, - SitePerimeter::class, - ] - as $perimeter - ) { - app(Perimeters::class) - ->addPerimeter(new $perimeter()); - } - tap($app->make('config'), function (Repository $config) { $config->set('auth.guards.web', [ 'driver' => 'session', 'provider' => 'users', ]); - - $config->set('access-control.perimeters.path', __DIR__); }); } @@ -98,4 +78,20 @@ protected function getPackageProviders($app) AccessServiceProvider::class, ]; } + + protected function withAuthenticatedUser($user = null, string $driver = 'web') + { + return $this->actingAs($user ?? $this->resolveAuthFactoryClass()::new()->create(), $driver); + } + + protected function resolveAuthFactoryClass() + { + return null; + } + + protected function assertUnauthorizedResponse($response) + { + $response->assertStatus(403); + $response->assertJson(['message' => 'This action is unauthorized.']); + } } diff --git a/tests/Unit/ControlsTest.php b/tests/Unit/ControlsTest.php deleted file mode 100644 index 1833701..0000000 --- a/tests/Unit/ControlsTest.php +++ /dev/null @@ -1,61 +0,0 @@ -shouldAllowMockingProtectedMethods() - ->makePartial(); - - $controlMock->shouldReceive('shouldClient')->with()->once()->andReturn(true); - $controlMock->shouldReceive('shouldSite')->with()->never(); - $controlMock->shouldReceive('shouldOwn')->with()->never(); - - $this->assertTrue( - $controlMock - ->should(new \Lomkit\Access\Tests\Support\Access\Perimeters\ClientPerimeter()) - ); - } - - public function test_should_second_perimeter() - { - $controlMock = Mockery::mock(\Lomkit\Access\Tests\Support\Access\Controls\ModelControl::class) - ->shouldAllowMockingProtectedMethods() - ->makePartial(); - - $controlMock->shouldReceive('shouldClient')->with()->never(); - $controlMock->shouldReceive('shouldSite')->with()->once()->andReturn(true); - $controlMock->shouldReceive('shouldOwn')->with()->never(); - - $this->assertTrue( - $controlMock - ->should(new \Lomkit\Access\Tests\Support\Access\Perimeters\SitePerimeter()) - ); - } - - public function test_should_third_perimeter() - { - $controlMock = Mockery::mock(\Lomkit\Access\Tests\Support\Access\Controls\ModelControl::class) - ->shouldAllowMockingProtectedMethods() - ->makePartial(); - - $controlMock->shouldReceive('shouldClient')->with()->never(); - $controlMock->shouldReceive('shouldSite')->with()->never(); - $controlMock->shouldReceive('shouldOwn')->with()->once()->andReturn(true); - - $this->assertTrue( - $controlMock - ->should(new \Lomkit\Access\Tests\Support\Access\Perimeters\OwnPerimeter()) - ); - } -} diff --git a/tests/Unit/PoliciesTest.php b/tests/Unit/PoliciesTest.php deleted file mode 100644 index 013603e..0000000 --- a/tests/Unit/PoliciesTest.php +++ /dev/null @@ -1,85 +0,0 @@ -shouldAllowMockingProtectedMethods() - ->makePartial(); - - Cache::set('model-should-own', true); - - Gate::policy(Model::class, ModelPolicy::class); - - $model = \Lomkit\Access\Tests\Support\Database\Factories\ModelFactory::new()->create(); - - $controlMock->shouldReceive('clientPolicy')->never(); - $controlMock->shouldReceive('sitePolicy')->never(); - $controlMock->shouldReceive('ownPolicy')->with('view', \Illuminate\Support\Facades\Auth::user(), $model)->once()->andReturn(true); - - $this->assertTrue( - $controlMock->runPolicy('view', Auth::user(), $model) - ); - } - - public function test_policy_update() - { - $controlMock = Mockery::mock(\Lomkit\Access\Tests\Support\Access\Controls\ModelControl::class, [app(Perimeters::class)]) - ->shouldAllowMockingProtectedMethods() - ->makePartial(); - - Cache::set('model-should-site', true); - Cache::set('model-should-own', true); - - Gate::policy(Model::class, ModelPolicy::class); - - $model = \Lomkit\Access\Tests\Support\Database\Factories\ModelFactory::new()->create(); - - $controlMock->shouldReceive('clientPolicy')->never(); - $controlMock->shouldReceive('sitePolicy')->with('update', \Illuminate\Support\Facades\Auth::user(), $model)->once()->andReturn(true); - $controlMock->shouldReceive('ownPolicy')->never(); - - $this->assertTrue( - $controlMock->runPolicy('update', Auth::user(), $model) - ); - } - - public function test_policy_delete() - { - $controlMock = Mockery::mock(\Lomkit\Access\Tests\Support\Access\Controls\ModelControl::class, [app(Perimeters::class)]) - ->shouldAllowMockingProtectedMethods() - ->makePartial(); - - Cache::set('model-should-client', true); - Cache::set('model-should-site', true); - Cache::set('model-should-own', true); - - Gate::policy(Model::class, ModelPolicy::class); - - $model = \Lomkit\Access\Tests\Support\Database\Factories\ModelFactory::new()->create(); - - $controlMock->shouldReceive('clientPolicy')->with('delete', \Illuminate\Support\Facades\Auth::user(), $model)->once()->andReturn(true); - $controlMock->shouldReceive('sitePolicy')->never(); - $controlMock->shouldReceive('ownPolicy')->never(); - - $this->assertTrue( - $controlMock->runPolicy('delete', Auth::user(), $model) - ); - } -} diff --git a/tests/Unit/TestCase.php b/tests/Unit/TestCase.php deleted file mode 100644 index 839a1db..0000000 --- a/tests/Unit/TestCase.php +++ /dev/null @@ -1,24 +0,0 @@ -withAuthenticatedUser(); - } - - protected function resolveAuthFactoryClass() - { - return UserFactory::class; - } -} From 280365c7b6ea5308fe6917f24b4962045dab0abf Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Wed, 12 Feb 2025 21:43:39 +0000 Subject: [PATCH 02/13] Apply fixes from StyleCI --- src/Controls/Control.php | 16 +++++++++------- src/Controls/HasControl.php | 6 ++---- src/Perimeters/Perimeter.php | 5 +++-- tests/Feature/PerimetersTest.php | 10 ++-------- tests/Support/Access/Controls/ModelControl.php | 4 ++-- .../Access/Perimeters/ClientPerimeter.php | 2 +- .../Access/Perimeters/GlobalPerimeter.php | 2 +- tests/Support/Access/Perimeters/OwnPerimeter.php | 2 +- .../Access/Perimeters/SharedPerimeter.php | 2 +- tests/Support/Database/Factories/UserFactory.php | 2 +- tests/Support/Models/Model.php | 3 ++- tests/Support/Models/User.php | 8 ++++---- 12 files changed, 29 insertions(+), 33 deletions(-) diff --git a/src/Controls/Control.php b/src/Controls/Control.php index bfdfe22..0f52c37 100644 --- a/src/Controls/Control.php +++ b/src/Controls/Control.php @@ -24,7 +24,7 @@ class Control public static $namespace = 'App\\Access\\Controls\\'; /** - * Get the perimeters for the current control + * Get the perimeters for the current control. * * @return array */ @@ -36,7 +36,8 @@ protected function perimeters(): array /** * Specify the callback that should be invoked to guess control names. * - * @param callable(class-string<\Illuminate\Database\Eloquent\Model>): class-string<\Lomkit\Access\Controls\Control> $callback + * @param callable(class-string<\Illuminate\Database\Eloquent\Model>): class-string<\Lomkit\Access\Controls\Control> $callback + * * @return void */ public static function guessControlNamesUsing(callable $callback) @@ -44,13 +45,13 @@ public static function guessControlNamesUsing(callable $callback) static::$controlNameResolver = $callback; } - /** * Get a new control instance for the given model name. * * @template TClass of \Illuminate\Database\Eloquent\Model * - * @param class-string $modelName + * @param class-string $modelName + * * @return \Lomkit\Access\Controls\Control */ public static function controlForModel(string $modelName) @@ -69,7 +70,7 @@ public static function controlForModel(string $modelName) */ public static function new() { - return (new static); + return new static(); } /** @@ -77,7 +78,8 @@ public static function new() * * @template TClass of \Illuminate\Database\Eloquent\Model * - * @param class-string $modelName + * @param class-string $modelName + * * @return class-string<\Lomkit\Access\Controls\Control> */ public static function resolveControlName(string $modelName) @@ -110,4 +112,4 @@ protected static function appNamespace() return 'App\\'; } } -} \ No newline at end of file +} diff --git a/src/Controls/HasControl.php b/src/Controls/HasControl.php index 89bd966..e3b6e27 100644 --- a/src/Controls/HasControl.php +++ b/src/Controls/HasControl.php @@ -3,8 +3,6 @@ namespace Lomkit\Access\Controls; use Illuminate\Database\Eloquent\Factories\Factory; -use Illuminate\Support\Facades\App; -use Illuminate\Support\Str; trait HasControl { @@ -15,7 +13,7 @@ trait HasControl */ public static function bootHasControl() { - static::addGlobalScope(new HasControlScope); + static::addGlobalScope(new HasControlScope()); } /** @@ -39,4 +37,4 @@ protected static function newControl(): Control|null { return static::$control::new() ?? null; } -} \ No newline at end of file +} diff --git a/src/Perimeters/Perimeter.php b/src/Perimeters/Perimeter.php index f195d76..0c005de 100644 --- a/src/Perimeters/Perimeter.php +++ b/src/Perimeters/Perimeter.php @@ -21,6 +21,7 @@ public function query(Closure $queryCallback): self { // @TODO: ok mais pas possible de le déclarer globalement alors, seems ok for me $this->queryCallback = $queryCallback; + return $this; } @@ -31,6 +32,6 @@ public function query(Closure $queryCallback): self */ public static function new() { - return (new static); + return new static(); } -} \ No newline at end of file +} diff --git a/tests/Feature/PerimetersTest.php b/tests/Feature/PerimetersTest.php index 9a8acfa..6d0387f 100644 --- a/tests/Feature/PerimetersTest.php +++ b/tests/Feature/PerimetersTest.php @@ -2,15 +2,9 @@ namespace Lomkit\Access\Tests\Feature; -use Illuminate\Database\Eloquent\Factories\Sequence; use Illuminate\Support\Facades\Auth; -use Illuminate\Support\Facades\Cache; -use Lomkit\Access\Exceptions\QueryNotImplemented; use Lomkit\Access\Tests\Support\Access\Perimeters\ClientPerimeter; -use Lomkit\Access\Tests\Support\Database\Factories\ModelFactory; use Lomkit\Access\Tests\Support\Models\Model; -use Lomkit\Access\Tests\Support\Models\NotImplementedQueryModel; -use Lomkit\Access\Tests\Support\Models\User; class PerimetersTest extends TestCase { @@ -18,14 +12,14 @@ public function test_should_client_perimeter(): void { Auth::user()->update(['should_client' => true]); - $this->assertTrue((new ClientPerimeter())->should(Auth::user(), 'create', new Model)); + $this->assertTrue((new ClientPerimeter())->should(Auth::user(), 'create', new Model())); } public function test_should_not_client_perimeter(): void { Auth::user()->update(['should_client' => false]); - $this->assertFalse((new ClientPerimeter())->should(Auth::user(), 'create', new Model)); + $this->assertFalse((new ClientPerimeter())->should(Auth::user(), 'create', new Model())); } // @TODO: other perimeters diff --git a/tests/Support/Access/Controls/ModelControl.php b/tests/Support/Access/Controls/ModelControl.php index d7de697..d66a34c 100644 --- a/tests/Support/Access/Controls/ModelControl.php +++ b/tests/Support/Access/Controls/ModelControl.php @@ -25,6 +25,6 @@ protected function perimeters(): array ->query(function (Builder $query) { $query->where('is_own', true); }), - ]; + ]; } -} \ No newline at end of file +} diff --git a/tests/Support/Access/Perimeters/ClientPerimeter.php b/tests/Support/Access/Perimeters/ClientPerimeter.php index f652625..4424748 100644 --- a/tests/Support/Access/Perimeters/ClientPerimeter.php +++ b/tests/Support/Access/Perimeters/ClientPerimeter.php @@ -12,4 +12,4 @@ public function should(Authenticatable $user, string $method, Model $model): boo { return $user->should_client; } -} \ No newline at end of file +} diff --git a/tests/Support/Access/Perimeters/GlobalPerimeter.php b/tests/Support/Access/Perimeters/GlobalPerimeter.php index a57e131..3b953dc 100644 --- a/tests/Support/Access/Perimeters/GlobalPerimeter.php +++ b/tests/Support/Access/Perimeters/GlobalPerimeter.php @@ -12,4 +12,4 @@ public function should(Authenticatable $user, string $method, Model $model): boo { return $user->should_global; } -} \ No newline at end of file +} diff --git a/tests/Support/Access/Perimeters/OwnPerimeter.php b/tests/Support/Access/Perimeters/OwnPerimeter.php index 26b1a4e..d34db96 100644 --- a/tests/Support/Access/Perimeters/OwnPerimeter.php +++ b/tests/Support/Access/Perimeters/OwnPerimeter.php @@ -12,4 +12,4 @@ public function should(Authenticatable $user, string $method, Model $model): boo { return $user->should_own; } -} \ No newline at end of file +} diff --git a/tests/Support/Access/Perimeters/SharedPerimeter.php b/tests/Support/Access/Perimeters/SharedPerimeter.php index 849e7d1..c087d3e 100644 --- a/tests/Support/Access/Perimeters/SharedPerimeter.php +++ b/tests/Support/Access/Perimeters/SharedPerimeter.php @@ -12,4 +12,4 @@ public function should(Authenticatable $user, string $method, Model $model): boo { return $user->should_shared; } -} \ No newline at end of file +} diff --git a/tests/Support/Database/Factories/UserFactory.php b/tests/Support/Database/Factories/UserFactory.php index eddf69b..16fdccf 100644 --- a/tests/Support/Database/Factories/UserFactory.php +++ b/tests/Support/Database/Factories/UserFactory.php @@ -33,7 +33,7 @@ public function definition(): array 'remember_token' => Str::random(10), 'should_shared' => false, 'should_global' => false, - 'should_own' => false, + 'should_own' => false, 'should_client' => false, ]; } diff --git a/tests/Support/Models/Model.php b/tests/Support/Models/Model.php index 6c80c5b..7b1d43c 100644 --- a/tests/Support/Models/Model.php +++ b/tests/Support/Models/Model.php @@ -9,7 +9,8 @@ class Model extends BaseModel { - use HasFactory, HasControl; + use HasFactory; + use HasControl; protected static function newFactory() { diff --git a/tests/Support/Models/User.php b/tests/Support/Models/User.php index 8fd7312..db081a8 100644 --- a/tests/Support/Models/User.php +++ b/tests/Support/Models/User.php @@ -49,9 +49,9 @@ protected static function newFactory() */ protected $casts = [ 'email_verified_at' => 'datetime', - 'should_shared' => 'bool', - 'should_global' => 'bool', - 'should_client' => 'bool', - 'should_own' => 'bool', + 'should_shared' => 'bool', + 'should_global' => 'bool', + 'should_client' => 'bool', + 'should_own' => 'bool', ]; } From 8cae9d8a02d8893d8f42af6a207ab18b657e426f Mon Sep 17 00:00:00 2001 From: Gautier DELEGLISE Date: Thu, 13 Feb 2025 18:28:38 +0100 Subject: [PATCH 03/13] =?UTF-8?q?=F0=9F=9A=A7=20Perimeters?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/tests.yml | 2 +- composer.json | 2 +- src/Controls/Control.php | 32 ++++- src/Perimeters/Perimeter.php | 15 +- src/Policies/ControlledPolicy.php | 76 ++++++++++ tests/Feature/ControlsTest.php | 133 ++++++++++++++++++ tests/Feature/PerimetersTest.php | 49 ++++++- .../Support/Access/Controls/ModelControl.php | 10 ++ .../Access/Perimeters/ClientPerimeter.php | 2 +- .../Access/Perimeters/GlobalPerimeter.php | 2 +- .../Access/Perimeters/OwnPerimeter.php | 2 +- .../Access/Perimeters/SharedPerimeter.php | 2 +- .../Database/Factories/ModelFactory.php | 4 + .../2023_04_00_000000_create_models_table.php | 5 + tests/Support/Policies/ModelPolicy.php | 11 ++ 15 files changed, 330 insertions(+), 17 deletions(-) create mode 100644 src/Policies/ControlledPolicy.php create mode 100644 tests/Feature/ControlsTest.php create mode 100644 tests/Support/Policies/ModelPolicy.php diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ce16fed..9a2a85c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,7 +14,7 @@ jobs: strategy: fail-fast: false matrix: - php-version: [ '8.1', '8.2', '8.3', '8.4' ] + php-version: [ '8.2', '8.3', '8.4' ] laravel-version: [ '^11.0' ] database: [ 'sqlite', 'mysql', 'pgsql' ] diff --git a/composer.json b/composer.json index 8c83113..f547791 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,7 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "ext-json": "*", "laravel/framework": "^11.0" }, diff --git a/src/Controls/Control.php b/src/Controls/Control.php index 0f52c37..6038f74 100644 --- a/src/Controls/Control.php +++ b/src/Controls/Control.php @@ -3,8 +3,11 @@ namespace Lomkit\Access\Controls; use Illuminate\Container\Container; +use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Contracts\Foundation\Application; +use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Str; +use Lomkit\Access\Perimeters\Perimeter; use Throwable; class Control @@ -26,13 +29,30 @@ class Control /** * Get the perimeters for the current control. * - * @return array + * @return array */ protected function perimeters(): array { return []; } + + public function applies(Model $user, string $method, Model $model): bool + { + foreach ($this->perimeters() as $perimeter) { + if ($perimeter->applies($user)) { + // If the model doesn't exists, it means the method is not related to a model + // so we don't need to activate the should result since we can't compare an existing model + if (!$model->exists) { + return true; + } + return $perimeter->getShouldResult($user, $method, $model); + } + } + + return false; + } + /** * Specify the callback that should be invoked to guess control names. * @@ -40,7 +60,7 @@ protected function perimeters(): array * * @return void */ - public static function guessControlNamesUsing(callable $callback) + public static function guessControlNamesUsing(callable $callback): void { static::$controlNameResolver = $callback; } @@ -54,7 +74,7 @@ public static function guessControlNamesUsing(callable $callback) * * @return \Lomkit\Access\Controls\Control */ - public static function controlForModel(string $modelName) + public static function controlForModel(string $modelName): self { $control = static::resolveControlName($modelName); @@ -68,7 +88,7 @@ public static function controlForModel(string $modelName) * * @return static */ - public static function new() + public static function new(): self { return new static(); } @@ -82,7 +102,7 @@ public static function new() * * @return class-string<\Lomkit\Access\Controls\Control> */ - public static function resolveControlName(string $modelName) + public static function resolveControlName(string $modelName): string { $resolver = static::$controlNameResolver ?? function (string $modelName) { $appNamespace = static::appNamespace(); @@ -102,7 +122,7 @@ public static function resolveControlName(string $modelName) * * @return string */ - protected static function appNamespace() + protected static function appNamespace(): string { try { return Container::getInstance() diff --git a/src/Perimeters/Perimeter.php b/src/Perimeters/Perimeter.php index 0c005de..d0b21c6 100644 --- a/src/Perimeters/Perimeter.php +++ b/src/Perimeters/Perimeter.php @@ -11,15 +11,26 @@ class Perimeter // @TODO: what for shared example ? (final on former project) protected Closure $queryCallback; + protected Closure $shouldCallback; - public function should(Authenticatable $user, string $method, Model $model): bool + public function applies(Model $user): bool { return false; } + public function getShouldResult(Model $user, string $method, Model $model): bool + { + return ($this->shouldCallback)($user, $method, $model); + } + + public function should(Closure $shouldCallback): self + { + $this->shouldCallback = $shouldCallback; + return $this; + } + public function query(Closure $queryCallback): self { - // @TODO: ok mais pas possible de le déclarer globalement alors, seems ok for me $this->queryCallback = $queryCallback; return $this; diff --git a/src/Policies/ControlledPolicy.php b/src/Policies/ControlledPolicy.php new file mode 100644 index 0000000..57b3b14 --- /dev/null +++ b/src/Policies/ControlledPolicy.php @@ -0,0 +1,76 @@ + + */ + protected function getModel(): string + { + return $this->model; + } + + /** + * Return the control instance. + * + * @return Control + */ + protected function getControl(): Control + { + return Control::controlForModel($this->getModel()); + } + + /** + * Determine if any model can be viewed by the user. + */ + public function viewAny(Model $user) + { + return $this->getControl()->should($user, __FUNCTION__, new ($this->getModel())); + } + + /** + * Determine if the given model can be viewed by the user. + */ + public function view(Model $user, Model $model) + { + return $this->getControl()->should($user, __FUNCTION__, $model); + } + + /** + * Determine if the model can be created by the user. + */ + public function create(Model $user) + { + return $this->getControl()->should($user, __FUNCTION__, new ($this->getModel())); + } + + /** + * Determine if the given model can be updated by the user. + */ + public function update(Model $user, Model $model) + { + return $this->getControl()->should($user, __FUNCTION__, $model); + } + + /** + * Determine if the given model can be deleted by the user. + */ + public function delete(Model $user, Model $model) + { + return $this->getControl()->should($user, __FUNCTION__, $model); + } +} \ No newline at end of file diff --git a/tests/Feature/ControlsTest.php b/tests/Feature/ControlsTest.php new file mode 100644 index 0000000..3c24e5a --- /dev/null +++ b/tests/Feature/ControlsTest.php @@ -0,0 +1,133 @@ +assertFalse((new \Lomkit\Access\Tests\Support\Access\Controls\ModelControl())->applies(Auth::user(), 'create', new Model)); + } + + public function test_control_should_view_any_using_client_perimeter(): void + { + Auth::user()->update(['should_client' => true]); + + $this->assertTrue((new \Lomkit\Access\Tests\Support\Access\Controls\ModelControl())->applies(Auth::user(), 'viewAny', new Model)); + } + + public function test_control_should_view_using_client_perimeter(): void + { + Auth::user()->update(['should_client' => true]); + $model = Model::factory() + ->create([ + 'allowed_methods' => 'view', + ]); + + $this->assertTrue((new \Lomkit\Access\Tests\Support\Access\Controls\ModelControl())->applies(Auth::user(), 'view', $model)); + } + + public function test_control_should_not_view_using_client_perimeter(): void + { + Auth::user()->update(['should_client' => true]); + $model = Model::factory() + ->create([ + 'allowed_methods' => 'create', + ]); + + $this->assertFalse((new \Lomkit\Access\Tests\Support\Access\Controls\ModelControl())->applies(Auth::user(), 'view', $model)); + } + + public function test_control_should_create_using_client_perimeter(): void + { + Auth::user()->update(['should_client' => true]); + + $this->assertTrue((new \Lomkit\Access\Tests\Support\Access\Controls\ModelControl())->applies(Auth::user(), 'create', new Model)); + } + + public function test_control_should_update_using_client_perimeter(): void + { + Auth::user()->update(['should_client' => true]); + $model = Model::factory() + ->create([ + 'allowed_methods' => 'update', + ]); + + $this->assertTrue((new \Lomkit\Access\Tests\Support\Access\Controls\ModelControl())->applies(Auth::user(), 'update', $model)); + } + + public function test_control_should_delete_using_client_perimeter(): void + { + Auth::user()->update(['should_client' => true]); + $model = Model::factory() + ->create([ + 'allowed_methods' => 'delete', + ]); + + $this->assertTrue((new \Lomkit\Access\Tests\Support\Access\Controls\ModelControl())->applies(Auth::user(), 'delete', $model)); + } + + public function test_control_should_view_any_using_global_perimeter(): void + { + Auth::user()->update(['should_global' => true]); + + $this->assertTrue((new \Lomkit\Access\Tests\Support\Access\Controls\ModelControl())->applies(Auth::user(), 'viewAny', new Model)); + } + + public function test_control_should_view_using_global_perimeter(): void + { + Auth::user()->update(['should_global' => true]); + $model = Model::factory() + ->create([ + 'allowed_methods' => 'view', + ]); + + $this->assertTrue((new \Lomkit\Access\Tests\Support\Access\Controls\ModelControl())->applies(Auth::user(), 'view', $model)); + } + + public function test_control_should_not_view_using_global_perimeter(): void + { + Auth::user()->update(['should_global' => true]); + $model = Model::factory() + ->create([ + 'allowed_methods' => 'create', + ]); + + $this->assertFalse((new \Lomkit\Access\Tests\Support\Access\Controls\ModelControl())->applies(Auth::user(), 'view', $model)); + } + + public function test_control_should_create_using_global_perimeter(): void + { + Auth::user()->update(['should_global' => true]); + + $this->assertTrue((new \Lomkit\Access\Tests\Support\Access\Controls\ModelControl())->applies(Auth::user(), 'create', new Model)); + } + + public function test_control_should_update_using_global_perimeter(): void + { + Auth::user()->update(['should_global' => true]); + $model = Model::factory() + ->create([ + 'allowed_methods' => 'update', + ]); + + $this->assertTrue((new \Lomkit\Access\Tests\Support\Access\Controls\ModelControl())->applies(Auth::user(), 'update', $model)); + } + + public function test_control_should_delete_using_global_perimeter(): void + { + Auth::user()->update(['should_global' => true]); + $model = Model::factory() + ->create([ + 'allowed_methods' => 'delete', + ]); + + $this->assertTrue((new \Lomkit\Access\Tests\Support\Access\Controls\ModelControl())->applies(Auth::user(), 'delete', $model)); + } +} diff --git a/tests/Feature/PerimetersTest.php b/tests/Feature/PerimetersTest.php index 6d0387f..582dac0 100644 --- a/tests/Feature/PerimetersTest.php +++ b/tests/Feature/PerimetersTest.php @@ -4,6 +4,9 @@ use Illuminate\Support\Facades\Auth; use Lomkit\Access\Tests\Support\Access\Perimeters\ClientPerimeter; +use Lomkit\Access\Tests\Support\Access\Perimeters\GlobalPerimeter; +use Lomkit\Access\Tests\Support\Access\Perimeters\OwnPerimeter; +use Lomkit\Access\Tests\Support\Access\Perimeters\SharedPerimeter; use Lomkit\Access\Tests\Support\Models\Model; class PerimetersTest extends TestCase @@ -12,15 +15,55 @@ public function test_should_client_perimeter(): void { Auth::user()->update(['should_client' => true]); - $this->assertTrue((new ClientPerimeter())->should(Auth::user(), 'create', new Model())); + $this->assertTrue((new ClientPerimeter())->applies(Auth::user(), 'create', new Model)); } public function test_should_not_client_perimeter(): void { Auth::user()->update(['should_client' => false]); - $this->assertFalse((new ClientPerimeter())->should(Auth::user(), 'create', new Model())); + $this->assertFalse((new ClientPerimeter())->applies(Auth::user(), 'create', new Model)); } - // @TODO: other perimeters + public function test_should_global_perimeter(): void + { + Auth::user()->update(['should_global' => true]); + + $this->assertTrue((new GlobalPerimeter)->applies(Auth::user(), 'create', new Model)); + } + + public function test_should_not_global_perimeter(): void + { + Auth::user()->update(['should_global' => false]); + + $this->assertFalse((new GlobalPerimeter)->applies(Auth::user(), 'create', new Model)); + } + + public function test_should_own_perimeter(): void + { + Auth::user()->update(['should_own' => true]); + + $this->assertTrue((new OwnPerimeter())->applies(Auth::user(), 'create', new Model)); + } + + public function test_should_not_own_perimeter(): void + { + Auth::user()->update(['should_own' => false]); + + $this->assertFalse((new OwnPerimeter())->applies(Auth::user(), 'create', new Model)); + } + + public function test_should_shared_perimeter(): void + { + Auth::user()->update(['should_shared' => true]); + + $this->assertTrue((new SharedPerimeter())->applies(Auth::user(), 'create', new Model)); + } + + public function test_should_not_shared_perimeter(): void + { + Auth::user()->update(['should_shared' => false]); + + $this->assertFalse((new SharedPerimeter())->applies(Auth::user(), 'create', new Model)); + } } diff --git a/tests/Support/Access/Controls/ModelControl.php b/tests/Support/Access/Controls/ModelControl.php index d66a34c..ac9849b 100644 --- a/tests/Support/Access/Controls/ModelControl.php +++ b/tests/Support/Access/Controls/ModelControl.php @@ -3,6 +3,7 @@ namespace Lomkit\Access\Tests\Support\Access\Controls; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Model; use Lomkit\Access\Controls\Control; use Lomkit\Access\Tests\Support\Access\Perimeters\ClientPerimeter; use Lomkit\Access\Tests\Support\Access\Perimeters\GlobalPerimeter; @@ -14,14 +15,23 @@ protected function perimeters(): array { return [ GlobalPerimeter::new() + ->should(function (Model $user, string $method, Model $model) { + return str_contains($model->allowed_methods, $method); + }) ->query(function (Builder $query) { $query->where('is_global', true); }), ClientPerimeter::new() + ->should(function (Model $user, string $method, Model $model) { + return str_contains($model->allowed_methods, $method); + }) ->query(function (Builder $query) { $query->where('is_client', true); }), OwnPerimeter::new() + ->should(function (Model $user, string $method, Model $model) { + return str_contains($model->allowed_methods, $method); + }) ->query(function (Builder $query) { $query->where('is_own', true); }), diff --git a/tests/Support/Access/Perimeters/ClientPerimeter.php b/tests/Support/Access/Perimeters/ClientPerimeter.php index 4424748..0e9a980 100644 --- a/tests/Support/Access/Perimeters/ClientPerimeter.php +++ b/tests/Support/Access/Perimeters/ClientPerimeter.php @@ -8,7 +8,7 @@ class ClientPerimeter extends Perimeter { - public function should(Authenticatable $user, string $method, Model $model): bool + public function applies(Model $user): bool { return $user->should_client; } diff --git a/tests/Support/Access/Perimeters/GlobalPerimeter.php b/tests/Support/Access/Perimeters/GlobalPerimeter.php index 3b953dc..6676375 100644 --- a/tests/Support/Access/Perimeters/GlobalPerimeter.php +++ b/tests/Support/Access/Perimeters/GlobalPerimeter.php @@ -8,7 +8,7 @@ class GlobalPerimeter extends Perimeter { - public function should(Authenticatable $user, string $method, Model $model): bool + public function applies(Model $user): bool { return $user->should_global; } diff --git a/tests/Support/Access/Perimeters/OwnPerimeter.php b/tests/Support/Access/Perimeters/OwnPerimeter.php index d34db96..8daeefc 100644 --- a/tests/Support/Access/Perimeters/OwnPerimeter.php +++ b/tests/Support/Access/Perimeters/OwnPerimeter.php @@ -8,7 +8,7 @@ class OwnPerimeter extends Perimeter { - public function should(Authenticatable $user, string $method, Model $model): bool + public function applies(Model $user): bool { return $user->should_own; } diff --git a/tests/Support/Access/Perimeters/SharedPerimeter.php b/tests/Support/Access/Perimeters/SharedPerimeter.php index c087d3e..460bd71 100644 --- a/tests/Support/Access/Perimeters/SharedPerimeter.php +++ b/tests/Support/Access/Perimeters/SharedPerimeter.php @@ -8,7 +8,7 @@ class SharedPerimeter extends Perimeter { - public function should(Authenticatable $user, string $method, Model $model): bool + public function applies(Model $user): bool { return $user->should_shared; } diff --git a/tests/Support/Database/Factories/ModelFactory.php b/tests/Support/Database/Factories/ModelFactory.php index 6ae89fc..b3f0421 100644 --- a/tests/Support/Database/Factories/ModelFactory.php +++ b/tests/Support/Database/Factories/ModelFactory.php @@ -24,6 +24,10 @@ public function definition() return [ 'name' => fake()->name(), 'number' => fake()->numberBetween(-9999999, 9999999), + 'is_shared' => false, + 'is_global' => false, + 'is_client' => false, + 'is_own' => false, ]; } } diff --git a/tests/Support/Database/migrations/2023_04_00_000000_create_models_table.php b/tests/Support/Database/migrations/2023_04_00_000000_create_models_table.php index ba86a4e..c2f27c0 100644 --- a/tests/Support/Database/migrations/2023_04_00_000000_create_models_table.php +++ b/tests/Support/Database/migrations/2023_04_00_000000_create_models_table.php @@ -18,6 +18,11 @@ public function up() $table->string('string')->nullable(); $table->string('unique')->unique()->nullable(); $table->bigInteger('number'); + $table->string('allowed_methods')->nullable(); + $table->boolean('is_shared'); + $table->boolean('is_global'); + $table->boolean('is_client'); + $table->boolean('is_own'); $table->timestamps(); }); } diff --git a/tests/Support/Policies/ModelPolicy.php b/tests/Support/Policies/ModelPolicy.php new file mode 100644 index 0000000..2985d81 --- /dev/null +++ b/tests/Support/Policies/ModelPolicy.php @@ -0,0 +1,11 @@ + Date: Thu, 13 Feb 2025 17:29:03 +0000 Subject: [PATCH 04/13] Apply fixes from StyleCI --- src/Controls/Control.php | 3 +-- src/Perimeters/Perimeter.php | 2 +- src/Policies/ControlledPolicy.php | 6 +++--- tests/Feature/ControlsTest.php | 15 +++++---------- tests/Feature/PerimetersTest.php | 16 ++++++++-------- .../Access/Perimeters/ClientPerimeter.php | 1 - .../Access/Perimeters/GlobalPerimeter.php | 1 - tests/Support/Access/Perimeters/OwnPerimeter.php | 1 - .../Access/Perimeters/SharedPerimeter.php | 1 - .../Support/Database/Factories/ModelFactory.php | 6 +++--- tests/Support/Policies/ModelPolicy.php | 2 +- 11 files changed, 22 insertions(+), 32 deletions(-) diff --git a/src/Controls/Control.php b/src/Controls/Control.php index 6038f74..c04b5c4 100644 --- a/src/Controls/Control.php +++ b/src/Controls/Control.php @@ -3,7 +3,6 @@ namespace Lomkit\Access\Controls; use Illuminate\Container\Container; -use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Contracts\Foundation\Application; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Str; @@ -36,7 +35,6 @@ protected function perimeters(): array return []; } - public function applies(Model $user, string $method, Model $model): bool { foreach ($this->perimeters() as $perimeter) { @@ -46,6 +44,7 @@ public function applies(Model $user, string $method, Model $model): bool if (!$model->exists) { return true; } + return $perimeter->getShouldResult($user, $method, $model); } } diff --git a/src/Perimeters/Perimeter.php b/src/Perimeters/Perimeter.php index d0b21c6..3962d6c 100644 --- a/src/Perimeters/Perimeter.php +++ b/src/Perimeters/Perimeter.php @@ -3,7 +3,6 @@ namespace Lomkit\Access\Perimeters; use Closure; -use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Database\Eloquent\Model; class Perimeter @@ -26,6 +25,7 @@ public function getShouldResult(Model $user, string $method, Model $model): bool public function should(Closure $shouldCallback): self { $this->shouldCallback = $shouldCallback; + return $this; } diff --git a/src/Policies/ControlledPolicy.php b/src/Policies/ControlledPolicy.php index 57b3b14..7648127 100644 --- a/src/Policies/ControlledPolicy.php +++ b/src/Policies/ControlledPolicy.php @@ -3,13 +3,13 @@ namespace Lomkit\Access\Policies; use Illuminate\Database\Eloquent\Model; -use Illuminate\Support\Facades\App; use Lomkit\Access\Controls\Control; class ControlledPolicy { /** - * The model class string + * The model class string. + * * @var string */ protected string $model = ''; @@ -73,4 +73,4 @@ public function delete(Model $user, Model $model) { return $this->getControl()->should($user, __FUNCTION__, $model); } -} \ No newline at end of file +} diff --git a/tests/Feature/ControlsTest.php b/tests/Feature/ControlsTest.php index 3c24e5a..00c1507 100644 --- a/tests/Feature/ControlsTest.php +++ b/tests/Feature/ControlsTest.php @@ -1,25 +1,20 @@ assertFalse((new \Lomkit\Access\Tests\Support\Access\Controls\ModelControl())->applies(Auth::user(), 'create', new Model)); + $this->assertFalse((new \Lomkit\Access\Tests\Support\Access\Controls\ModelControl())->applies(Auth::user(), 'create', new Model())); } public function test_control_should_view_any_using_client_perimeter(): void { Auth::user()->update(['should_client' => true]); - $this->assertTrue((new \Lomkit\Access\Tests\Support\Access\Controls\ModelControl())->applies(Auth::user(), 'viewAny', new Model)); + $this->assertTrue((new \Lomkit\Access\Tests\Support\Access\Controls\ModelControl())->applies(Auth::user(), 'viewAny', new Model())); } public function test_control_should_view_using_client_perimeter(): void @@ -48,7 +43,7 @@ public function test_control_should_create_using_client_perimeter(): void { Auth::user()->update(['should_client' => true]); - $this->assertTrue((new \Lomkit\Access\Tests\Support\Access\Controls\ModelControl())->applies(Auth::user(), 'create', new Model)); + $this->assertTrue((new \Lomkit\Access\Tests\Support\Access\Controls\ModelControl())->applies(Auth::user(), 'create', new Model())); } public function test_control_should_update_using_client_perimeter(): void @@ -77,7 +72,7 @@ public function test_control_should_view_any_using_global_perimeter(): void { Auth::user()->update(['should_global' => true]); - $this->assertTrue((new \Lomkit\Access\Tests\Support\Access\Controls\ModelControl())->applies(Auth::user(), 'viewAny', new Model)); + $this->assertTrue((new \Lomkit\Access\Tests\Support\Access\Controls\ModelControl())->applies(Auth::user(), 'viewAny', new Model())); } public function test_control_should_view_using_global_perimeter(): void @@ -106,7 +101,7 @@ public function test_control_should_create_using_global_perimeter(): void { Auth::user()->update(['should_global' => true]); - $this->assertTrue((new \Lomkit\Access\Tests\Support\Access\Controls\ModelControl())->applies(Auth::user(), 'create', new Model)); + $this->assertTrue((new \Lomkit\Access\Tests\Support\Access\Controls\ModelControl())->applies(Auth::user(), 'create', new Model())); } public function test_control_should_update_using_global_perimeter(): void diff --git a/tests/Feature/PerimetersTest.php b/tests/Feature/PerimetersTest.php index 582dac0..1fad04f 100644 --- a/tests/Feature/PerimetersTest.php +++ b/tests/Feature/PerimetersTest.php @@ -15,55 +15,55 @@ public function test_should_client_perimeter(): void { Auth::user()->update(['should_client' => true]); - $this->assertTrue((new ClientPerimeter())->applies(Auth::user(), 'create', new Model)); + $this->assertTrue((new ClientPerimeter())->applies(Auth::user(), 'create', new Model())); } public function test_should_not_client_perimeter(): void { Auth::user()->update(['should_client' => false]); - $this->assertFalse((new ClientPerimeter())->applies(Auth::user(), 'create', new Model)); + $this->assertFalse((new ClientPerimeter())->applies(Auth::user(), 'create', new Model())); } public function test_should_global_perimeter(): void { Auth::user()->update(['should_global' => true]); - $this->assertTrue((new GlobalPerimeter)->applies(Auth::user(), 'create', new Model)); + $this->assertTrue((new GlobalPerimeter())->applies(Auth::user(), 'create', new Model())); } public function test_should_not_global_perimeter(): void { Auth::user()->update(['should_global' => false]); - $this->assertFalse((new GlobalPerimeter)->applies(Auth::user(), 'create', new Model)); + $this->assertFalse((new GlobalPerimeter())->applies(Auth::user(), 'create', new Model())); } public function test_should_own_perimeter(): void { Auth::user()->update(['should_own' => true]); - $this->assertTrue((new OwnPerimeter())->applies(Auth::user(), 'create', new Model)); + $this->assertTrue((new OwnPerimeter())->applies(Auth::user(), 'create', new Model())); } public function test_should_not_own_perimeter(): void { Auth::user()->update(['should_own' => false]); - $this->assertFalse((new OwnPerimeter())->applies(Auth::user(), 'create', new Model)); + $this->assertFalse((new OwnPerimeter())->applies(Auth::user(), 'create', new Model())); } public function test_should_shared_perimeter(): void { Auth::user()->update(['should_shared' => true]); - $this->assertTrue((new SharedPerimeter())->applies(Auth::user(), 'create', new Model)); + $this->assertTrue((new SharedPerimeter())->applies(Auth::user(), 'create', new Model())); } public function test_should_not_shared_perimeter(): void { Auth::user()->update(['should_shared' => false]); - $this->assertFalse((new SharedPerimeter())->applies(Auth::user(), 'create', new Model)); + $this->assertFalse((new SharedPerimeter())->applies(Auth::user(), 'create', new Model())); } } diff --git a/tests/Support/Access/Perimeters/ClientPerimeter.php b/tests/Support/Access/Perimeters/ClientPerimeter.php index 0e9a980..0fdab46 100644 --- a/tests/Support/Access/Perimeters/ClientPerimeter.php +++ b/tests/Support/Access/Perimeters/ClientPerimeter.php @@ -2,7 +2,6 @@ namespace Lomkit\Access\Tests\Support\Access\Perimeters; -use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Database\Eloquent\Model; use Lomkit\Access\Perimeters\Perimeter; diff --git a/tests/Support/Access/Perimeters/GlobalPerimeter.php b/tests/Support/Access/Perimeters/GlobalPerimeter.php index 6676375..8b565bf 100644 --- a/tests/Support/Access/Perimeters/GlobalPerimeter.php +++ b/tests/Support/Access/Perimeters/GlobalPerimeter.php @@ -2,7 +2,6 @@ namespace Lomkit\Access\Tests\Support\Access\Perimeters; -use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Database\Eloquent\Model; use Lomkit\Access\Perimeters\Perimeter; diff --git a/tests/Support/Access/Perimeters/OwnPerimeter.php b/tests/Support/Access/Perimeters/OwnPerimeter.php index 8daeefc..e823c66 100644 --- a/tests/Support/Access/Perimeters/OwnPerimeter.php +++ b/tests/Support/Access/Perimeters/OwnPerimeter.php @@ -2,7 +2,6 @@ namespace Lomkit\Access\Tests\Support\Access\Perimeters; -use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Database\Eloquent\Model; use Lomkit\Access\Perimeters\Perimeter; diff --git a/tests/Support/Access/Perimeters/SharedPerimeter.php b/tests/Support/Access/Perimeters/SharedPerimeter.php index 460bd71..b5174b0 100644 --- a/tests/Support/Access/Perimeters/SharedPerimeter.php +++ b/tests/Support/Access/Perimeters/SharedPerimeter.php @@ -2,7 +2,6 @@ namespace Lomkit\Access\Tests\Support\Access\Perimeters; -use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Database\Eloquent\Model; use Lomkit\Access\Perimeters\Perimeter; diff --git a/tests/Support/Database/Factories/ModelFactory.php b/tests/Support/Database/Factories/ModelFactory.php index b3f0421..35d83d6 100644 --- a/tests/Support/Database/Factories/ModelFactory.php +++ b/tests/Support/Database/Factories/ModelFactory.php @@ -22,12 +22,12 @@ class ModelFactory extends Factory public function definition() { return [ - 'name' => fake()->name(), - 'number' => fake()->numberBetween(-9999999, 9999999), + 'name' => fake()->name(), + 'number' => fake()->numberBetween(-9999999, 9999999), 'is_shared' => false, 'is_global' => false, 'is_client' => false, - 'is_own' => false, + 'is_own' => false, ]; } } diff --git a/tests/Support/Policies/ModelPolicy.php b/tests/Support/Policies/ModelPolicy.php index 2985d81..41e553c 100644 --- a/tests/Support/Policies/ModelPolicy.php +++ b/tests/Support/Policies/ModelPolicy.php @@ -8,4 +8,4 @@ class ModelPolicy extends ControlledPolicy { protected string $model = Model::class; -} \ No newline at end of file +} From 24fd431ec901624ec98c57f9a549c78311f6b697 Mon Sep 17 00:00:00 2001 From: Gautier DELEGLISE Date: Mon, 3 Mar 2025 18:10:11 +0100 Subject: [PATCH 05/13] =?UTF-8?q?=F0=9F=9A=A7=20query=20development?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/tests.yml | 2 +- composer.json | 4 +- src/Controls/Control.php | 21 ++++++++-- src/Controls/HasControlScope.php | 8 ++-- src/Perimeters/OverlayPerimeter.php | 12 ++++++ src/Perimeters/Perimeter.php | 19 ++++++++- tests/Feature/ControlsQueryTest.php | 40 +++++++++++++++++++ ...ontrolsTest.php => ControlsShouldTest.php} | 2 +- tests/Feature/PerimetersTest.php | 16 ++++---- .../Support/Access/Controls/ModelControl.php | 36 ++++++++++------- .../Access/Perimeters/SharedPerimeter.php | 3 +- tests/Unit/TestCase.php | 20 ++++++++++ 12 files changed, 147 insertions(+), 36 deletions(-) create mode 100644 src/Perimeters/OverlayPerimeter.php create mode 100644 tests/Feature/ControlsQueryTest.php rename tests/Feature/{ControlsTest.php => ControlsShouldTest.php} (98%) create mode 100644 tests/Unit/TestCase.php diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9a2a85c..f1d25a2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -15,7 +15,7 @@ jobs: fail-fast: false matrix: php-version: [ '8.2', '8.3', '8.4' ] - laravel-version: [ '^11.0' ] + laravel-version: [ '^11.0', '^12.0' ] database: [ 'sqlite', 'mysql', 'pgsql' ] name: Tests on PHP ${{ matrix.php-version }} with Laravel ${{ matrix.laravel-version }} and ${{ matrix.database }} diff --git a/composer.json b/composer.json index f547791..90fd26b 100644 --- a/composer.json +++ b/composer.json @@ -12,11 +12,11 @@ "require": { "php": "^8.2", "ext-json": "*", - "laravel/framework": "^11.0" + "laravel/framework": "^11.0|^12.0" }, "require-dev": { "guzzlehttp/guzzle": "^6.0|^7.0", - "orchestra/testbench": "^9", + "orchestra/testbench": "^9|^10", "phpunit/phpunit": "^11.0" }, "autoload": { diff --git a/src/Controls/Control.php b/src/Controls/Control.php index c04b5c4..a53f53f 100644 --- a/src/Controls/Control.php +++ b/src/Controls/Control.php @@ -4,6 +4,7 @@ use Illuminate\Container\Container; use Illuminate\Contracts\Foundation\Application; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Str; use Lomkit\Access\Perimeters\Perimeter; @@ -45,13 +46,29 @@ public function applies(Model $user, string $method, Model $model): bool return true; } - return $perimeter->getShouldResult($user, $method, $model); + return $perimeter->applyShouldCallback($user, $method, $model); } } return false; } + public function queried(Builder $query, Model $user): Builder + { + foreach ($this->perimeters() as $perimeter) { + if ($perimeter->applies($user)) { + return $perimeter->applyQueryCallback($query, $user); + } + } + + return $this->noResultQuery($query); + } + + protected function noResultQuery(Builder $query): Builder + { + return $query->whereRaw('0=1'); + } + /** * Specify the callback that should be invoked to guess control names. * @@ -79,8 +96,6 @@ public static function controlForModel(string $modelName): self return $control::new(); } - //@TODO: new ClientPerimeter($queryCallback, $policyCallback) ? - // @TODO: shouldCallback déjà définie ? /** * Get a new control instance for the given attributes. diff --git a/src/Controls/HasControlScope.php b/src/Controls/HasControlScope.php index 93372af..3456df5 100644 --- a/src/Controls/HasControlScope.php +++ b/src/Controls/HasControlScope.php @@ -5,6 +5,7 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Scope; +use Illuminate\Support\Facades\Auth; class HasControlScope implements Scope { @@ -20,7 +21,7 @@ class HasControlScope implements Scope */ public function apply(Builder $builder, Model $model): void { - if (config('access-control.queries.enabled_by_default', true)) { + if (config('access-control.queries.enabled_by_default', false)) { $builder->controlled(); } } @@ -46,14 +47,13 @@ public function extend(Builder $builder) * * @return void */ - protected function addControlled(Builder $builder) + protected function addControlled(Builder $builder):void { $builder->macro('controlled', function (Builder $builder) { /** @var Control $control */ $control = $builder->getModel()->newControl(); - // @TODO: - //return $control->runQuery($builder); + return $control->queried($builder, Auth::user()); }); } diff --git a/src/Perimeters/OverlayPerimeter.php b/src/Perimeters/OverlayPerimeter.php new file mode 100644 index 0000000..e34a96c --- /dev/null +++ b/src/Perimeters/OverlayPerimeter.php @@ -0,0 +1,12 @@ +shouldCallback)($user, $method, $model); } + public function applyQueryCallback(Builder $query, Model $user): Builder { + return ($this->queryCallback)($query, $user); + } + public function should(Closure $shouldCallback): self { $this->shouldCallback = $shouldCallback; @@ -45,4 +50,14 @@ public static function new() { return new static(); } + + /** + * Indicates if the Perimeter can overlay with others + * + * @return bool + */ + protected function overlays(): bool + { + return false; + } } diff --git a/tests/Feature/ControlsQueryTest.php b/tests/Feature/ControlsQueryTest.php new file mode 100644 index 0000000..55d4d21 --- /dev/null +++ b/tests/Feature/ControlsQueryTest.php @@ -0,0 +1,40 @@ +count(50) + ->create(); + + $query = Model::query(); + $query = (new \Lomkit\Access\Tests\Support\Access\Controls\ModelControl())->queried($query, Auth::user()); + + $this->assertEquals(0, $query->count()); + } + + public function test_control_queried_using_client_perimeter(): void + { + Auth::user()->update(['should_client' => true]); + + Model::factory() + ->count(50) + ->create(); + Model::factory() + ->state(['is_client' => true]) + ->count(50) + ->create(); + + $query = Model::query(); + $query = (new \Lomkit\Access\Tests\Support\Access\Controls\ModelControl())->queried($query, Auth::user()); + // @TODO: le soucis ici c'est que on n'applique pas le applies ? + + $this->assertEquals(50, $query->count()); + } + + // @TODO: tester le overlays perimeter +} diff --git a/tests/Feature/ControlsTest.php b/tests/Feature/ControlsShouldTest.php similarity index 98% rename from tests/Feature/ControlsTest.php rename to tests/Feature/ControlsShouldTest.php index 00c1507..14f215d 100644 --- a/tests/Feature/ControlsTest.php +++ b/tests/Feature/ControlsShouldTest.php @@ -3,7 +3,7 @@ use Illuminate\Support\Facades\Auth; use Lomkit\Access\Tests\Support\Models\Model; -class ControlsTest extends \Lomkit\Access\Tests\Feature\TestCase +class ControlsShouldTest extends \Lomkit\Access\Tests\Feature\TestCase { public function test_control_with_no_perimeter_passing(): void { diff --git a/tests/Feature/PerimetersTest.php b/tests/Feature/PerimetersTest.php index 1fad04f..288d9fe 100644 --- a/tests/Feature/PerimetersTest.php +++ b/tests/Feature/PerimetersTest.php @@ -15,55 +15,55 @@ public function test_should_client_perimeter(): void { Auth::user()->update(['should_client' => true]); - $this->assertTrue((new ClientPerimeter())->applies(Auth::user(), 'create', new Model())); + $this->assertTrue((new ClientPerimeter())->applies(Auth::user())); } public function test_should_not_client_perimeter(): void { Auth::user()->update(['should_client' => false]); - $this->assertFalse((new ClientPerimeter())->applies(Auth::user(), 'create', new Model())); + $this->assertFalse((new ClientPerimeter())->applies(Auth::user())); } public function test_should_global_perimeter(): void { Auth::user()->update(['should_global' => true]); - $this->assertTrue((new GlobalPerimeter())->applies(Auth::user(), 'create', new Model())); + $this->assertTrue((new GlobalPerimeter())->applies(Auth::user())); } public function test_should_not_global_perimeter(): void { Auth::user()->update(['should_global' => false]); - $this->assertFalse((new GlobalPerimeter())->applies(Auth::user(), 'create', new Model())); + $this->assertFalse((new GlobalPerimeter())->applies(Auth::user())); } public function test_should_own_perimeter(): void { Auth::user()->update(['should_own' => true]); - $this->assertTrue((new OwnPerimeter())->applies(Auth::user(), 'create', new Model())); + $this->assertTrue((new OwnPerimeter())->applies(Auth::user())); } public function test_should_not_own_perimeter(): void { Auth::user()->update(['should_own' => false]); - $this->assertFalse((new OwnPerimeter())->applies(Auth::user(), 'create', new Model())); + $this->assertFalse((new OwnPerimeter())->applies(Auth::user())); } public function test_should_shared_perimeter(): void { Auth::user()->update(['should_shared' => true]); - $this->assertTrue((new SharedPerimeter())->applies(Auth::user(), 'create', new Model())); + $this->assertTrue((new SharedPerimeter())->applies(Auth::user())); } public function test_should_not_shared_perimeter(): void { Auth::user()->update(['should_shared' => false]); - $this->assertFalse((new SharedPerimeter())->applies(Auth::user(), 'create', new Model())); + $this->assertFalse((new SharedPerimeter())->applies(Auth::user())); } } diff --git a/tests/Support/Access/Controls/ModelControl.php b/tests/Support/Access/Controls/ModelControl.php index ac9849b..f7388d7 100644 --- a/tests/Support/Access/Controls/ModelControl.php +++ b/tests/Support/Access/Controls/ModelControl.php @@ -5,34 +5,42 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Lomkit\Access\Controls\Control; +use Lomkit\Access\Perimeters\OverlayPerimeter; use Lomkit\Access\Tests\Support\Access\Perimeters\ClientPerimeter; use Lomkit\Access\Tests\Support\Access\Perimeters\GlobalPerimeter; use Lomkit\Access\Tests\Support\Access\Perimeters\OwnPerimeter; +use Lomkit\Access\Tests\Support\Access\Perimeters\SharedPerimeter; class ModelControl extends Control { protected function perimeters(): array { + // @TODO: possible to extract the should callback to another method ?? + $shouldCallback = function (Model $user, string $method, Model $model) { + return in_array($method, explode(',', $model->allowed_methods)); + }; + + // @TODO: should or applies ? Why do we have two way of defining ? + return [ + SharedPerimeter::new() + ->should($shouldCallback) + ->query(function (Builder $query, Model $user) { + return $query->where('is_shared', true); + }), GlobalPerimeter::new() - ->should(function (Model $user, string $method, Model $model) { - return str_contains($model->allowed_methods, $method); - }) - ->query(function (Builder $query) { - $query->where('is_global', true); + ->should($shouldCallback) + ->query(function (Builder $query, Model $user) { + return $query->where('is_global', true); }), ClientPerimeter::new() - ->should(function (Model $user, string $method, Model $model) { - return str_contains($model->allowed_methods, $method); - }) - ->query(function (Builder $query) { - $query->where('is_client', true); + ->should($shouldCallback) + ->query(function (Builder $query, Model $user) { + return $query->where('is_client', true); }), OwnPerimeter::new() - ->should(function (Model $user, string $method, Model $model) { - return str_contains($model->allowed_methods, $method); - }) - ->query(function (Builder $query) { + ->should($shouldCallback) + ->query(function (Builder $query, Model $user) { $query->where('is_own', true); }), ]; diff --git a/tests/Support/Access/Perimeters/SharedPerimeter.php b/tests/Support/Access/Perimeters/SharedPerimeter.php index b5174b0..8eb811d 100644 --- a/tests/Support/Access/Perimeters/SharedPerimeter.php +++ b/tests/Support/Access/Perimeters/SharedPerimeter.php @@ -3,9 +3,10 @@ namespace Lomkit\Access\Tests\Support\Access\Perimeters; use Illuminate\Database\Eloquent\Model; +use Lomkit\Access\Perimeters\OverlayPerimeter; use Lomkit\Access\Perimeters\Perimeter; -class SharedPerimeter extends Perimeter +class SharedPerimeter extends OverlayPerimeter { public function applies(Model $user): bool { diff --git a/tests/Unit/TestCase.php b/tests/Unit/TestCase.php new file mode 100644 index 0000000..bfc9471 --- /dev/null +++ b/tests/Unit/TestCase.php @@ -0,0 +1,20 @@ +withAuthenticatedUser(); + } + + protected function resolveAuthFactoryClass() + { + return UserFactory::class; + } +} From 7fead1eb4d80ba13ac6845982364acc9f0259219 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Mon, 3 Mar 2025 17:10:28 +0000 Subject: [PATCH 06/13] Apply fixes from StyleCI --- src/Controls/HasControlScope.php | 2 +- src/Perimeters/OverlayPerimeter.php | 3 +-- src/Perimeters/Perimeter.php | 5 +++-- tests/Feature/ControlsQueryTest.php | 2 +- tests/Feature/PerimetersTest.php | 1 - tests/Support/Access/Controls/ModelControl.php | 1 - tests/Support/Access/Perimeters/SharedPerimeter.php | 1 - tests/Unit/TestCase.php | 1 - 8 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/Controls/HasControlScope.php b/src/Controls/HasControlScope.php index 3456df5..4a0876d 100644 --- a/src/Controls/HasControlScope.php +++ b/src/Controls/HasControlScope.php @@ -47,7 +47,7 @@ public function extend(Builder $builder) * * @return void */ - protected function addControlled(Builder $builder):void + protected function addControlled(Builder $builder): void { $builder->macro('controlled', function (Builder $builder) { /** @var Control $control */ diff --git a/src/Perimeters/OverlayPerimeter.php b/src/Perimeters/OverlayPerimeter.php index e34a96c..e0c1812 100644 --- a/src/Perimeters/OverlayPerimeter.php +++ b/src/Perimeters/OverlayPerimeter.php @@ -8,5 +8,4 @@ public function overlays(): bool { return true; } - -} \ No newline at end of file +} diff --git a/src/Perimeters/Perimeter.php b/src/Perimeters/Perimeter.php index bd12e24..c3a6ff8 100644 --- a/src/Perimeters/Perimeter.php +++ b/src/Perimeters/Perimeter.php @@ -23,7 +23,8 @@ public function applyShouldCallback(Model $user, string $method, Model $model): return ($this->shouldCallback)($user, $method, $model); } - public function applyQueryCallback(Builder $query, Model $user): Builder { + public function applyQueryCallback(Builder $query, Model $user): Builder + { return ($this->queryCallback)($query, $user); } @@ -52,7 +53,7 @@ public static function new() } /** - * Indicates if the Perimeter can overlay with others + * Indicates if the Perimeter can overlay with others. * * @return bool */ diff --git a/tests/Feature/ControlsQueryTest.php b/tests/Feature/ControlsQueryTest.php index 55d4d21..0f85915 100644 --- a/tests/Feature/ControlsQueryTest.php +++ b/tests/Feature/ControlsQueryTest.php @@ -36,5 +36,5 @@ public function test_control_queried_using_client_perimeter(): void $this->assertEquals(50, $query->count()); } - // @TODO: tester le overlays perimeter + // @TODO: tester le overlays perimeter } diff --git a/tests/Feature/PerimetersTest.php b/tests/Feature/PerimetersTest.php index 288d9fe..f40902a 100644 --- a/tests/Feature/PerimetersTest.php +++ b/tests/Feature/PerimetersTest.php @@ -7,7 +7,6 @@ use Lomkit\Access\Tests\Support\Access\Perimeters\GlobalPerimeter; use Lomkit\Access\Tests\Support\Access\Perimeters\OwnPerimeter; use Lomkit\Access\Tests\Support\Access\Perimeters\SharedPerimeter; -use Lomkit\Access\Tests\Support\Models\Model; class PerimetersTest extends TestCase { diff --git a/tests/Support/Access/Controls/ModelControl.php b/tests/Support/Access/Controls/ModelControl.php index f7388d7..f63f665 100644 --- a/tests/Support/Access/Controls/ModelControl.php +++ b/tests/Support/Access/Controls/ModelControl.php @@ -5,7 +5,6 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Lomkit\Access\Controls\Control; -use Lomkit\Access\Perimeters\OverlayPerimeter; use Lomkit\Access\Tests\Support\Access\Perimeters\ClientPerimeter; use Lomkit\Access\Tests\Support\Access\Perimeters\GlobalPerimeter; use Lomkit\Access\Tests\Support\Access\Perimeters\OwnPerimeter; diff --git a/tests/Support/Access/Perimeters/SharedPerimeter.php b/tests/Support/Access/Perimeters/SharedPerimeter.php index 8eb811d..a7b0308 100644 --- a/tests/Support/Access/Perimeters/SharedPerimeter.php +++ b/tests/Support/Access/Perimeters/SharedPerimeter.php @@ -4,7 +4,6 @@ use Illuminate\Database\Eloquent\Model; use Lomkit\Access\Perimeters\OverlayPerimeter; -use Lomkit\Access\Perimeters\Perimeter; class SharedPerimeter extends OverlayPerimeter { diff --git a/tests/Unit/TestCase.php b/tests/Unit/TestCase.php index bfc9471..977e9e9 100644 --- a/tests/Unit/TestCase.php +++ b/tests/Unit/TestCase.php @@ -1,6 +1,5 @@ Date: Mon, 10 Mar 2025 08:03:38 +0100 Subject: [PATCH 07/13] =?UTF-8?q?=F0=9F=9A=A7=20queries?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/access-control.php | 2 +- src/Controls/Control.php | 25 ++++- src/Perimeters/Perimeter.php | 21 ++-- src/Policies/ControlledPolicy.php | 3 + tests/Feature/ControlsQueryTest.php | 97 ++++++++++++++++++- tests/Feature/PerimetersTest.php | 17 ++-- .../Support/Access/Controls/ModelControl.php | 22 +++-- .../Access/Perimeters/ClientPerimeter.php | 4 - .../Access/Perimeters/GlobalPerimeter.php | 4 - .../Access/Perimeters/OwnPerimeter.php | 5 +- .../Access/Perimeters/SharedPerimeter.php | 4 - 11 files changed, 162 insertions(+), 42 deletions(-) diff --git a/config/access-control.php b/config/access-control.php index 54318cb..8ed55d9 100644 --- a/config/access-control.php +++ b/config/access-control.php @@ -6,9 +6,9 @@ | Access Control Queries |-------------------------------------------------------------------------- | - | */ 'queries' => [ 'enabled_by_default' => false, + 'isolated' => true // Isolate the control's logic by applying a parent where on the query ], ]; diff --git a/src/Controls/Control.php b/src/Controls/Control.php index a53f53f..9292885 100644 --- a/src/Controls/Control.php +++ b/src/Controls/Control.php @@ -39,7 +39,7 @@ protected function perimeters(): array public function applies(Model $user, string $method, Model $model): bool { foreach ($this->perimeters() as $perimeter) { - if ($perimeter->applies($user)) { + if ($perimeter->applyAllowedCallback($user)) { // If the model doesn't exists, it means the method is not related to a model // so we don't need to activate the should result since we can't compare an existing model if (!$model->exists) { @@ -54,10 +54,29 @@ public function applies(Model $user, string $method, Model $model): bool } public function queried(Builder $query, Model $user): Builder + { + $callback = function (Builder $query, Model $user) { + return $this->applyQueryControl($query, $user); + }; + + if (config('access-control.queries.isolated')) { + return $query->where(function (Builder $query) use ($user, $callback) { + $callback($query, $user); + }); + } + + return $callback($query, $user); + } + + protected function applyQueryControl(Builder $query, Model $user): Builder { foreach ($this->perimeters() as $perimeter) { - if ($perimeter->applies($user)) { - return $perimeter->applyQueryCallback($query, $user); + if ($perimeter->applyAllowedCallback($user)) { + $query = $perimeter->applyQueryCallback($query, $user); + + if (!$perimeter->overlays()) { + return $query; + } } } diff --git a/src/Perimeters/Perimeter.php b/src/Perimeters/Perimeter.php index c3a6ff8..d6ebb1d 100644 --- a/src/Perimeters/Perimeter.php +++ b/src/Perimeters/Perimeter.php @@ -13,10 +13,7 @@ class Perimeter protected Closure $queryCallback; protected Closure $shouldCallback; - public function applies(Model $user): bool - { - return true; - } + protected Closure $allowedCallback; public function applyShouldCallback(Model $user, string $method, Model $model): bool { @@ -28,6 +25,18 @@ public function applyQueryCallback(Builder $query, Model $user): Builder return ($this->queryCallback)($query, $user); } + public function applyAllowedCallback(Model $user): bool + { + return ($this->allowedCallback)($user); + } + + public function allowed(Closure $allowedCallback): self + { + $this->allowedCallback = $allowedCallback; + + return $this; + } + public function should(Closure $shouldCallback): self { $this->shouldCallback = $shouldCallback; @@ -54,10 +63,8 @@ public static function new() /** * Indicates if the Perimeter can overlay with others. - * - * @return bool */ - protected function overlays(): bool + public function overlays(): bool { return false; } diff --git a/src/Policies/ControlledPolicy.php b/src/Policies/ControlledPolicy.php index 7648127..67ed143 100644 --- a/src/Policies/ControlledPolicy.php +++ b/src/Policies/ControlledPolicy.php @@ -7,6 +7,9 @@ class ControlledPolicy { + + //@TODO: what to do for other methods like attach ? It only has view / etc basic methods + /** * The model class string. * diff --git a/tests/Feature/ControlsQueryTest.php b/tests/Feature/ControlsQueryTest.php index 0f85915..8aa4571 100644 --- a/tests/Feature/ControlsQueryTest.php +++ b/tests/Feature/ControlsQueryTest.php @@ -36,5 +36,100 @@ public function test_control_queried_using_client_perimeter(): void $this->assertEquals(50, $query->count()); } - // @TODO: tester le overlays perimeter + public function test_control_queried_using_shared_overlayed_perimeter(): void + { + Auth::user()->update(['should_shared' => true]); + Auth::user()->update(['should_client' => true]); + + Model::factory() + ->count(50) + ->create(); + Model::factory() + ->state(['is_shared' => true]) + ->count(50) + ->create(); + Model::factory() + ->state(['is_client' => true]) + ->count(50) + ->create(); + + $query = Model::query(); + $query = (new \Lomkit\Access\Tests\Support\Access\Controls\ModelControl())->queried($query, Auth::user()); + + $this->assertEquals(100, $query->count()); + } + + public function test_control_queried_using_shared_overlayed_perimeter_with_distant_perimeter(): void + { + Auth::user()->update(['should_shared' => true]); + Auth::user()->update(['should_own' => true]); + + Model::factory() + ->state(['is_client' => true]) + ->count(50) + ->create(); + Model::factory() + ->state(['is_shared' => true]) + ->count(50) + ->create(); + Model::factory() + ->state(['is_own' => true]) + ->count(50) + ->create(); + + $query = Model::query(); + $query = (new \Lomkit\Access\Tests\Support\Access\Controls\ModelControl())->queried($query, Auth::user()); + + $this->assertEquals(100, $query->count()); + } + + public function test_control_queried_isolated(): void + { + Auth::user()->update(['should_shared' => true]); + Auth::user()->update(['should_own' => true]); + + Model::factory() + ->state(['is_client' => true]) + ->count(50) + ->create(); + Model::factory() + ->state(['is_shared' => true, 'is_client' => true]) + ->count(50) + ->create(); + Model::factory() + ->state(['is_own' => true]) + ->count(50) + ->create(); + + $query = Model::query()->where('is_client', true); + $query = (new \Lomkit\Access\Tests\Support\Access\Controls\ModelControl())->queried($query, Auth::user()); + + $this->assertEquals(50, $query->count()); + } + + public function test_control_queried_not_isolated(): void + { + config(['access-control.queries.isolated' => false]); + + Auth::user()->update(['should_shared' => true]); + Auth::user()->update(['should_own' => true]); + + Model::factory() + ->state(['is_client' => true]) + ->count(50) + ->create(); + Model::factory() + ->state(['is_shared' => true, 'is_client' => true]) + ->count(50) + ->create(); + Model::factory() + ->state(['is_own' => true]) + ->count(50) + ->create(); + + $query = Model::query()->where('is_client', true); + $query = (new \Lomkit\Access\Tests\Support\Access\Controls\ModelControl())->queried($query, Auth::user()); + + $this->assertEquals(150, $query->count()); + } } diff --git a/tests/Feature/PerimetersTest.php b/tests/Feature/PerimetersTest.php index f40902a..9f479e6 100644 --- a/tests/Feature/PerimetersTest.php +++ b/tests/Feature/PerimetersTest.php @@ -2,6 +2,7 @@ namespace Lomkit\Access\Tests\Feature; +use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\Auth; use Lomkit\Access\Tests\Support\Access\Perimeters\ClientPerimeter; use Lomkit\Access\Tests\Support\Access\Perimeters\GlobalPerimeter; @@ -14,55 +15,55 @@ public function test_should_client_perimeter(): void { Auth::user()->update(['should_client' => true]); - $this->assertTrue((new ClientPerimeter())->applies(Auth::user())); + $this->assertTrue((new ClientPerimeter())->allowed(function(Model $user) { return $user->should_client; })->applyAllowedCallback(Auth::user())); } public function test_should_not_client_perimeter(): void { Auth::user()->update(['should_client' => false]); - $this->assertFalse((new ClientPerimeter())->applies(Auth::user())); + $this->assertFalse((new ClientPerimeter())->allowed(function(Model $user) { return $user->should_client; })->applyAllowedCallback(Auth::user())); } public function test_should_global_perimeter(): void { Auth::user()->update(['should_global' => true]); - $this->assertTrue((new GlobalPerimeter())->applies(Auth::user())); + $this->assertTrue((new GlobalPerimeter())->allowed(function(Model $user) { return $user->should_global; })->applyAllowedCallback(Auth::user())); } public function test_should_not_global_perimeter(): void { Auth::user()->update(['should_global' => false]); - $this->assertFalse((new GlobalPerimeter())->applies(Auth::user())); + $this->assertFalse((new GlobalPerimeter())->allowed(function(Model $user) { return $user->should_global; })->applyAllowedCallback(Auth::user())); } public function test_should_own_perimeter(): void { Auth::user()->update(['should_own' => true]); - $this->assertTrue((new OwnPerimeter())->applies(Auth::user())); + $this->assertTrue((new OwnPerimeter())->allowed(function(Model $user) { return $user->should_own; })->applyAllowedCallback(Auth::user())); } public function test_should_not_own_perimeter(): void { Auth::user()->update(['should_own' => false]); - $this->assertFalse((new OwnPerimeter())->applies(Auth::user())); + $this->assertFalse((new OwnPerimeter())->allowed(function(Model $user) { return $user->should_own; })->applyAllowedCallback(Auth::user())); } public function test_should_shared_perimeter(): void { Auth::user()->update(['should_shared' => true]); - $this->assertTrue((new SharedPerimeter())->applies(Auth::user())); + $this->assertTrue((new SharedPerimeter())->allowed(function(Model $user) { return $user->should_shared; })->applyAllowedCallback(Auth::user())); } public function test_should_not_shared_perimeter(): void { Auth::user()->update(['should_shared' => false]); - $this->assertFalse((new SharedPerimeter())->applies(Auth::user())); + $this->assertFalse((new SharedPerimeter())->allowed(function(Model $user) { return $user->should_shared; })->applyAllowedCallback(Auth::user())); } } diff --git a/tests/Support/Access/Controls/ModelControl.php b/tests/Support/Access/Controls/ModelControl.php index f63f665..114b23d 100644 --- a/tests/Support/Access/Controls/ModelControl.php +++ b/tests/Support/Access/Controls/ModelControl.php @@ -19,28 +19,38 @@ protected function perimeters(): array return in_array($method, explode(',', $model->allowed_methods)); }; - // @TODO: should or applies ? Why do we have two way of defining ? - return [ SharedPerimeter::new() ->should($shouldCallback) + ->allowed(function(Model $user) { + return $user->should_shared; + }) ->query(function (Builder $query, Model $user) { - return $query->where('is_shared', true); + return $query->orWhere('is_shared', true); }), GlobalPerimeter::new() ->should($shouldCallback) + ->allowed(function(Model $user) { + return $user->should_global; + }) ->query(function (Builder $query, Model $user) { - return $query->where('is_global', true); + return $query->orWhere('is_global', true); }), ClientPerimeter::new() ->should($shouldCallback) + ->allowed(function(Model $user) { + return $user->should_client; + }) ->query(function (Builder $query, Model $user) { - return $query->where('is_client', true); + return $query->orWhere('is_client', true); }), OwnPerimeter::new() ->should($shouldCallback) + ->allowed(function(Model $user) { + return $user->should_own; + }) ->query(function (Builder $query, Model $user) { - $query->where('is_own', true); + return $query->orWhere('is_own', true); }), ]; } diff --git a/tests/Support/Access/Perimeters/ClientPerimeter.php b/tests/Support/Access/Perimeters/ClientPerimeter.php index 0fdab46..3771199 100644 --- a/tests/Support/Access/Perimeters/ClientPerimeter.php +++ b/tests/Support/Access/Perimeters/ClientPerimeter.php @@ -7,8 +7,4 @@ class ClientPerimeter extends Perimeter { - public function applies(Model $user): bool - { - return $user->should_client; - } } diff --git a/tests/Support/Access/Perimeters/GlobalPerimeter.php b/tests/Support/Access/Perimeters/GlobalPerimeter.php index 8b565bf..df8fbcf 100644 --- a/tests/Support/Access/Perimeters/GlobalPerimeter.php +++ b/tests/Support/Access/Perimeters/GlobalPerimeter.php @@ -7,8 +7,4 @@ class GlobalPerimeter extends Perimeter { - public function applies(Model $user): bool - { - return $user->should_global; - } } diff --git a/tests/Support/Access/Perimeters/OwnPerimeter.php b/tests/Support/Access/Perimeters/OwnPerimeter.php index e823c66..3083e2d 100644 --- a/tests/Support/Access/Perimeters/OwnPerimeter.php +++ b/tests/Support/Access/Perimeters/OwnPerimeter.php @@ -7,8 +7,5 @@ class OwnPerimeter extends Perimeter { - public function applies(Model $user): bool - { - return $user->should_own; - } + } diff --git a/tests/Support/Access/Perimeters/SharedPerimeter.php b/tests/Support/Access/Perimeters/SharedPerimeter.php index a7b0308..247a0b0 100644 --- a/tests/Support/Access/Perimeters/SharedPerimeter.php +++ b/tests/Support/Access/Perimeters/SharedPerimeter.php @@ -7,8 +7,4 @@ class SharedPerimeter extends OverlayPerimeter { - public function applies(Model $user): bool - { - return $user->should_shared; - } } From fa3b18668d11409476958921362b677f9a3086f9 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Mon, 10 Mar 2025 07:03:55 +0000 Subject: [PATCH 08/13] Apply fixes from StyleCI --- config/access-control.php | 2 +- src/Policies/ControlledPolicy.php | 1 - tests/Feature/PerimetersTest.php | 16 ++++++++-------- tests/Support/Access/Controls/ModelControl.php | 8 ++++---- .../Access/Perimeters/ClientPerimeter.php | 1 - .../Access/Perimeters/GlobalPerimeter.php | 1 - tests/Support/Access/Perimeters/OwnPerimeter.php | 2 -- .../Access/Perimeters/SharedPerimeter.php | 1 - 8 files changed, 13 insertions(+), 19 deletions(-) diff --git a/config/access-control.php b/config/access-control.php index 8ed55d9..3633039 100644 --- a/config/access-control.php +++ b/config/access-control.php @@ -9,6 +9,6 @@ */ 'queries' => [ 'enabled_by_default' => false, - 'isolated' => true // Isolate the control's logic by applying a parent where on the query + 'isolated' => true, // Isolate the control's logic by applying a parent where on the query ], ]; diff --git a/src/Policies/ControlledPolicy.php b/src/Policies/ControlledPolicy.php index 67ed143..031b9df 100644 --- a/src/Policies/ControlledPolicy.php +++ b/src/Policies/ControlledPolicy.php @@ -7,7 +7,6 @@ class ControlledPolicy { - //@TODO: what to do for other methods like attach ? It only has view / etc basic methods /** diff --git a/tests/Feature/PerimetersTest.php b/tests/Feature/PerimetersTest.php index 9f479e6..dbc582d 100644 --- a/tests/Feature/PerimetersTest.php +++ b/tests/Feature/PerimetersTest.php @@ -15,55 +15,55 @@ public function test_should_client_perimeter(): void { Auth::user()->update(['should_client' => true]); - $this->assertTrue((new ClientPerimeter())->allowed(function(Model $user) { return $user->should_client; })->applyAllowedCallback(Auth::user())); + $this->assertTrue((new ClientPerimeter())->allowed(function (Model $user) { return $user->should_client; })->applyAllowedCallback(Auth::user())); } public function test_should_not_client_perimeter(): void { Auth::user()->update(['should_client' => false]); - $this->assertFalse((new ClientPerimeter())->allowed(function(Model $user) { return $user->should_client; })->applyAllowedCallback(Auth::user())); + $this->assertFalse((new ClientPerimeter())->allowed(function (Model $user) { return $user->should_client; })->applyAllowedCallback(Auth::user())); } public function test_should_global_perimeter(): void { Auth::user()->update(['should_global' => true]); - $this->assertTrue((new GlobalPerimeter())->allowed(function(Model $user) { return $user->should_global; })->applyAllowedCallback(Auth::user())); + $this->assertTrue((new GlobalPerimeter())->allowed(function (Model $user) { return $user->should_global; })->applyAllowedCallback(Auth::user())); } public function test_should_not_global_perimeter(): void { Auth::user()->update(['should_global' => false]); - $this->assertFalse((new GlobalPerimeter())->allowed(function(Model $user) { return $user->should_global; })->applyAllowedCallback(Auth::user())); + $this->assertFalse((new GlobalPerimeter())->allowed(function (Model $user) { return $user->should_global; })->applyAllowedCallback(Auth::user())); } public function test_should_own_perimeter(): void { Auth::user()->update(['should_own' => true]); - $this->assertTrue((new OwnPerimeter())->allowed(function(Model $user) { return $user->should_own; })->applyAllowedCallback(Auth::user())); + $this->assertTrue((new OwnPerimeter())->allowed(function (Model $user) { return $user->should_own; })->applyAllowedCallback(Auth::user())); } public function test_should_not_own_perimeter(): void { Auth::user()->update(['should_own' => false]); - $this->assertFalse((new OwnPerimeter())->allowed(function(Model $user) { return $user->should_own; })->applyAllowedCallback(Auth::user())); + $this->assertFalse((new OwnPerimeter())->allowed(function (Model $user) { return $user->should_own; })->applyAllowedCallback(Auth::user())); } public function test_should_shared_perimeter(): void { Auth::user()->update(['should_shared' => true]); - $this->assertTrue((new SharedPerimeter())->allowed(function(Model $user) { return $user->should_shared; })->applyAllowedCallback(Auth::user())); + $this->assertTrue((new SharedPerimeter())->allowed(function (Model $user) { return $user->should_shared; })->applyAllowedCallback(Auth::user())); } public function test_should_not_shared_perimeter(): void { Auth::user()->update(['should_shared' => false]); - $this->assertFalse((new SharedPerimeter())->allowed(function(Model $user) { return $user->should_shared; })->applyAllowedCallback(Auth::user())); + $this->assertFalse((new SharedPerimeter())->allowed(function (Model $user) { return $user->should_shared; })->applyAllowedCallback(Auth::user())); } } diff --git a/tests/Support/Access/Controls/ModelControl.php b/tests/Support/Access/Controls/ModelControl.php index 114b23d..48dbb79 100644 --- a/tests/Support/Access/Controls/ModelControl.php +++ b/tests/Support/Access/Controls/ModelControl.php @@ -22,7 +22,7 @@ protected function perimeters(): array return [ SharedPerimeter::new() ->should($shouldCallback) - ->allowed(function(Model $user) { + ->allowed(function (Model $user) { return $user->should_shared; }) ->query(function (Builder $query, Model $user) { @@ -30,7 +30,7 @@ protected function perimeters(): array }), GlobalPerimeter::new() ->should($shouldCallback) - ->allowed(function(Model $user) { + ->allowed(function (Model $user) { return $user->should_global; }) ->query(function (Builder $query, Model $user) { @@ -38,7 +38,7 @@ protected function perimeters(): array }), ClientPerimeter::new() ->should($shouldCallback) - ->allowed(function(Model $user) { + ->allowed(function (Model $user) { return $user->should_client; }) ->query(function (Builder $query, Model $user) { @@ -46,7 +46,7 @@ protected function perimeters(): array }), OwnPerimeter::new() ->should($shouldCallback) - ->allowed(function(Model $user) { + ->allowed(function (Model $user) { return $user->should_own; }) ->query(function (Builder $query, Model $user) { diff --git a/tests/Support/Access/Perimeters/ClientPerimeter.php b/tests/Support/Access/Perimeters/ClientPerimeter.php index 3771199..16e2225 100644 --- a/tests/Support/Access/Perimeters/ClientPerimeter.php +++ b/tests/Support/Access/Perimeters/ClientPerimeter.php @@ -2,7 +2,6 @@ namespace Lomkit\Access\Tests\Support\Access\Perimeters; -use Illuminate\Database\Eloquent\Model; use Lomkit\Access\Perimeters\Perimeter; class ClientPerimeter extends Perimeter diff --git a/tests/Support/Access/Perimeters/GlobalPerimeter.php b/tests/Support/Access/Perimeters/GlobalPerimeter.php index df8fbcf..ed0a72b 100644 --- a/tests/Support/Access/Perimeters/GlobalPerimeter.php +++ b/tests/Support/Access/Perimeters/GlobalPerimeter.php @@ -2,7 +2,6 @@ namespace Lomkit\Access\Tests\Support\Access\Perimeters; -use Illuminate\Database\Eloquent\Model; use Lomkit\Access\Perimeters\Perimeter; class GlobalPerimeter extends Perimeter diff --git a/tests/Support/Access/Perimeters/OwnPerimeter.php b/tests/Support/Access/Perimeters/OwnPerimeter.php index 3083e2d..7c6bdce 100644 --- a/tests/Support/Access/Perimeters/OwnPerimeter.php +++ b/tests/Support/Access/Perimeters/OwnPerimeter.php @@ -2,10 +2,8 @@ namespace Lomkit\Access\Tests\Support\Access\Perimeters; -use Illuminate\Database\Eloquent\Model; use Lomkit\Access\Perimeters\Perimeter; class OwnPerimeter extends Perimeter { - } diff --git a/tests/Support/Access/Perimeters/SharedPerimeter.php b/tests/Support/Access/Perimeters/SharedPerimeter.php index 247a0b0..34785f6 100644 --- a/tests/Support/Access/Perimeters/SharedPerimeter.php +++ b/tests/Support/Access/Perimeters/SharedPerimeter.php @@ -2,7 +2,6 @@ namespace Lomkit\Access\Tests\Support\Access\Perimeters; -use Illuminate\Database\Eloquent\Model; use Lomkit\Access\Perimeters\OverlayPerimeter; class SharedPerimeter extends OverlayPerimeter From ad989ea86f0b1f5ce02d39d717e22b65b7869b73 Mon Sep 17 00:00:00 2001 From: Gautier DELEGLISE Date: Thu, 3 Apr 2025 22:45:46 +0200 Subject: [PATCH 09/13] =?UTF-8?q?=F0=9F=90=9B=20overlays=20perimeter=20adj?= =?UTF-8?q?ustments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Controls/Control.php | 16 +++++++++++-- src/Perimeters/Perimeter.php | 2 -- tests/Feature/ControlsQueryTest.php | 24 ++++++++++++++++++- tests/Feature/ControlsShouldTest.php | 24 +++++++++++++++++++ .../Support/Access/Controls/ModelControl.php | 10 ++++---- 5 files changed, 67 insertions(+), 9 deletions(-) diff --git a/src/Controls/Control.php b/src/Controls/Control.php index 9292885..c86a56b 100644 --- a/src/Controls/Control.php +++ b/src/Controls/Control.php @@ -12,6 +12,7 @@ class Control { + // @TODO: scout queried /** * The control name resolver. * @@ -46,7 +47,11 @@ public function applies(Model $user, string $method, Model $model): bool return true; } - return $perimeter->applyShouldCallback($user, $method, $model); + $should = $perimeter->applyShouldCallback($user, $method, $model); + + if (!$perimeter->overlays() || $should) { + return $should; + } } } @@ -70,17 +75,24 @@ public function queried(Builder $query, Model $user): Builder protected function applyQueryControl(Builder $query, Model $user): Builder { + $noResultCallback = function(Builder $query) { + return $this->noResultQuery($query); + }; + + foreach ($this->perimeters() as $perimeter) { if ($perimeter->applyAllowedCallback($user)) { $query = $perimeter->applyQueryCallback($query, $user); + $noResultCallback = function($query){return $query;}; + if (!$perimeter->overlays()) { return $query; } } } - return $this->noResultQuery($query); + return $noResultCallback($query); } protected function noResultQuery(Builder $query): Builder diff --git a/src/Perimeters/Perimeter.php b/src/Perimeters/Perimeter.php index d6ebb1d..7758f6a 100644 --- a/src/Perimeters/Perimeter.php +++ b/src/Perimeters/Perimeter.php @@ -8,8 +8,6 @@ class Perimeter { - // @TODO: what for shared example ? (final on former project) - protected Closure $queryCallback; protected Closure $shouldCallback; diff --git a/tests/Feature/ControlsQueryTest.php b/tests/Feature/ControlsQueryTest.php index 8aa4571..d0acae0 100644 --- a/tests/Feature/ControlsQueryTest.php +++ b/tests/Feature/ControlsQueryTest.php @@ -31,7 +31,6 @@ public function test_control_queried_using_client_perimeter(): void $query = Model::query(); $query = (new \Lomkit\Access\Tests\Support\Access\Controls\ModelControl())->queried($query, Auth::user()); - // @TODO: le soucis ici c'est que on n'applique pas le applies ? $this->assertEquals(50, $query->count()); } @@ -83,6 +82,29 @@ public function test_control_queried_using_shared_overlayed_perimeter_with_dista $this->assertEquals(100, $query->count()); } + public function test_control_queried_using_only_shared_overlayed_perimeter(): void + { + Auth::user()->update(['should_shared' => true]); + + Model::factory() + ->state(['is_client' => true]) + ->count(50) + ->create(); + Model::factory() + ->state(['is_shared' => true]) + ->count(50) + ->create(); + Model::factory() + ->state(['is_own' => true]) + ->count(50) + ->create(); + + $query = Model::query(); + $query = (new \Lomkit\Access\Tests\Support\Access\Controls\ModelControl())->queried($query, Auth::user()); + + $this->assertEquals(50, $query->count()); + } + public function test_control_queried_isolated(): void { Auth::user()->update(['should_shared' => true]); diff --git a/tests/Feature/ControlsShouldTest.php b/tests/Feature/ControlsShouldTest.php index 14f215d..3bed597 100644 --- a/tests/Feature/ControlsShouldTest.php +++ b/tests/Feature/ControlsShouldTest.php @@ -125,4 +125,28 @@ public function test_control_should_delete_using_global_perimeter(): void $this->assertTrue((new \Lomkit\Access\Tests\Support\Access\Controls\ModelControl())->applies(Auth::user(), 'delete', $model)); } + + public function test_control_should_delete_global_using_shared_overlayed_perimeter(): void + { + Auth::user()->update(['should_shared' => true]); + Auth::user()->update(['should_global' => true]); + $model = Model::factory() + ->create([ + 'allowed_methods' => 'delete', + ]); + + $this->assertTrue((new \Lomkit\Access\Tests\Support\Access\Controls\ModelControl())->applies(Auth::user(), 'delete', $model)); + } + + public function test_control_should_not_delete_global_using_shared_overlayed_perimeter(): void + { + Auth::user()->update(['should_shared' => true]); + Auth::user()->update(['should_global' => true]); + $model = Model::factory() + ->create([ + 'allowed_methods' => 'delete_shared', + ]); + + $this->assertTrue((new \Lomkit\Access\Tests\Support\Access\Controls\ModelControl())->applies(Auth::user(), 'delete', $model)); + } } diff --git a/tests/Support/Access/Controls/ModelControl.php b/tests/Support/Access/Controls/ModelControl.php index 48dbb79..fc3add1 100644 --- a/tests/Support/Access/Controls/ModelControl.php +++ b/tests/Support/Access/Controls/ModelControl.php @@ -21,34 +21,36 @@ protected function perimeters(): array return [ SharedPerimeter::new() - ->should($shouldCallback) ->allowed(function (Model $user) { return $user->should_shared; }) + ->should(function (Model $user, string $method, Model $model) { + return in_array($method.'_shared', explode(',', $model->allowed_methods)); + }) ->query(function (Builder $query, Model $user) { return $query->orWhere('is_shared', true); }), GlobalPerimeter::new() - ->should($shouldCallback) ->allowed(function (Model $user) { return $user->should_global; }) + ->should($shouldCallback) ->query(function (Builder $query, Model $user) { return $query->orWhere('is_global', true); }), ClientPerimeter::new() - ->should($shouldCallback) ->allowed(function (Model $user) { return $user->should_client; }) + ->should($shouldCallback) ->query(function (Builder $query, Model $user) { return $query->orWhere('is_client', true); }), OwnPerimeter::new() - ->should($shouldCallback) ->allowed(function (Model $user) { return $user->should_own; }) + ->should($shouldCallback) ->query(function (Builder $query, Model $user) { return $query->orWhere('is_own', true); }), From 58f70ad2aaaa2790beb0c404a456ae3fe90fa531 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Thu, 3 Apr 2025 20:46:05 +0000 Subject: [PATCH 10/13] Apply fixes from StyleCI --- src/Controls/Control.php | 5 ++--- src/Controls/HasControl.php | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Controls/Control.php b/src/Controls/Control.php index c86a56b..0114693 100644 --- a/src/Controls/Control.php +++ b/src/Controls/Control.php @@ -75,16 +75,15 @@ public function queried(Builder $query, Model $user): Builder protected function applyQueryControl(Builder $query, Model $user): Builder { - $noResultCallback = function(Builder $query) { + $noResultCallback = function (Builder $query) { return $this->noResultQuery($query); }; - foreach ($this->perimeters() as $perimeter) { if ($perimeter->applyAllowedCallback($user)) { $query = $perimeter->applyQueryCallback($query, $user); - $noResultCallback = function($query){return $query;}; + $noResultCallback = function ($query) {return $query; }; if (!$perimeter->overlays()) { return $query; diff --git a/src/Controls/HasControl.php b/src/Controls/HasControl.php index e3b6e27..34e5201 100644 --- a/src/Controls/HasControl.php +++ b/src/Controls/HasControl.php @@ -33,7 +33,7 @@ public static function control() * * @return Control|null */ - protected static function newControl(): Control|null + protected static function newControl(): ?Control { return static::$control::new() ?? null; } From 7d825ee081e4f0ef187d314b6455ccde0316e6b7 Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 13:23:08 +0200 Subject: [PATCH 11/13] =?UTF-8?q?=F0=9F=93=9D=20Add=20docstrings=20to=20`f?= =?UTF-8?q?eature/reworked-package`=20(#9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 📝 Add docstrings to `feature/reworked-package` Docstrings generation was requested by @GautierDele. * https://github.com/Lomkit/laravel-access-control/pull/5#issuecomment-2782275849 The following files were modified: * `src/AccessServiceProvider.php` * `src/Controls/Control.php` * `src/Controls/HasControl.php` * `src/Controls/HasControlScope.php` * `src/Perimeters/OverlayPerimeter.php` * `src/Perimeters/Perimeter.php` * `src/Policies/ControlledPolicy.php` * `tests/Feature/TestCase.php` * `tests/Support/Access/Controls/ModelControl.php` * `tests/Support/Database/Factories/ModelFactory.php` * `tests/Support/Database/Factories/UserFactory.php` * `tests/Support/Database/migrations/2014_00_00_000000_create_users_table.php` * `tests/Support/Database/migrations/2023_04_00_000000_create_models_table.php` * `tests/Support/Models/Model.php` * `tests/TestCase.php` * `tests/Unit/TestCase.php` * Apply fixes from StyleCI * ♻️ too much comments * Apply fixes from StyleCI * ♻️ --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: StyleCI Bot Co-authored-by: Gautier DELEGLISE --- src/AccessServiceProvider.php | 2 +- src/Controls/Control.php | 50 +++++++++++++---- src/Controls/HasControl.php | 10 ++-- src/Controls/HasControlScope.php | 13 +++-- src/Perimeters/OverlayPerimeter.php | 5 ++ src/Perimeters/Perimeter.php | 53 +++++++++++++++++-- src/Policies/ControlledPolicy.php | 41 ++++++++++---- .../Database/Factories/ModelFactory.php | 5 -- .../Database/Factories/UserFactory.php | 5 -- 9 files changed, 141 insertions(+), 43 deletions(-) diff --git a/src/AccessServiceProvider.php b/src/AccessServiceProvider.php index eefcd6b..f9b8d4e 100644 --- a/src/AccessServiceProvider.php +++ b/src/AccessServiceProvider.php @@ -7,7 +7,7 @@ class AccessServiceProvider extends ServiceProvider { /** - * Register the service provider. + * Registers the service provider. * * @return void */ diff --git a/src/Controls/Control.php b/src/Controls/Control.php index 0114693..50a8283 100644 --- a/src/Controls/Control.php +++ b/src/Controls/Control.php @@ -28,15 +28,24 @@ class Control public static $namespace = 'App\\Access\\Controls\\'; /** - * Get the perimeters for the current control. + * Retrieve the list of perimeter definitions for the current control. * - * @return array + * @return array An array of Perimeter objects. */ protected function perimeters(): array { return []; } + /** + * Determines if the control applies based on the user's permissions and model state. + * + * @param Model $user The user whose permissions are evaluated. + * @param string $method The action or method to verify. + * @param Model $model The target model; if it does not exist, the control applies by default. + * + * @return bool True if the control applies to the user and model; otherwise, false. + */ public function applies(Model $user, string $method, Model $model): bool { foreach ($this->perimeters() as $perimeter) { @@ -58,6 +67,14 @@ public function applies(Model $user, string $method, Model $model): bool return false; } + /** + * Modifies the query builder to enforce access control restrictions for a given user. + * + * @param Builder $query The query builder instance to modify. + * @param Model $user The user model used to determine applicable query control restrictions. + * + * @return Builder The modified query builder with access controls applied. + */ public function queried(Builder $query, Model $user): Builder { $callback = function (Builder $query, Model $user) { @@ -73,6 +90,14 @@ public function queried(Builder $query, Model $user): Builder return $callback($query, $user); } + /** + * Applies query modifications based on access control perimeters for the given user. + * + * @param Builder $query The query builder instance to be modified. + * @param Model $user The user model used to evaluate access control conditions. + * + * @return Builder The query builder after applying access control modifications. + */ protected function applyQueryControl(Builder $query, Model $user): Builder { $noResultCallback = function (Builder $query) { @@ -94,6 +119,13 @@ protected function applyQueryControl(Builder $query, Model $user): Builder return $noResultCallback($query); } + /** + * Modifies the query builder to return no results. + * + * @param Builder $query The query builder instance to modify. + * + * @return Builder The modified query builder that yields an empty result set. + */ protected function noResultQuery(Builder $query): Builder { return $query->whereRaw('0=1'); @@ -128,9 +160,9 @@ public static function controlForModel(string $modelName): self } /** - * Get a new control instance for the given attributes. + * Creates a new instance of the control. * - * @return static + * @return static A newly created control instance. */ public static function new(): self { @@ -138,13 +170,13 @@ public static function new(): self } /** - * Get the control name for the given model name. + * Resolve the control name for a given model. * * @template TClass of \Illuminate\Database\Eloquent\Model * - * @param class-string $modelName + * @param class-string $modelName The fully qualified model class name. * - * @return class-string<\Lomkit\Access\Controls\Control> + * @return class-string<\Lomkit\Access\Controls\Control> The fully qualified control class name corresponding to the model. */ public static function resolveControlName(string $modelName): string { @@ -162,9 +194,9 @@ public static function resolveControlName(string $modelName): string } /** - * Get the application namespace for the application. + * Retrieves the application's namespace. * - * @return string + * @return string The resolved or default application namespace. */ protected static function appNamespace(): string { diff --git a/src/Controls/HasControl.php b/src/Controls/HasControl.php index 34e5201..9c47810 100644 --- a/src/Controls/HasControl.php +++ b/src/Controls/HasControl.php @@ -2,8 +2,6 @@ namespace Lomkit\Access\Controls; -use Illuminate\Database\Eloquent\Factories\Factory; - trait HasControl { /** @@ -17,9 +15,9 @@ public static function bootHasControl() } /** - * Get a new factory instance for the model. + * Retrieves a control instance for the model. * - * @return Control + * @return Control The control instance for the model. */ public static function control() { @@ -29,9 +27,9 @@ public static function control() } /** - * Return a new control instance. + * Attempts to create a new control instance. * - * @return Control|null + * @return Control|null The newly created control instance, or null if creation was unsuccessful. */ protected static function newControl(): ?Control { diff --git a/src/Controls/HasControlScope.php b/src/Controls/HasControlScope.php index 4a0876d..f01c057 100644 --- a/src/Controls/HasControlScope.php +++ b/src/Controls/HasControlScope.php @@ -17,7 +17,10 @@ class HasControlScope implements Scope protected $extensions = ['controlled', 'uncontrolled']; /** - * Apply the access control features to the query. + * Applies access control to the query builder. + * + * @param Builder $builder The Eloquent query builder instance. + * @param Model $model The related model instance (unused in this method). */ public function apply(Builder $builder, Model $model): void { @@ -41,9 +44,9 @@ public function extend(Builder $builder) } /** - * Add the with-control extension to the builder. + * Registers the "controlled" macro on the query builder. * - * @param \Illuminate\Database\Eloquent\Builder $builder + * @param \Illuminate\Database\Eloquent\Builder $builder The query builder instance to extend. * * @return void */ @@ -58,9 +61,9 @@ protected function addControlled(Builder $builder): void } /** - * Add the without-control extension to the builder. + * Registers the "uncontrolled" macro on the query builder. * - * @param \Illuminate\Database\Eloquent\Builder $builder + * @param \Illuminate\Database\Eloquent\Builder $builder The query builder instance to extend. * * @return void */ diff --git a/src/Perimeters/OverlayPerimeter.php b/src/Perimeters/OverlayPerimeter.php index e0c1812..96428a3 100644 --- a/src/Perimeters/OverlayPerimeter.php +++ b/src/Perimeters/OverlayPerimeter.php @@ -4,6 +4,11 @@ class OverlayPerimeter extends Perimeter { + /** + * A perimeter overlays if he collides with other perimeters. + * + * @return bool + */ public function overlays(): bool { return true; diff --git a/src/Perimeters/Perimeter.php b/src/Perimeters/Perimeter.php index 7758f6a..0bd1eaa 100644 --- a/src/Perimeters/Perimeter.php +++ b/src/Perimeters/Perimeter.php @@ -13,21 +13,52 @@ class Perimeter protected Closure $allowedCallback; + /** + * Executes the should callback to determine if the access control condition is met. + * + * @param Model $user The user instance for which the check is performed. + * @param string $method The access control method or action being evaluated. + * @param Model $model The model instance related to the access check. + * + * @return bool True if the callback validation passes; otherwise, false. + */ public function applyShouldCallback(Model $user, string $method, Model $model): bool { return ($this->shouldCallback)($user, $method, $model); } + /** + * Applies the registered query callback to modify the query builder based on the user's context. + * + * @param Builder $query The query builder instance to be customized. + * @param Model $user The user model providing context for the query modification. + * + * @return Builder The modified query builder. + */ public function applyQueryCallback(Builder $query, Model $user): Builder { return ($this->queryCallback)($query, $user); } + /** + * Executes the allowed callback to check user access. + * + * @param Model $user The user model instance to evaluate for access. + * + * @return bool True if the user is allowed; false otherwise. + */ public function applyAllowedCallback(Model $user): bool { return ($this->allowedCallback)($user); } + /** + * Sets the allowed callback for permission checks. + * + * @param Closure $allowedCallback A callback that performs the permission evaluation. + * + * @return self Returns the current instance. + */ public function allowed(Closure $allowedCallback): self { $this->allowedCallback = $allowedCallback; @@ -35,6 +66,13 @@ public function allowed(Closure $allowedCallback): self return $this; } + /** + * Sets the callback used to determine if a specific access control condition should be applied. + * + * @param Closure $shouldCallback A callback that returns a boolean based on custom logic. + * + * @return self The current instance. + */ public function should(Closure $shouldCallback): self { $this->shouldCallback = $shouldCallback; @@ -42,6 +80,13 @@ public function should(Closure $shouldCallback): self return $this; } + /** + * Sets the query modification callback. + * + * @param Closure $queryCallback A callback that customizes the query logic. + * + * @return self Returns the current instance for method chaining. + */ public function query(Closure $queryCallback): self { $this->queryCallback = $queryCallback; @@ -50,9 +95,9 @@ public function query(Closure $queryCallback): self } /** - * Get a new control instance for the given attributes. + * Creates and returns a new instance of the Perimeter class. * - * @return static + * @return static A new instance of the current class. */ public static function new() { @@ -60,7 +105,9 @@ public static function new() } /** - * Indicates if the Perimeter can overlay with others. + * Determines whether this Perimeter instance supports overlay functionality with other perimeters. + * + * @return bool */ public function overlays(): bool { diff --git a/src/Policies/ControlledPolicy.php b/src/Policies/ControlledPolicy.php index 031b9df..cfd17ed 100644 --- a/src/Policies/ControlledPolicy.php +++ b/src/Policies/ControlledPolicy.php @@ -17,9 +17,9 @@ class ControlledPolicy protected string $model = ''; /** - * Return the control instance string. + * Returns the fully qualified model class name associated with this policy. * - * @return string-class + * @return string The fully qualified class name of the model. */ protected function getModel(): string { @@ -27,9 +27,9 @@ protected function getModel(): string } /** - * Return the control instance. + * Retrieves the control instance associated with the current model. * - * @return Control + * @return Control The control instance for the current model. */ protected function getControl(): Control { @@ -37,7 +37,11 @@ protected function getControl(): Control } /** - * Determine if any model can be viewed by the user. + * Determine if the user is authorized to view any instances of the model. + * + * @param Model $user The user for which the permission check is performed. + * + * @return bool True if the user is authorized to view any instances, false otherwise. */ public function viewAny(Model $user) { @@ -45,7 +49,12 @@ public function viewAny(Model $user) } /** - * Determine if the given model can be viewed by the user. + * Checks whether a specific model instance is viewable by the given user. + * + * @param Model $user The user whose permission to view the model is being evaluated. + * @param Model $model The model instance for which view permission is checked. + * + * @return bool True if the user is authorized to view the model instance, false otherwise. */ public function view(Model $user, Model $model) { @@ -53,7 +62,11 @@ public function view(Model $user, Model $model) } /** - * Determine if the model can be created by the user. + * Checks if the given user has permission to create a new instance of the model. + * + * @param Model $user The user whose permission to create the model is being verified. + * + * @return bool True if the user is allowed to create a new model instance, false otherwise. */ public function create(Model $user) { @@ -61,7 +74,12 @@ public function create(Model $user) } /** - * Determine if the given model can be updated by the user. + * Determines whether the user is authorized to update the specified model instance. + * + * @param Model $user The user attempting to perform the update. + * @param Model $model The model instance targeted for update. + * + * @return bool True if the update action is permitted, false otherwise. */ public function update(Model $user, Model $model) { @@ -69,7 +87,12 @@ public function update(Model $user, Model $model) } /** - * Determine if the given model can be deleted by the user. + * Determines if the specified user is authorized to delete the given model instance. + * + * @param Model $user The user attempting the deletion. + * @param Model $model The model instance to be deleted. + * + * @return bool True if deletion is permitted, false otherwise. */ public function delete(Model $user, Model $model) { diff --git a/tests/Support/Database/Factories/ModelFactory.php b/tests/Support/Database/Factories/ModelFactory.php index 35d83d6..88409a3 100644 --- a/tests/Support/Database/Factories/ModelFactory.php +++ b/tests/Support/Database/Factories/ModelFactory.php @@ -14,11 +14,6 @@ class ModelFactory extends Factory */ protected $model = Model::class; - /** - * Define the model's default state. - * - * @return array - */ public function definition() { return [ diff --git a/tests/Support/Database/Factories/UserFactory.php b/tests/Support/Database/Factories/UserFactory.php index 16fdccf..d981673 100644 --- a/tests/Support/Database/Factories/UserFactory.php +++ b/tests/Support/Database/Factories/UserFactory.php @@ -18,11 +18,6 @@ class UserFactory extends Factory */ protected $model = User::class; - /** - * Define the model's default state. - * - * @return array - */ public function definition(): array { return [ From eb2b69de787a3d7757d6fb3d3df23c8502f3a476 Mon Sep 17 00:00:00 2001 From: Gautier DELEGLISE Date: Mon, 7 Apr 2025 13:32:03 +0200 Subject: [PATCH 12/13] =?UTF-8?q?=E2=9C=85=20applied=20code=20rabbit=20fee?= =?UTF-8?q?dback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Perimeters/OverlayPerimeter.php | 2 ++ src/Perimeters/Perimeter.php | 9 ++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Perimeters/OverlayPerimeter.php b/src/Perimeters/OverlayPerimeter.php index 96428a3..fa4c744 100644 --- a/src/Perimeters/OverlayPerimeter.php +++ b/src/Perimeters/OverlayPerimeter.php @@ -6,6 +6,8 @@ class OverlayPerimeter extends Perimeter { /** * A perimeter overlays if he collides with other perimeters. + * When true, this perimeter's rules can be combined with other perimeters. + * When false, this perimeter's rules will be applied independently and can override other perimeters. * * @return bool */ diff --git a/src/Perimeters/Perimeter.php b/src/Perimeters/Perimeter.php index 0bd1eaa..8ebf71c 100644 --- a/src/Perimeters/Perimeter.php +++ b/src/Perimeters/Perimeter.php @@ -10,9 +10,16 @@ class Perimeter { protected Closure $queryCallback; protected Closure $shouldCallback; - protected Closure $allowedCallback; + public function __construct() + { + // Default implementations that can be overridden + $this->queryCallback = function (Builder $query, Model $user) { return $query; }; + $this->shouldCallback = function (Model $user, string $method, Model $model) { return true; }; + $this->allowedCallback = function (Model $user) { return true; }; + } + /** * Executes the should callback to determine if the access control condition is met. * From 89504ed46532a80ef59f47e9dbc4174addb34f10 Mon Sep 17 00:00:00 2001 From: Gautier DELEGLISE Date: Mon, 7 Apr 2025 13:35:18 +0200 Subject: [PATCH 13/13] Update Perimeter.php --- src/Perimeters/Perimeter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Perimeters/Perimeter.php b/src/Perimeters/Perimeter.php index 8ebf71c..bf03795 100644 --- a/src/Perimeters/Perimeter.php +++ b/src/Perimeters/Perimeter.php @@ -106,7 +106,7 @@ public function query(Closure $queryCallback): self * * @return static A new instance of the current class. */ - public static function new() + public static function new(): static { return new static(); }