Skip to content

Commit 2f22642

Browse files
authored
Add access permission (#13)
* Added access permission for detours. analyse and style * Fix failing test and quality script * Add tests for the new middleware authorizing the routes
1 parent 81fe0aa commit 2f22642

File tree

9 files changed

+128
-12
lines changed

9 files changed

+128
-12
lines changed

config/statamic-detour.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,8 @@
2323
],
2424

2525
'queue' => 'default',
26+
27+
'permissions' => [
28+
'access' => 'access detours',
29+
],
2630
];

routes/cp.php

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,17 @@
44
use JustBetter\Detour\Http\Controllers\DetourController;
55
use JustBetter\Detour\Http\Controllers\ImportExportController;
66

7-
Route::prefix('detours')->group(function () {
8-
Route::get('/', [DetourController::class, 'index'])->name('justbetter.detours.index');
7+
Route::prefix('detours')
8+
->middleware('detours.access')
9+
->group(function () {
10+
Route::get('/', [DetourController::class, 'index'])->name('justbetter.detours.index');
911

10-
Route::post('/store', [DetourController::class, 'store'])->name('justbetter.detours.store');
12+
Route::post('/store', [DetourController::class, 'store'])->name('justbetter.detours.store');
1113

12-
Route::delete('/{detour}', [DetourController::class, 'destroy'])->name('justbetter.detours.destroy');
14+
Route::delete('/{detour}', [DetourController::class, 'destroy'])->name('justbetter.detours.destroy');
1315

14-
Route::get('/actions', [ImportExportController::class, 'index'])->name('justbetter.detours.actions.index');
15-
Route::get('/actions/export', [ImportExportController::class, 'export'])->name('justbetter.detours.actions.export');
16+
Route::get('/actions', [ImportExportController::class, 'index'])->name('justbetter.detours.actions.index');
17+
Route::get('/actions/export', [ImportExportController::class, 'export'])->name('justbetter.detours.actions.export');
1618

17-
Route::post('/actions/import', [ImportExportController::class, 'import'])->name('justbetter.detours.actions.import');
18-
});
19+
Route::post('/actions/import', [ImportExportController::class, 'import'])->name('justbetter.detours.actions.import');
20+
});
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace JustBetter\Detour\Http\Middleware;
4+
5+
use Closure;
6+
use Illuminate\Http\Request;
7+
use Statamic\Facades\User;
8+
use Symfony\Component\HttpFoundation\Response;
9+
10+
class AuthorizeDetours
11+
{
12+
public function handle(Request $request, Closure $next): Response
13+
{
14+
$user = User::current();
15+
$permission = config()->string('justbetter.statamic-detour.permissions.access');
16+
17+
abort_unless(
18+
$user && ($user->isSuper() || $user->hasPermission($permission)),
19+
403
20+
);
21+
22+
return $next($request);
23+
}
24+
}

src/Http/Requests/ImportRequest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace JustBetter\Detour\Http\Requests;
44

5+
use Illuminate\Contracts\Validation\ValidationRule;
56
use Illuminate\Foundation\Http\FormRequest;
67
use Illuminate\Http\UploadedFile;
78
use Illuminate\Validation\Validator;
@@ -16,7 +17,7 @@ class ImportRequest extends FormRequest
1617
protected array $requiredHeaders = ['from', 'to', 'type', 'code'];
1718

