Skip to content

Commit 56fbcd8

Browse files
Feature/refactored (#13)
* ♻️ refactored the process * Apply fixes from StyleCI * Update 2013_04_00_000000_create_clients_table.php * ♻️ ai feedback --------- Co-authored-by: StyleCI Bot <bot@styleci.io>
1 parent 99b9d0d commit 56fbcd8

21 files changed

+437
-246
lines changed

.github/FUNDING.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
github: GautierDele
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
name: Packagist Deploy
2+
3+
on:
4+
release:
5+
types: [created]
6+
7+
jobs:
8+
build:
9+
10+
runs-on: ubuntu-latest
11+
permissions:
12+
contents: read
13+
packages: write
14+
15+
steps:
16+
- uses: actions/checkout@v4
17+
- uses: mnavarrocarter/packagist-update@v1.0.0
18+
with:
19+
username: "GautierDele"
20+
api_token: ${{ secrets.PACKAGIST_TOKEN }}

README.md

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,69 @@
22

33
# Laravel Access Control
44

5-
# BETA
6-
Please note that this package is under beta and is not recommended to use for production environment for now. End of beta should be by summer 2024.
5+
Laravel Access Control allows you to fully secure your application in two key areas: Policies and Queries. Manage everything in one place!
6+
## Requirements
7+
8+
PHP 8.2+ and Laravel 11+
9+
10+
## Documentation, Installation, and Usage Instructions
11+
12+
See the [documentation](https://laravel-access-control.lomkit.com) for detailed installation and usage instructions.
13+
14+
## What it does
15+
16+
You first need to define the perimeters concerned by your applications.
17+
18+
Create the model control:
19+
20+
```php
21+
class PostControl extends Control
22+
{
23+
protected function perimeters(): array
24+
{
25+
return [
26+
GlobalPerimeter::new()
27+
->allowed(function (Model $user, string $method) {
28+
return $user->can(sprintf('%s global models', $method));
29+
})
30+
->should(function (Model $user, Model $model) {
31+
return true;
32+
})
33+
->scoutQuery(function (\Laravel\Scout\Builder $query, Model $user) {
34+
return $query;
35+
})
36+
->query(function (Builder $query, Model $user) {
37+
return $query;
38+
}),
39+
ClientPerimeter::new()
40+
->allowed(function (Model $user, string $method) {
41+
return $user->can(sprintf('%s client models', $method));
42+
})
43+
->should(function (Model $user, Model $model) {
44+
return $model->client()->is($user->client);
45+
})
46+
->scoutQuery(function (\Laravel\Scout\Builder $query, Model $user) {
47+
return $query->where('client_id', $user->client->getKey());
48+
})
49+
->query(function (Builder $query, Model $user) {
50+
return $query->orWhere('client_id', $user->client->getKey());
51+
}),
52+
// ...
53+
```
54+
55+
Then setup your policy:
56+
57+
```php
58+
class PostPolicy extends ControlledPolicy
59+
{
60+
protected string $model = Post::class;
61+
}
62+
```
63+
64+
and you are ready to go !
65+
66+
```php
67+
App\Models\Post::controlled()->get() // Apply the Control to the query
68+
69+
$user->can('view', App\Models\Post::first()) // Check if the user can view the post according to the policy
70+
```

src/Controls/Control.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,14 @@ protected function perimeters(): array
4949
public function applies(Model $user, string $method, Model $model): bool
5050
{
5151
foreach ($this->perimeters() as $perimeter) {
52-
if ($perimeter->applyAllowedCallback($user)) {
52+
if ($perimeter->applyAllowedCallback($user, $method)) {
5353
// If the model doesn't exists, it means the method is not related to a model
5454
// so we don't need to activate the should result since we can't compare an existing model
5555
if (!$model->exists) {
5656
return true;
5757
}
5858

59-
$should = $perimeter->applyShouldCallback($user, $method, $model);
59+
$should = $perimeter->applyShouldCallback($user, $model);
6060

6161
if (!$perimeter->overlays() || $should) {
6262
return $should;
@@ -118,7 +118,7 @@ protected function applyQueryControl(Builder $query, Model $user): Builder
118118
};
119119

120120
foreach ($this->perimeters() as $perimeter) {
121-
if ($perimeter->applyAllowedCallback($user)) {
121+
if ($perimeter->applyAllowedCallback($user, 'view')) {
122122
$query = $perimeter->applyQueryCallback($query, $user);
123123

124124
$noResultCallback = function ($query) {return $query; };
@@ -147,7 +147,7 @@ protected function applyScoutQueryControl(\Laravel\Scout\Builder $query, Model $
147147
};
148148

149149
foreach ($this->perimeters() as $perimeter) {
150-
if ($perimeter->applyAllowedCallback($user)) {
150+
if ($perimeter->applyAllowedCallback($user, 'view')) {
151151
$query = $perimeter->applyScoutQueryCallback($query, $user);
152152

153153
$noResultCallback = function ($query) {return $query; };

src/Perimeters/Perimeter.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ public function __construct()
2222
// Default implementations that can be overridden
2323
$this->scoutQueryCallback = function (\Laravel\Scout\Builder $query, Model $user) { return $query; };
2424
$this->queryCallback = function (Builder $query, Model $user) { return $query; };
25-
$this->shouldCallback = function (Model $user, string $method, Model $model) { return true; };
26-
$this->allowedCallback = function (Model $user) { return true; };
25+
$this->shouldCallback = function (Model $user, Model $model) { return true; };
26+
$this->allowedCallback = function (Model $user, string $method) { return true; };
2727
}
2828

2929
/**
@@ -35,9 +35,9 @@ public function __construct()
3535
*
3636
* @return bool True if the condition applies; false otherwise.
3737
*/
38-
public function applyShouldCallback(Model $user, string $method, Model $model): bool
38+
public function applyShouldCallback(Model $user, Model $model): bool
3939
{
40-
return ($this->shouldCallback)($user, $method, $model);
40+
return ($this->shouldCallback)($user, $model);
4141
}
4242

4343
/**
@@ -73,9 +73,9 @@ public function applyQueryCallback(Builder $query, Model $user): Builder
7373
*
7474
* @return bool True if the user is allowed; false otherwise.
7575
*/
76-
public function applyAllowedCallback(Model $user): bool
76+
public function applyAllowedCallback(Model $user, string $method): bool
7777
{
78-
return ($this->allowedCallback)($user);
78+
return ($this->allowedCallback)($user, $method);
7979
}
8080

8181
/**

tests/Feature/ControlsQueryTest.php

Lines changed: 51 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
namespace Lomkit\Access\Tests\Feature;
44

55
use Illuminate\Support\Facades\Auth;
6+
use Illuminate\Support\Facades\Gate;
67
use Lomkit\Access\Tests\Support\Models\Model;
8+
use Lomkit\Access\Tests\Support\Models\User;
79

810
class ControlsQueryTest extends \Lomkit\Access\Tests\Feature\TestCase
911
{
@@ -21,13 +23,15 @@ public function test_control_with_no_perimeter_passing(): void
2123

2224
public function test_control_queried_using_client_perimeter(): void
2325
{
24-
Auth::user()->update(['should_client' => true]);
26+
Gate::define('view client models', function (User $user) {
27+
return true;
28+
});
2529

2630
Model::factory()
2731
->count(50)
2832
->create();
2933
Model::factory()
30-
->state(['is_client' => true])
34+
->clientPerimeter()
3135
->count(50)
3236
->create();
3337

@@ -39,18 +43,22 @@ public function test_control_queried_using_client_perimeter(): void
3943

4044
public function test_control_queried_using_shared_overlayed_perimeter(): void
4145
{
42-
Auth::user()->update(['should_shared' => true]);
43-
Auth::user()->update(['should_client' => true]);
46+
Gate::define('view client models', function (User $user) {
47+
return true;
48+
});
49+
Gate::define('view shared models', function (User $user) {
50+
return true;
51+
});
4452

4553
Model::factory()
4654
->count(50)
4755
->create();
4856
Model::factory()
49-
->state(['is_shared' => true])
57+
->sharedPerimeter()
5058
->count(50)
5159
->create();
5260
Model::factory()
53-
->state(['is_client' => true])
61+
->clientPerimeter()
5462
->count(50)
5563
->create();
5664

@@ -62,19 +70,23 @@ public function test_control_queried_using_shared_overlayed_perimeter(): void
6270

6371
public function test_control_queried_using_shared_overlayed_perimeter_with_distant_perimeter(): void
6472
{
65-
Auth::user()->update(['should_shared' => true]);
66-
Auth::user()->update(['should_own' => true]);
73+
Gate::define('view shared models', function (User $user) {
74+
return true;
75+
});
76+
Gate::define('view own models', function (User $user) {
77+
return true;
78+
});
6779

6880
Model::factory()
69-
->state(['is_client' => true])
81+
->clientPerimeter()
7082
->count(50)
7183
->create();
7284
Model::factory()
73-
->state(['is_shared' => true])
85+
->sharedPerimeter()
7486
->count(50)
7587
->create();
7688
Model::factory()
77-
->state(['is_own' => true])
89+
->ownPerimeter()
7890
->count(50)
7991
->create();
8092

@@ -86,18 +98,20 @@ public function test_control_queried_using_shared_overlayed_perimeter_with_dista
8698

8799
public function test_control_queried_using_only_shared_overlayed_perimeter(): void
88100
{
89-
Auth::user()->update(['should_shared' => true]);
101+
Gate::define('view shared models', function (User $user) {
102+
return true;
103+
});
90104

91105
Model::factory()
92-
->state(['is_client' => true])
106+
->clientPerimeter()
93107
->count(50)
94108
->create();
95109
Model::factory()
96-
->state(['is_shared' => true])
110+
->sharedPerimeter()
97111
->count(50)
98112
->create();
99113
Model::factory()
100-
->state(['is_own' => true])
114+
->ownPerimeter()
101115
->count(50)
102116
->create();
103117

@@ -109,23 +123,28 @@ public function test_control_queried_using_only_shared_overlayed_perimeter(): vo
109123

110124
public function test_control_queried_isolated(): void
111125
{
112-
Auth::user()->update(['should_shared' => true]);
113-
Auth::user()->update(['should_own' => true]);
126+
Gate::define('view shared models', function (User $user) {
127+
return true;
128+
});
129+
Gate::define('view own models', function (User $user) {
130+
return true;
131+
});
114132

115133
Model::factory()
116-
->state(['is_client' => true])
134+
->clientPerimeter()
117135
->count(50)
118136
->create();
119137
Model::factory()
120-
->state(['is_shared' => true, 'is_client' => true])
138+
->clientPerimeter()
139+
->sharedPerimeter()
121140
->count(50)
122141
->create();
123142
Model::factory()
124-
->state(['is_own' => true])
143+
->ownPerimeter()
125144
->count(50)
126145
->create();
127146

128-
$query = Model::query()->where('is_client', true);
147+
$query = Model::query()->where('client_id', Auth::user()->client->getKey());
129148
$query = (new \Lomkit\Access\Tests\Support\Access\Controls\ModelControl())->queried($query, Auth::user());
130149

131150
$this->assertEquals(50, $query->count());
@@ -135,23 +154,28 @@ public function test_control_queried_not_isolated(): void
135154
{
136155
config(['access-control.queries.isolated' => false]);
137156

138-
Auth::user()->update(['should_shared' => true]);
139-
Auth::user()->update(['should_own' => true]);
157+
Gate::define('view shared models', function (User $user) {
158+
return true;
159+
});
160+
Gate::define('view own models', function (User $user) {
161+
return true;
162+
});
140163

141164
Model::factory()
142-
->state(['is_client' => true])
165+
->clientPerimeter()
143166
->count(50)
144167
->create();
145168
Model::factory()
146-
->state(['is_shared' => true, 'is_client' => true])
169+
->clientPerimeter()
170+
->sharedPerimeter()
147171
->count(50)
148172
->create();
149173
Model::factory()
150-
->state(['is_own' => true])
174+
->ownPerimeter()
151175
->count(50)
152176
->create();
153177

154-
$query = Model::query()->where('is_client', true);
178+
$query = Model::query()->where('client_id', Auth::user()->client->getKey());
155179
$query = (new \Lomkit\Access\Tests\Support\Access\Controls\ModelControl())->queried($query, Auth::user());
156180

157181
$this->assertEquals(150, $query->count());

0 commit comments

Comments
 (0)