Skip to content

Commit

Permalink
feat: added new way to authorize pivots (#768)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kyrch authored Dec 13, 2024
1 parent af1d86f commit 912a9c7
Show file tree
Hide file tree
Showing 28 changed files with 725 additions and 223 deletions.
2 changes: 1 addition & 1 deletion app/Enums/Models/List/ExternalProfileSite.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public function getAuthorizeUrl(): ?string
if ($this === static::MAL) {
$codeVerifier = bin2hex(random_bytes(64));

$id = Str::uuid()->toString();
$id = Str::uuid()->__toString();

Cache::set("mal-external-token-request-{$id}", $codeVerifier);

Expand Down
21 changes: 19 additions & 2 deletions app/Filament/Actions/Base/AttachAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
use App\Filament\RelationManagers\BaseRelationManager;
use Filament\Tables\Actions\AttachAction as DefaultAttachAction;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Str;

/**
* Class AttachAction.
Expand All @@ -26,9 +29,23 @@ protected function setUp(): void
{
parent::setUp();

$this->authorize('create');
$this->visible(function (BaseRelationManager $livewire) {
if (!$livewire->getRelationship() instanceof BelongsToMany) {
return false;
}

$this->hidden(fn (BaseRelationManager $livewire) => !($livewire->getRelationship() instanceof BelongsToMany));
$ownerRecord = $livewire->getOwnerRecord();

$gate = Gate::getPolicyFor($ownerRecord);

$ability = Str::of('attachAny')
->append(Str::singular(class_basename($livewire->getTable()->getModel())))
->__toString();

return is_object($gate) & method_exists($gate, $ability)
? Gate::forUser(Auth::user())->any($ability, $ownerRecord)
: true;
});

$this->recordSelect(function (BaseRelationManager $livewire) {
/** @var string */
Expand Down
26 changes: 21 additions & 5 deletions app/Filament/Actions/Base/CreateAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
use App\Enums\Auth\Role;
use App\Filament\RelationManagers\BaseRelationManager;
use App\Filament\RelationManagers\Wiki\ResourceRelationManager;
use App\Models\Auth\User;
use Filament\Forms\Form;
use Filament\Tables\Actions\CreateAction as DefaultCreateAction;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Http\Request;
use Livewire\Component;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Str;

/**
* Class CreateAction.
Expand Down Expand Up @@ -49,12 +51,26 @@ protected function setUp(): void
}
});

$this->hidden(function (Component $livewire, Request $request) {
$this->visible(function (BaseRelationManager $livewire) {
if ($livewire instanceof ResourceRelationManager) {
return !$request->user()->hasRole(Role::ADMIN->value);
/** @var User $user */
$user = Auth::user();
return $user->hasRole(Role::ADMIN->value);
}

return false;
$ownerRecord = $livewire->getOwnerRecord();

$gate = Gate::getPolicyFor($ownerRecord);

$method = $livewire->getRelationship() instanceof BelongsToMany ? 'attachAny' : 'addAny';

$ability = Str::of($method)
->append(Str::singular(class_basename($livewire->getTable()->getModel())))
->__toString();

return is_object($gate) & method_exists($gate, $ability)
? Gate::forUser(Auth::user())->any($ability, $ownerRecord)
: true;
});
}
}
30 changes: 28 additions & 2 deletions app/Filament/Actions/Base/DetachAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
use App\Filament\RelationManagers\BaseRelationManager;
use Filament\Tables\Actions\DetachAction as DefaultDetachAction;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Str;