1819
/**
19-
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
20+
* @return array<string, ValidationRule|array<mixed>|string>
2021
*/
2122
public function rules(): array
2223
{

src/Http/Requests/IndexRequest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace JustBetter\Detour\Http\Requests;
44

5+
use Illuminate\Contracts\Validation\ValidationRule;
56
use Illuminate\Foundation\Http\FormRequest;
67

78
/**
@@ -10,7 +11,7 @@
1011
class IndexRequest extends FormRequest
1112
{
1213
/**
13-
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
14+
* @return array<string, ValidationRule|array<mixed>|string>
1415
*/
1516
public function rules(): array
1617
{

src/Http/Requests/StoreRequest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace JustBetter\Detour\Http\Requests;
44

5+
use Illuminate\Contracts\Validation\ValidationRule;
56
use Illuminate\Foundation\Http\FormRequest;
67
use Illuminate\Validation\Rule;
78

@@ -15,7 +16,7 @@
1516
class StoreRequest extends FormRequest
1617
{
1718
/**
18-
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
19+
* @return array<string, ValidationRule|array<mixed>|string>
1920
*/
2021
public function rules(): array
2122
{

src/ServiceProvider.php

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@
1515
use JustBetter\Detour\Actions\MatchDetour;
1616
use JustBetter\Detour\Actions\ResolveRepository;
1717
use JustBetter\Detour\Actions\StoreDetour;
18+
use JustBetter\Detour\Http\Middleware\AuthorizeDetours;
1819
use JustBetter\Detour\Http\Middleware\RedirectIfNeeded;
1920
use JustBetter\Detour\Repositories\FileRepository;
21+
use Statamic\Auth\Permission;
2022
use Statamic\Facades\CP\Nav;
23+
use Statamic\Facades\Permission as PermissionFacade;
2124
use Statamic\Providers\AddonServiceProvider;
2225

2326
class ServiceProvider extends AddonServiceProvider
@@ -76,6 +79,9 @@ protected function registerMiddleware(): static
7679
{
7780
$this->app->booted(function () {
7881
$router = app(Router::class);
82+
83+
$router->aliasMiddleware('detours.access', AuthorizeDetours::class);
84+
7985
if (config('justbetter.statamic-detour.mode') === 'performance') {
8086
$router->pushMiddlewareToGroup('web', RedirectIfNeeded::class);
8187
} else {
@@ -90,6 +96,7 @@ public function bootAddon(): void
9096
{
9197
$this
9298
->bootConfig()
99+
->bootPermissions()
93100
->bootNavigation()
94101
->bootMigrations();
95102
}
@@ -110,17 +117,33 @@ function ($nav) {
110117
$nav->content('Overview')
111118
->section('Detours')
112119
->route('justbetter.detours.index')
113-
->icon('<svg class="svg-icon" style="width: 2em; height: 2em;vertical-align: middle;text-align:center;fill: currentColor;overflow: hidden;" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M653.328 125.024l-56.576 56.704L734.88 320H399.68C240.88 320 112 448.992 112 607.776c0 158.816 128 287.952 288 287.952v-80c-112 0-208-93.312-208-208.016 0-114.688 93.152-208 207.84-208h334.96l-137.888 137.856 56.528 56.56 234.48-234.496L653.344 125.024z" fill="#565D64" /></svg>');
120+
->can(config()->string('justbetter.statamic-detour.permissions.access'))
121+
->icon('<svg class="svg-icon" style="width: 1.25em; height: 1.25em;vertical-align: middle;text-align:center;fill: currentColor;overflow: hidden;" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M653.328 125.024l-56.576 56.704L734.88 320H399.68C240.88 320 112 448.992 112 607.776c0 158.816 128 287.952 288 287.952v-80c-112 0-208-93.312-208-208.016 0-114.688 93.152-208 207.84-208h334.96l-137.888 137.856 56.528 56.56 234.48-234.496L653.344 125.024z" fill="#565D64" /></svg>');
114122

115123
$nav->content('Import / Export')
116124
->section('Detours')
117125
->route('justbetter.detours.actions.index')
126+
->can(config()->string('justbetter.statamic-detour.permissions.access'))
118127
->icon('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M7 4v12"/><path d="M4 13l3 3 3-3"/><path d="M17 20V8"/><path d="M14 11l3-3 3 3"/></svg>');
119128
});
120129

121130
return $this;
122131
}
123132

133+
protected function bootPermissions(): static
134+
{
135+
PermissionFacade::group('detours', 'Detours', function () {
136+
$permission = config()->string('justbetter.statamic-detour.permissions.access');
137+
PermissionFacade::register($permission, function (Permission $permission) {
138+
$permission
139+
->label('Access detours')
140+
->description('Gives the user access to managing detours (view, create, delete)');
141+
});
142+
});
143+
144+
return $this;
145+
}
146+
124147
protected function bootMigrations(): static
125148
{
126149
$driver = config('justbetter.statamic-detour.driver');

tests/Http/Controllers/DetourControllerTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,19 @@
1010
use JustBetter\Detour\Http\Requests\IndexRequest;
1111
use JustBetter\Detour\Tests\TestCase;
1212
use PHPUnit\Framework\Attributes\Test;
13+
use Statamic\Facades\User;
1314

1415
class DetourControllerTest extends TestCase
1516
{
1617
#[Test]
1718
public function it_can_load_a_view(): void
1819
{
20+
/** @var \Statamic\Auth\File\User $user */
21+
$user = User::make();
22+
$user->id('test-user')->email('test@example.com')->makeSuper();
23+
24+
$this->actingAs($user);
25+
1926
$controller = app(DetourController::class);
2027
$contract = app(ListsDetours::class);
2128
$request = IndexRequest::create('/cp/detours', 'GET');
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
namespace JustBetter\Detour\Tests\Http\Middleware;
4+
5+
use JustBetter\Detour\Tests\TestCase;
6+
use PHPUnit\Framework\Attributes\Test;
7+
use Statamic\Facades\Role;
8+
use Statamic\Facades\User;
9+
10+
class AuthorizeDetoursTest extends TestCase
11+
{
12+
#[Test]
13+
public function it_forbids_users_without_the_detours_permission(): void
14+
{
15+
$role = Role::make('cp-only')->permissions(['access cp']);
16+
$role->save();
17+
18+
/** @var \Statamic\Auth\File\User $user */
19+
$user = User::make();
20+
$user
21+
->id('test-user-no-detours')
22+
->email('no-detours@example.com')
23+
->assignRole($role)
24+
->save();
25+
26+
$this->actingAs($user);
27+
28+
$this->get(cp_route('index'))->assertRedirect(cp_route('dashboard'));
29+
$this->get(cp_route('justbetter.detours.index'))->assertForbidden();
30+
}
31+
32+
#[Test]
33+
public function it_can_authorize_detours(): void
34+
{
35+
$permission = config()->string('justbetter.statamic-detour.permissions.access');
36+
37+
$role = Role::make('detours-access')->permissions(['access cp', $permission]);
38+
$role->save();
39+
40+
/** @var \Statamic\Auth\File\User $user */
41+
$user = User::make();
42+
$user
43+
->id('test-user-with-detours')
44+
->email('with-detours@example.com')
45+
->assignRole($role)
46+
->save();
47+
48+
$this->actingAs($user);
49+
50+
$this->get(cp_route('index'))->assertRedirect(cp_route('dashboard'));
51+
$this->get(cp_route('justbetter.detours.index'))->assertOk();
52+
}
53+
}

0 commit comments

Comments
 (0)