Refactor: consolidate and replace permission enums for streamlined access management
- Replaced `AccessManager`, `Apps`, and `Settings` enums with modular enums like `RoleAndAppsPermissionEnum`, `AppPermissionEnum`, etc. - Updated route, view, Livewire components, and tests to use new modular enums. - Enhanced `DefaultRoleWithPermissionSeeder` and `PermissionSeeder` with updated enums. - Added README in the Enums/Permissions directory to explain usage and naming conventions. - Refactored dashboard sidebar and route definitions for better enum-based permission handling. - Improved test coverage to validate new permission enums functionality.
This commit is contained in:
parent
4fe1765525
commit
d7c2491fed
@ -1,11 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enums\Permissions;
|
||||
|
||||
enum AccessManager: string
|
||||
{
|
||||
case RoleAndApps = 'role-and-apps';
|
||||
case Users = 'users';
|
||||
}
|
||||
14
app/Enums/Permissions/AppPermissionEnum.php
Normal file
14
app/Enums/Permissions/AppPermissionEnum.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enums\Permissions;
|
||||
|
||||
enum AppPermissionEnum: string
|
||||
{
|
||||
case Access = 'app:access';
|
||||
case Create = 'app:create';
|
||||
case Read = 'app:read';
|
||||
case Update = 'app:update';
|
||||
case Delete = 'app:delete';
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enums\Permissions;
|
||||
|
||||
enum Apps: string
|
||||
{
|
||||
case AllApps = 'all-apps';
|
||||
case Providers = 'providers';
|
||||
}
|
||||
14
app/Enums/Permissions/ConnectionProviderPermissionEnum.php
Normal file
14
app/Enums/Permissions/ConnectionProviderPermissionEnum.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enums\Permissions;
|
||||
|
||||
enum ConnectionProviderPermissionEnum: string
|
||||
{
|
||||
case Access = 'connection-provider:access';
|
||||
case Create = 'connection-provider:create';
|
||||
case Read = 'connection-provider:read';
|
||||
case Update = 'connection-provider:update';
|
||||
case Delete = 'connection-provider:delete';
|
||||
}
|
||||
14
app/Enums/Permissions/ProfilePermissionEnum.php
Normal file
14
app/Enums/Permissions/ProfilePermissionEnum.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enums\Permissions;
|
||||
|
||||
enum ProfilePermissionEnum: string
|
||||
{
|
||||
case Access = 'profile:access';
|
||||
case Create = 'profile:create';
|
||||
case Read = 'profile:read';
|
||||
case Update = 'profile:update';
|
||||
case Delete = 'profile:delete';
|
||||
}
|
||||
34
app/Enums/Permissions/README.md
Normal file
34
app/Enums/Permissions/README.md
Normal file
@ -0,0 +1,34 @@
|
||||
# Permissions Enums
|
||||
|
||||
This directory contains all the permissions enums.
|
||||
Spatie/laravel-permission uses these enums to check permissions.
|
||||
Any Permission enum added here will be automatically added to the database, when the
|
||||
[PermissionSeeder](../../../database/seeders/PermissionSeeder.php) is run, no need to add it manually.
|
||||
This is done via Reflection API.
|
||||
|
||||
`Only string backed are supported.`
|
||||
|
||||
## Value format of Enum
|
||||
|
||||
We are following the convention of `scope:resource:action` .
|
||||
|
||||
**File name** of the enum should be same as the **scope name**, and Enum *value* should be in the format of
|
||||
*resource:action*.
|
||||
|
||||
Example:
|
||||
UserPermissionEnum.php
|
||||
|
||||
```php
|
||||
namespace App\Enums\Permissions;
|
||||
enum UserPermissionEnum:string
|
||||
{
|
||||
case Create = 'user:create';
|
||||
case Read = 'user:read';
|
||||
case Update = 'user:update';
|
||||
case Delete = 'user:delete';
|
||||
}
|
||||
|
||||
// using the enum makes straight forward permission check
|
||||
$user->can(UserPermissionEnum::Create);
|
||||
@can(UserPermissionEnum::Create);
|
||||
```
|
||||
14
app/Enums/Permissions/RoleAndAppsPermissionEnum.php
Normal file
14
app/Enums/Permissions/RoleAndAppsPermissionEnum.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enums\Permissions;
|
||||
|
||||
enum RoleAndAppsPermissionEnum: string
|
||||
{
|
||||
case Access = 'roles-and-apps:access';
|
||||
case Create = 'roles-and-apps:create';
|
||||
case Read = 'roles-and-apps:read';
|
||||
case Update = 'roles-and-apps:update';
|
||||
case Delete = 'roles-and-apps:delete';
|
||||
}
|
||||
14
app/Enums/Permissions/SecurityPermissionEnum.php
Normal file
14
app/Enums/Permissions/SecurityPermissionEnum.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enums\Permissions;
|
||||
|
||||
enum SecurityPermissionEnum: string
|
||||
{
|
||||
case Access = 'security:access';
|
||||
case Create = 'security:create';
|
||||
case Read = 'security:read';
|
||||
case Update = 'security:update';
|
||||
case Delete = 'security:delete';
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enums\Permissions;
|
||||
|
||||
enum Settings: string
|
||||
{
|
||||
case Profile = 'profile';
|
||||
case Security = 'security';
|
||||
}
|
||||
14
app/Enums/Permissions/UserPermissionEnum.php
Normal file
14
app/Enums/Permissions/UserPermissionEnum.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enums\Permissions;
|
||||
|
||||
enum UserPermissionEnum: string
|
||||
{
|
||||
case Access = 'user:access';
|
||||
case Create = 'user:create';
|
||||
case Read = 'user:read';
|
||||
case Update = 'user:update';
|
||||
case Delete = 'user:delete';
|
||||
}
|
||||
@ -10,22 +10,59 @@
|
||||
class AppPermission
|
||||
{
|
||||
/**
|
||||
* Generates the standard permission string.
|
||||
* The standard scope name for connected app permissions.
|
||||
*/
|
||||
public const SCOPE = 'app';
|
||||
|
||||
/**
|
||||
* Generates the standard permission string following the scope:resource:action convention.
|
||||
* Format: app:{slug}:{action}
|
||||
*
|
||||
* @param ConnectedAppPermissonEnum $permission The action to perform (e.g., 'use')
|
||||
* @param string|ConnectedAppData $app The connected app object or its slug
|
||||
*
|
||||
* @return string The formatted permission name
|
||||
*/
|
||||
public static function name(ConnectedAppPermissonEnum $permission, string|ConnectedAppData $app): string
|
||||
{
|
||||
$slug = is_string($app) ? $app : $app->slug;
|
||||
|
||||
return "{$permission->value}-{$slug}";
|
||||
return sprintf('%s:%s:%s', self::SCOPE, $slug, $permission->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $permissionName This should be the name constructed by the {@see self::name()} method.
|
||||
* Parses the permission string to extract the ConnectedAppPermissonEnum type (action).
|
||||
*
|
||||
* @param string $permissionName The constructed permission name
|
||||
*
|
||||
* @return ?ConnectedAppPermissonEnum The matching enum or null
|
||||
*/
|
||||
public static function type(string $permissionName): ?ConnectedAppPermissonEnum
|
||||
{
|
||||
$permission = explode('-', $permissionName);
|
||||
$parts = explode(':', $permissionName);
|
||||
|
||||
return ConnectedAppPermissonEnum::tryFrom($permission[0]);
|
||||
if (3 === count($parts) && self::SCOPE === $parts[0]) {
|
||||
return ConnectedAppPermissonEnum::tryFrom($parts[2]);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the permission string to extract the connected app slug (resource).
|
||||
*
|
||||
* @param string $permissionName The constructed permission name
|
||||
*
|
||||
* @return ?string The app slug or null
|
||||
*/
|
||||
public static function slug(string $permissionName): ?string
|
||||
{
|
||||
$parts = explode(':', $permissionName);
|
||||
|
||||
if (3 === count($parts) && self::SCOPE === $parts[0]) {
|
||||
return $parts[1];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,10 +5,14 @@
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Spatie\Activitylog\Models\Concerns\LogsActivity;
|
||||
use Spatie\Activitylog\Support\LogOptions;
|
||||
use Spatie\Permission\Models\Role as SpatieRole;
|
||||
|
||||
class Role extends SpatieRole
|
||||
{
|
||||
use LogsActivity;
|
||||
|
||||
public function apps(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(
|
||||
@ -20,4 +24,12 @@ public function apps(): BelongsToMany
|
||||
->using(AppRole::class)
|
||||
->withPivot('id', 'duration');
|
||||
}
|
||||
|
||||
public function getActivitylogOptions(): LogOptions
|
||||
{
|
||||
return LogOptions::defaults()
|
||||
->logOnly(['name', 'priority'])
|
||||
->logOnlyDirty()
|
||||
->useLogName('role_management');
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,6 +19,7 @@ public function run(): void
|
||||
$this->call(ConnectionProtocolSeeder::class);
|
||||
$this->call(ConnectionProviderSeeder::class);
|
||||
$this->call(PermissionSeeder::class);
|
||||
$this->call(DefaultRoleWithPermissionSeeder::class);
|
||||
$this->call(ExampleUserWithRoleSeeder::class);
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Enums\DefaultUserRole;
|
||||
use App\Enums\Permissions\Settings;
|
||||
use App\Enums\Permissions\{ProfilePermissionEnum, SecurityPermissionEnum};
|
||||
use App\Models\Role;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
@ -22,6 +22,6 @@ public function run(): void
|
||||
|
||||
private function syncUserPermissions(Role $role): void
|
||||
{
|
||||
$role->givePermissionTo(Settings::Profile->value, Settings::Security->value);
|
||||
$role->givePermissionTo(ProfilePermissionEnum::Access->value, SecurityPermissionEnum::Access->value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,9 +2,7 @@
|
||||
|
||||
use App\Data\Ui\Sidebar\NavItemData;
|
||||
use App\Data\Ui\Sidebar\NavSubItemData;
|
||||
use App\Enums\Permissions\AccessManager;
|
||||
use App\Enums\Permissions\Apps;
|
||||
use App\Enums\Permissions\Settings;
|
||||
use App\Enums\Permissions\{AppPermissionEnum, ConnectionProviderPermissionEnum, ProfilePermissionEnum, RoleAndAppsPermissionEnum, SecurityPermissionEnum, UserPermissionEnum};
|
||||
use Livewire\Component;
|
||||
use Spatie\LaravelData\DataCollection;
|
||||
|
||||
@ -45,13 +43,13 @@ public function navItems(): DataCollection
|
||||
label: 'All Apps',
|
||||
route: 'apps.index',
|
||||
active_pattern: 'apps.index',
|
||||
permission: Apps::AllApps
|
||||
permission: AppPermissionEnum::Access
|
||||
),
|
||||
new NavSubItemData(
|
||||
label: 'Providers',
|
||||
route: 'apps.connectionProviders.index',
|
||||
active_pattern: 'apps.connectionProviders.*',
|
||||
permission: Apps::Providers
|
||||
permission: ConnectionProviderPermissionEnum::Access
|
||||
)
|
||||
], DataCollection::class)
|
||||
),
|
||||
@ -65,13 +63,13 @@ public function navItems(): DataCollection
|
||||
label: 'Roles and Apps',
|
||||
route: 'access-manager.roles-and-apps.index',
|
||||
active_pattern: 'access-manager.roles-and-apps.*',
|
||||
permission: AccessManager::RoleAndApps
|
||||
permission: RoleAndAppsPermissionEnum::Access
|
||||
),
|
||||
new NavSubItemData(
|
||||
label: 'Users',
|
||||
route: 'access-manager.users.index',
|
||||
active_pattern: 'access-manager.users.*',
|
||||
permission: AccessManager::Users
|
||||
permission: UserPermissionEnum::Access
|
||||
),
|
||||
], DataCollection::class)
|
||||
),
|
||||
@ -85,13 +83,13 @@ public function navItems(): DataCollection
|
||||
label: 'Profile',
|
||||
route: 'profile.edit',
|
||||
active_pattern: 'profile.edit',
|
||||
permission: Settings::Profile
|
||||
permission: ProfilePermissionEnum::Access
|
||||
),
|
||||
new NavSubItemData(
|
||||
label: 'Security',
|
||||
route: 'security.edit',
|
||||
active_pattern: 'security.edit',
|
||||
permission: Settings::Security
|
||||
permission: SecurityPermissionEnum::Access
|
||||
),
|
||||
], DataCollection::class)
|
||||
),
|
||||
|
||||
@ -347,7 +347,7 @@ class="toggle-primary"
|
||||
Cancel
|
||||
</x-mary-button>
|
||||
<x-mary-button type="submit" spinner="saveRoles" class="btn-primary">
|
||||
Save Roles
|
||||
Save
|
||||
</x-mary-button>
|
||||
</x-slot:actions>
|
||||
</x-mary-form>
|
||||
|
||||
@ -2,9 +2,11 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Enums\Permissions\{ProfilePermissionEnum, SecurityPermissionEnum};
|
||||
use App\Livewire\Settings\{Appearance, Profile, Security};
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Laravel\Fortify\Features;
|
||||
use Spatie\Permission\Middleware\PermissionMiddleware;
|
||||
|
||||
// Grouping by the "Settings" sidebar category
|
||||
Route::middleware(['auth'])->prefix('settings')->group(function (): void {
|
||||
@ -13,7 +15,9 @@
|
||||
Route::redirect('/', '/settings/profile')->name('settings');
|
||||
|
||||
// Profile Settings
|
||||
Route::livewire('profile', Profile::class)->name('profile.edit');
|
||||
Route::livewire('profile', Profile::class)
|
||||
->middleware(PermissionMiddleware::using(ProfilePermissionEnum::Access))
|
||||
->name('profile.edit');
|
||||
|
||||
// Settings that require email verification
|
||||
Route::middleware(['verified'])->group(function (): void {
|
||||
@ -24,12 +28,15 @@
|
||||
// Security Settings
|
||||
Route::livewire('security', Security::class)
|
||||
->middleware(
|
||||
when(
|
||||
Features::canManageTwoFactorAuthentication()
|
||||
&& Features::optionEnabled(Features::twoFactorAuthentication(), 'confirmPassword'),
|
||||
['password.confirm'],
|
||||
[],
|
||||
),
|
||||
array_merge(
|
||||
[PermissionMiddleware::using(SecurityPermissionEnum::Access)],
|
||||
when(
|
||||
Features::canManageTwoFactorAuthentication()
|
||||
&& Features::optionEnabled(Features::twoFactorAuthentication(), 'confirmPassword'),
|
||||
['password.confirm'],
|
||||
[],
|
||||
),
|
||||
)
|
||||
)
|
||||
->name('security.edit');
|
||||
});
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Enums\DefaultUserRole;
|
||||
use App\Enums\Permissions\{AccessManager, Apps};
|
||||
use App\Enums\Permissions\{AppPermissionEnum, ConnectionProviderPermissionEnum, RoleAndAppsPermissionEnum, UserPermissionEnum};
|
||||
use App\Http\Controllers\ResolveDashboardRouteController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Spatie\Permission\Middleware\{PermissionMiddleware, RoleMiddleware};
|
||||
@ -23,28 +23,28 @@
|
||||
|
||||
Route::prefix('access-manager')->name('access-manager.')->group(function (): void {
|
||||
Route::prefix('roles-and-apps')->name('roles-and-apps.')
|
||||
->middleware(PermissionMiddleware::using(AccessManager::RoleAndApps))
|
||||
->middleware(PermissionMiddleware::using(RoleAndAppsPermissionEnum::Access))
|
||||
->group(function (): void {
|
||||
Route::livewire('roles-and-apps', 'pages::access-manager.roles-and-apps.index')->name('index');
|
||||
Route::livewire('roles-and-apps/{id}', 'pages::access-manager.roles-and-apps.show')->name('show');
|
||||
});
|
||||
|
||||
Route::prefix('users')->name('users.')
|
||||
->middleware(PermissionMiddleware::using(AccessManager::Users))
|
||||
->middleware(PermissionMiddleware::using(UserPermissionEnum::Access))
|
||||
->group(function (): void {
|
||||
Route::livewire('users', 'pages::access-manager.users.index')->name('index');
|
||||
});
|
||||
});
|
||||
|
||||
Route::prefix('apps')->name('apps.')->group(function (): void {
|
||||
Route::middleware(PermissionMiddleware::using(Apps::AllApps))->group(function (): void {
|
||||
Route::middleware(PermissionMiddleware::using(AppPermissionEnum::Access))->group(function (): void {
|
||||
Route::livewire('create', 'pages::connected-apps.create')->name('create');
|
||||
Route::livewire('{id}/edit', 'pages::connected-apps.edit')->name('edit');
|
||||
Route::livewire('/', 'pages::connected-apps.index')->name('index');
|
||||
});
|
||||
|
||||
Route::prefix('connection-providers')->name('connectionProviders.')
|
||||
->middleware(PermissionMiddleware::using(Apps::Providers))
|
||||
->middleware(PermissionMiddleware::using(ConnectionProviderPermissionEnum::Access))
|
||||
->group(function (): void {
|
||||
Route::livewire('create', 'pages::connecton-providers.create')->name('create');
|
||||
Route::livewire('{slug}/edit', 'pages::connecton-providers.edit')->name('edit');
|
||||
|
||||
@ -2,12 +2,20 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Enums\Permissions\ProfilePermissionEnum;
|
||||
use App\Livewire\Settings\Profile;
|
||||
use App\Models\User;
|
||||
use Livewire\Livewire;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
|
||||
beforeEach(function (): void {
|
||||
Permission::findOrCreate(ProfilePermissionEnum::Access->value);
|
||||
});
|
||||
|
||||
test('profile page is displayed', function (): void {
|
||||
$this->actingAs($user = User::factory()->create());
|
||||
$user = User::factory()->create();
|
||||
$user->givePermissionTo(ProfilePermissionEnum::Access->value);
|
||||
$this->actingAs($user);
|
||||
|
||||
$this->get('/settings/profile')->assertOk();
|
||||
});
|
||||
|
||||
@ -2,11 +2,13 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Enums\Permissions\SecurityPermissionEnum;
|
||||
use App\Livewire\Settings\Security;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Laravel\Fortify\Features;
|
||||
use Livewire\Livewire;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
|
||||
beforeEach(function (): void {
|
||||
$this->skipUnlessFortifyHas(Features::twoFactorAuthentication());
|
||||
@ -15,10 +17,13 @@
|
||||
'confirm' => true,
|
||||
'confirmPassword' => true,
|
||||
]);
|
||||
|
||||
Permission::findOrCreate(SecurityPermissionEnum::Access->value);
|
||||
});
|
||||
|
||||
test('security settings page can be rendered', function (): void {
|
||||
$user = User::factory()->create();
|
||||
$user->givePermissionTo(SecurityPermissionEnum::Access->value);
|
||||
|
||||
$this->actingAs($user)
|
||||
->withSession(['auth.password_confirmed_at' => time()])
|
||||
@ -30,6 +35,7 @@
|
||||
|
||||
test('security settings page requires password confirmation when enabled', function (): void {
|
||||
$user = User::factory()->create();
|
||||
$user->givePermissionTo(SecurityPermissionEnum::Access->value);
|
||||
|
||||
$response = $this->actingAs($user)
|
||||
->get(route('security.edit'));
|
||||
@ -41,6 +47,7 @@
|
||||
config(['fortify.features' => []]);
|
||||
|
||||
$user = User::factory()->create();
|
||||
$user->givePermissionTo(SecurityPermissionEnum::Access->value);
|
||||
|
||||
$this->actingAs($user)
|
||||
->withSession(['auth.password_confirmed_at' => time()])
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user