/**
* Class DetachAction.
Expand All @@ -27,9 +30,32 @@ protected function setUp(): void

$this->label(__('filament.actions.base.detach'));

$this->hidden(fn ($livewire) => !($livewire instanceof BaseRelationManager && $livewire->getRelationship() instanceof BelongsToMany));
$this->visible(function (mixed $livewire) {
if (
!($livewire instanceof BaseRelationManager)
|| !($livewire->getRelationship() instanceof BelongsToMany)
) {
return false;
}

$ownerRecord = $livewire->getOwnerRecord();

$gate = Gate::getPolicyFor($ownerRecord);

$model = Str::singular(class_basename($livewire->getTable()->getModel()));

$this->authorize('delete');
$detachAny = Str::of('detachAny')
->append($model)
->__toString();

$detach = Str::of('detach')
->append($model)
->__toString();

return is_object($gate) & method_exists($gate, $detachAny)
? Gate::forUser(Auth::user())->any([$detachAny, $detach], [$ownerRecord, $this->getRecord()])
: true;
});

$this->after(function ($livewire, $record) {
$relationship = $livewire->getRelationship();
Expand Down
4 changes: 2 additions & 2 deletions app/Http/Controllers/Api/Pivot/PivotController.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use App\Http\Api\Schema\Schema;
use App\Http\Controllers\Controller;
use App\Http\Middleware\Auth\Authenticate;
use App\Http\Middleware\Models\AuthorizesPivot;
use Illuminate\Support\Str;

/**
Expand All @@ -25,8 +26,7 @@ abstract class PivotController extends Controller implements InteractsWithSchema
*/
public function __construct(string $foreignModel, string $foreignParameter, string $relatedModel, string $relatedParameter)
{
$this->authorizeResource($foreignModel, $foreignParameter);
$this->authorizeResource($relatedModel, $relatedParameter);
$this->middleware(AuthorizesPivot::class.":{$foreignModel},{$foreignParameter},{$relatedModel},{$relatedParameter}");
$this->middleware(Authenticate::using('sanctum'))->except(['index', 'show']);
}

