diff --git a/app/Enums/Permissions/AccessManager.php b/app/Enums/Permissions/AccessManager.php deleted file mode 100644 index 8d43f87..0000000 --- a/app/Enums/Permissions/AccessManager.php +++ /dev/null @@ -1,11 +0,0 @@ -can(UserPermissionEnum::Create); + @can(UserPermissionEnum::Create); +``` diff --git a/app/Enums/Permissions/RoleAndAppsPermissionEnum.php b/app/Enums/Permissions/RoleAndAppsPermissionEnum.php new file mode 100644 index 0000000..e461fe6 --- /dev/null +++ b/app/Enums/Permissions/RoleAndAppsPermissionEnum.php @@ -0,0 +1,14 @@ +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; } } diff --git a/app/Models/Role.php b/app/Models/Role.php index aa3555f..49dbc6d 100644 --- a/app/Models/Role.php +++ b/app/Models/Role.php @@ -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'); + } } diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index b944316..e189687 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -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); } } diff --git a/database/seeders/DefaultRoleWithPermissionSeeder.php b/database/seeders/DefaultRoleWithPermissionSeeder.php index effd0de..7d28392 100644 --- a/database/seeders/DefaultRoleWithPermissionSeeder.php +++ b/database/seeders/DefaultRoleWithPermissionSeeder.php @@ -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); } } diff --git a/resources/views/components/dashboard-sidebar.blade.php b/resources/views/components/dashboard-sidebar.blade.php index 086999e..c243d0a 100644 --- a/resources/views/components/dashboard-sidebar.blade.php +++ b/resources/views/components/dashboard-sidebar.blade.php @@ -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) ), diff --git a/resources/views/pages/access-manager/users/⚡index.blade.php b/resources/views/pages/access-manager/users/⚡index.blade.php index 378c12e..4e92e97 100644 --- a/resources/views/pages/access-manager/users/⚡index.blade.php +++ b/resources/views/pages/access-manager/users/⚡index.blade.php @@ -347,7 +347,7 @@ class="toggle-primary" Cancel - Save Roles + Save diff --git a/routes/settings.php b/routes/settings.php index 4020e94..3dbaa9e 100644 --- a/routes/settings.php +++ b/routes/settings.php @@ -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'); }); diff --git a/routes/web.php b/routes/web.php index 07705e1..6f74fba 100644 --- a/routes/web.php +++ b/routes/web.php @@ -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'); diff --git a/tests/Feature/Settings/ProfileUpdateTest.php b/tests/Feature/Settings/ProfileUpdateTest.php index c07c860..5d8e930 100644 --- a/tests/Feature/Settings/ProfileUpdateTest.php +++ b/tests/Feature/Settings/ProfileUpdateTest.php @@ -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(); }); diff --git a/tests/Feature/Settings/SecurityTest.php b/tests/Feature/Settings/SecurityTest.php index e626489..77343e2 100644 --- a/tests/Feature/Settings/SecurityTest.php +++ b/tests/Feature/Settings/SecurityTest.php @@ -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()])