Expand Down
2 changes: 1 addition & 1 deletion app/Http/Middleware/LogRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class LogRequest
*/
public function handle(Request $request, Closure $next): mixed
{
$requestId = Str::uuid()->toString();
$requestId = Str::uuid()->__toString();

Log::withContext([
'request-id' => $requestId,
Expand Down
133 changes: 133 additions & 0 deletions app/Http/Middleware/Models/AuthorizesPivot.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
<?php

declare(strict_types=1);

namespace App\Http\Middleware\Models;

use App\Models\Auth\User;
use Closure;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Str;

/**
* Class AuthorizesPivot.
*/
class AuthorizesPivot
{
/**
* Handle an incoming request.
*
* @param Request $request
* @param Closure(Request): mixed $next
* @param string $foreignClass
* @param string $foreignParameter
* @param string $relatedClass
* @param string $relatedParameter
* @return mixed
*/
public function handle(Request $request, Closure $next, string $foreignClass, string $foreignParameter, string $relatedClass, string $relatedParameter): mixed
{
/** @var Model $foreignModel */
$foreignModel = $request->route($foreignParameter);

/** @var Model $relatedModel */
$relatedModel = $request->route($relatedParameter);

$isAuthorized = match ($request->route()->getActionMethod()) {
'index' => $this->forIndex($request->user(), $foreignClass, $relatedClass),
'show' => $this->forShow($request->user(), $foreignModel, $relatedModel),
'create', 'store' => $this->forStore($request->user(), $foreignModel, $relatedModel),
'destroy' => $this->forDestroy($request->user(), $foreignModel, $relatedModel),
'edit', 'update' => $this->forUpdate($request->user(), $foreignModel, $relatedModel),
default => false,
};

if (!$isAuthorized) {
abort(403);
}

return $next($request);
}

/**
* Get the authorization to index.
*
* @param User|null $user
* @param string $foreignClass
* @param string $relatedClass
* @return bool
*/
protected function forIndex(?User $user, string $foreignClass, string $relatedClass): bool
{
return Gate::forUser($user)->check('viewAny', $foreignClass)
&& Gate::forUser($user)->check('viewAny', $relatedClass);
}

/**
* Get the authorization to show a pivot.
*
* @param User|null $user
* @param Model $foreignModel
* @param Model $relatedModel
* @return bool
*/
protected function forShow(?User $user, Model $foreignModel, Model $relatedModel): bool
{
return Gate::forUser($user)->check('view', $foreignModel)
&& Gate::forUser($user)->check('view', $relatedModel);
}

/**
* Get the authorization to store a pivot.
*
* @param User $user
* @param Model $foreignModel
* @param Model $relatedModel
* @return bool
*/
protected function forStore(User $user, Model $foreignModel, Model $relatedModel): bool
{
$attach = Str::of('attach')
->append(Str::singular(class_basename($relatedModel)))
->__toString();

return Gate::forUser($user)->check($attach, [$foreignModel, $relatedModel]);
}

/**
* Get the authorization to destroy a pivot.
*
* @param User $user
* @param Model $foreignModel
* @param Model $relatedModel
* @return bool
*/
protected function forDestroy(User $user, Model $foreignModel, Model $relatedModel): bool
{
$detachAny = Str::of('detachAny')
->append(Str::singular(class_basename($relatedModel)))
->__toString();

$detach = Str::of('detach')
->append(Str::singular(class_basename($relatedModel)))
->__toString();

return Gate::forUser($user)->any([$detach, $detachAny], $foreignModel);
}

/**
* Get the authorization to update a pivot.
*
* @param User $user
* @param Model $foreignModel
* @param Model $relatedModel
* @return bool
*/
protected function forUpdate(User $user, Model $foreignModel, Model $relatedModel): bool
{
return Gate::forUser($user)->check('update', $foreignModel)
&& Gate::forUser($user)->check('update', $relatedModel);
}
}
20 changes: 20 additions & 0 deletions app/Policies/Auth/PermissionPolicy.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,16 @@ public function attachRole(): bool
return false;
}

/**
* Determine whether the user can detach any role from the permission.
*
* @return bool
*/
public function detachAnyRole(): bool
{
return false;
}

/**
* Determine whether the user can detach a role from the permission.
*
Expand Down Expand Up @@ -133,6 +143,16 @@ public function attachUser(): bool
return false;
}

/**
* Determine whether the user can detach any user from the permission.
*
* @return bool
*/
public function detachAnyUser(): bool
{
return false;
}

/**
* Determine whether the user can detach a user from the permission.
*
Expand Down
26 changes: 23 additions & 3 deletions app/Policies/Auth/RolePolicy.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public function view(?User $user, Model $role): bool
* @param User $user
* @param Role $role
* @return bool
*
*
* @noinspection PhpUnusedParameterInspection
*/
public function update(User $user, Model $role): bool
Expand All @@ -59,7 +59,7 @@ public function update(User $user, Model $role): bool
* @param User $user
* @param Role $role
* @return bool
*
*
* @noinspection PhpUnusedParameterInspection
*/
public function delete(User $user, Model $role): bool
Expand All @@ -73,7 +73,7 @@ public function delete(User $user, Model $role): bool
* @param User $user
* @param Role $role
* @return bool
*
*
* @noinspection PhpUnusedParameterInspection
*/
public function restore(User $user, Model $role): bool
Expand Down Expand Up @@ -101,6 +101,16 @@ public function attachPermission(): bool
return false;
}

/**
* Determine whether the user can detach any permission from the role.
*
* @return bool
*/
public function detachAnyPermission(): bool
{
return false;
}

/**
* Determine whether the user can detach a permission from the role.
*
Expand Down Expand Up @@ -131,6 +141,16 @@ public function attachUser(): bool
return false;
}

/**
* Determine whether the user can detach any user from the role.
*
* @return bool
*/
public function detachAnyUser(): bool
{
return false;
}

/**
* Determine whether the user can detach a user from the role.
*
Expand Down
Loading

0 comments on commit 912a9c7

Please sign in to comment.