feat(permissions): implement permission management enhancements

- Mengubah logika akses `PermissionsController` dengan:
  - Menambahkan konstruktor untuk mendefinisikan user yang terautentikasi.
  - Mengganti logika permission check dari `permissions.*` ke `usermanagement.*`.
  - Menambahkan validasi `abort` untuk operasi CRUD jika user tidak memiliki hak akses.
  - Memperbarui respons penghapusan permission menjadi JSON yang lebih semantik.

- Memperbarui `PermissionGroup` untuk:
  - Menambahkan mekanisme auto-generated slug saat membuat instance baru.

- Memperbaiki export logic pada `PermissionExport` dengan:
  - Mengonversi array `roles` ke collection sebelum menggunakan fungsi `pluck`.

- Menambahkan soft delete pada model `Permission`, memungkinkan penghapusan data non-permanen.

- Menghapus elemen filter dropdown di view `permissions.index` untuk meningkatkan clarity UI.

- Menambahkan comprehensive test suite pada `PermissionsControllerTest` untuk:
  - Menguji validasi CRUD pada permission dengan role dan tanpa role.
  - Menguji restore permissions yang terhapus.
  - Menguji filter, pencarian, dan sorting pada datatables.
  - Menguji export permissions.

Perubahan ini meningkatkan pengelolaan permission, validasi akses, dan memperkaya pengujian untuk memastikan kualitas fitur permission management.
This commit is contained in:
Daeng Deni Mardaeni
2025-05-18 18:23:06 +07:00
parent 8bd31cf54f
commit 1e958c9dd7
6 changed files with 517 additions and 52 deletions

View File

@@ -21,7 +21,8 @@ class PermissionExport implements WithColumnFormatting, WithHeadings, FromCollec
}
public function map($row): array{
$role = $row->roles->pluck('name')->toArray();
// Convert the array to a collection before using pluck
$role = collect($row->roles)->pluck('name')->toArray();
return [
$row->id,
$row->name,

View File

@@ -25,6 +25,16 @@
*/
public $user;
/**
* UsersController constructor.
*
* Initializes the user property with the authenticated user.
*/
public function __construct()
{
$this->user = Auth::guard('web')->user();
}
/**
* Display a listing of the resource.
*
@@ -34,8 +44,8 @@
public function index()
{
// Check if the authenticated user has the required permission to view permissions
if (is_null($this->user) || !$this->user->can('permissions.view')) {
//abort(403, 'Sorry! You are not allowed to view permissions.');
if (is_null($this->user) || !$this->user->can('usermanagement.read')) {
abort(403, 'Sorry! You are not allowed to view permissions.');
}
// Return the view for displaying the permissions
@@ -53,8 +63,8 @@
public function store(PermissionRequest $request)
{
// Check if the authenticated user has the required permission to store permissions
if (is_null($this->user) || !$this->user->can('permissions.store')) {
//abort(403, 'Sorry! You are not allowed to store permissions.');
if (is_null($this->user) || !$this->user->can('usermanagement.store')) {
abort(403, 'Sorry! You are not allowed to store permissions.');
}
$validate = $request->validated();
@@ -97,24 +107,14 @@
public function create()
{
// Check if the authenticated user has the required permission to create permissions
if (is_null($this->user) || !$this->user->can('permissions.create')) {
//abort(403, 'Sorry! You are not allowed to create permissions.');
if (is_null($this->user) || !$this->user->can('usermanagement.create')) {
abort(403, 'Sorry! You are not allowed to create permissions.');
}
// Return the view for creating a new role
return view('usermanagement::permissions.create');
}
public function show($id){
// Check if the authenticated user has the required permission to view permissions
if (is_null($this->user) ||!$this->user->can('permissions.view')) {
//abort(403, 'Sorry! You are not allowed to view permissions.');
}
// Return the view for editing the role
return view('usermanagement::permissions.create');
}
/**
* Show the form for editing the specified resource.
*
@@ -126,8 +126,8 @@
public function edit($id)
{
// Check if the authenticated user has the required permission to edit permissions
if (is_null($this->user) || !$this->user->can('permissions.edit')) {
//abort(403, 'Sorry! You are not allowed to edit permissions.');
if (is_null($this->user) || !$this->user->can('usermanagement.edit')) {
abort(403, 'Sorry! You are not allowed to edit permissions.');
}
$permission = PermissionGroup::find($id);
@@ -150,8 +150,8 @@
public function update(PermissionRequest $request, $id)
{
// Check if the authenticated user has the required permission to update permissions
if (is_null($this->user) || !$this->user->can('permissions.update')) {
//abort(403, 'Sorry! You are not allowed to update permissions.');
if (is_null($this->user) || !$this->user->can('usermanagement.update')) {
abort(403, 'Sorry! You are not allowed to update permissions.');
}
$validated = $request->validated();
@@ -202,8 +202,8 @@
public function destroy($id)
{
// Check if the authenticated user has the required permission to delete permissions
if (is_null($this->user) || !$this->user->can('permissions.delete')) {
//abort(403, 'Sorry! You are not allowed to delete permissions.');
if (is_null($this->user) || !$this->user->can('usermanagement.delete')) {
abort(403, 'Sorry! You are not allowed to delete permissions.');
}
$permission = PermissionGroup::find($id);
@@ -214,7 +214,7 @@
}
// Redirect back to the permissions index with a success message
echo json_encode(['message' => 'Permission deleted successfully.', 'success' => true]);
return response()->json(['message' => 'Permission deleted successfully.','success' => true]);
}
/**
@@ -228,7 +228,7 @@
public function restore($id)
{
// Check if the authenticated user has the required permission to restore permissions
if (is_null($this->user) || !$this->user->can('permissions.restore')) {
if (is_null($this->user) || !$this->user->can('usermanagement.restore')) {
abort(403, 'Sorry! You are not allowed to restore permissions.');
}
@@ -257,8 +257,8 @@
*/
public function dataForDatatables(Request $request)
{
if (is_null($this->user) || !$this->user->can('permissions.view')) {
//abort(403, 'Sorry! You are not allowed to view users.');
if (is_null($this->user) || !$this->user->can('usermanagement.read')) {
abort(403, 'Sorry! You are not allowed to view users.');
}
// Retrieve data from the database

View File

@@ -2,13 +2,14 @@
namespace Modules\Usermanagement\Models;
use Spatie\Activitylog\LogOptions;
use Illuminate\Database\Eloquent\SoftDeletes;
use Spatie\Activitylog\LogOptions;
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Permission\Models\Permission as SpatiePermission;
class Permission extends SpatiePermission
{
use LogsActivity;
use LogsActivity, SoftDeletes;
/**
* Retrieve the activity log options for this permission.

View File

@@ -12,6 +12,17 @@
'slug'
];
protected static function boot()
{
parent::boot();
static::creating(function ($model) {
if (!$model->slug) {
$model->slug = \Str::slug($model->name);
}
});
}
/**
* Retrieves all permissions associated with a given permission group ID.
*

View File

@@ -19,29 +19,7 @@
</label>
</div>
<div class="flex flex-wrap gap-2.5">
<select class="select select-sm w-28">
<option value="1">
Active
</option>
<option value="2">
Disabled
</option>
<option value="2">
Pending
</option>
</select>
<select class="select select-sm w-28">
<option value="desc">
Latest
</option>
<option value="asc">
Oldest
</option>
</select>
<button class="btn btn-sm btn-outline btn-primary">
<i class="ki-filled ki-setting-4"> </i> <Filters></Filters>
</button>
<div class="flex flex-wrap gap-2.5 lg:gap-5">
<div class="h-[24px] border border-r-gray-200"> </div>
<a class="btn btn-sm btn-light" href="{{ route('users.permissions.export') }}"> Export to Excel </a>
<a class="btn btn-sm btn-primary" href="{{ route('users.permissions.create') }}"> Add Permission </a>

View File

@@ -0,0 +1,474 @@
<?php
namespace Modules\Usermanagement\Tests\Feature;
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Modules\Usermanagement\Models\Permission;
use Modules\Usermanagement\Models\PermissionGroup;
use Modules\Usermanagement\Models\Role;
use Modules\Usermanagement\Models\User;
use Illuminate\Support\Facades\Auth;
class PermissionsControllerTest extends TestCase
{
use RefreshDatabase, WithFaker;
protected $user;
protected $adminRole;
protected $permissionGroup;
/**
* Setup the test environment.
*/
protected function setUp(): void
{
parent::setUp();
// Create admin role
$this->adminRole = Role::create([
'name' => 'Admin',
'guard_name' => 'web'
]);
// Create a permission group for usermanagement permissions
$usermanagementGroup = PermissionGroup::create(['name' => 'Usermanagement']);
// Create permissions
$permissions = [
'usermanagement.create',
'usermanagement.read',
'usermanagement.update',
'usermanagement.delete',
'usermanagement.export',
'usermanagement.store',
'usermanagement.edit',
'usermanagement.view',
'usermanagement.restore'
];
foreach ($permissions as $permission) {
Permission::create([
'name' => $permission,
'guard_name' => 'web',
'permission_group_id' => $usermanagementGroup->id
]);
}
// Assign permissions to admin role
$this->adminRole->givePermissionTo($permissions);
// Create user with admin role
$this->user = User::create([
'name' => 'Admin User',
'email' => 'admin@example.com',
'password' => bcrypt('password')
]);
$this->user->assignRole($this->adminRole);
// Create a test permission group
$this->permissionGroup = PermissionGroup::create(['name' => 'Test Group']);
}
/**
* Test user with permission can view permissions index.
*/
public function test_user_with_permission_can_view_permissions_index(): void
{
$this->actingAs($this->user);
$response = $this->get(route('users.permissions.index'));
$response->assertStatus(200);
$response->assertViewIs('usermanagement::permissions.index');
}
/**
* Test user without permission cannot view permissions index.
*/
public function test_user_without_permission_cannot_view_permissions_index(): void
{
// Create user without permissions
$user = User::create([
'name' => 'Regular User',
'email' => 'user@example.com',
'password' => bcrypt('password')
]);
$this->actingAs($user);
$response = $this->get(route('users.permissions.index'));
$response->assertStatus(403);
}
/**
* Test user with permission can create permission.
*/
public function test_user_with_permission_can_create_permission(): void
{
$this->actingAs($this->user);
$response = $this->get(route('users.permissions.create'));
$response->assertStatus(200);
$response->assertViewIs('usermanagement::permissions.create');
}
/**
* Test user without permission cannot create permission.
*/
public function test_user_without_permission_cannot_create_permission(): void
{
// Create user without permissions
$user = User::create([
'name' => 'Regular User',
'email' => 'user2@example.com',
'password' => bcrypt('password')
]);
$this->actingAs($user);
$response = $this->get(route('users.permissions.create'));
$response->assertStatus(403);
}
/**
* Test user with permission can store permission.
*/
public function test_user_with_permission_can_store_permission(): void
{
$this->actingAs($this->user);
$data = [
'name' => 'TestPermission'
];
$response = $this->post(route('users.permissions.store'), $data);
$response->assertRedirect(route('users.permissions.index'));
$response->assertSessionHas('success');
$this->assertDatabaseHas('permission_groups', ['name' => 'TestPermission']);
// Check if all the required permissions were created
$this->assertDatabaseHas('permissions', ['name' => 'testpermission.create']);
$this->assertDatabaseHas('permissions', ['name' => 'testpermission.read']);
$this->assertDatabaseHas('permissions', ['name' => 'testpermission.update']);
$this->assertDatabaseHas('permissions', ['name' => 'testpermission.delete']);
$this->assertDatabaseHas('permissions', ['name' => 'testpermission.export']);
$this->assertDatabaseHas('permissions', ['name' => 'testpermission.authorize']);
$this->assertDatabaseHas('permissions', ['name' => 'testpermission.report']);
}
/**
* Test user without permission cannot store permission.
*/
public function test_user_without_permission_cannot_store_permission(): void
{
// Create user without permissions
$user = User::create([
'name' => 'Regular User',
'email' => 'user3@example.com',
'password' => bcrypt('password')
]);
$this->actingAs($user);
$data = [
'name' => 'TestPermission2'
];
$response = $this->post(route('users.permissions.store'), $data);
$response->assertStatus(403);
$this->assertDatabaseMissing('permission_groups', ['name' => 'TestPermission2']);
}
/**
* Test user with permission can edit permission.
*/
public function test_user_with_permission_can_edit_permission(): void
{
$this->actingAs($this->user);
$response = $this->get(route('users.permissions.edit', $this->permissionGroup->id));
$response->assertStatus(200);
$response->assertViewIs('usermanagement::permissions.create');
$response->assertViewHas('permission', $this->permissionGroup);
}
/**
* Test user without permission cannot edit permission.
*/
public function test_user_without_permission_cannot_edit_permission(): void
{
// Create user without permissions
$user = User::create([
'name' => 'Regular User',
'email' => 'user4@example.com',
'password' => bcrypt('password')
]);
$this->actingAs($user);
$response = $this->get(route('users.permissions.edit', $this->permissionGroup->id));
$response->assertStatus(403);
}
/**
* Test user with permission can update permission.
*/
public function test_user_with_permission_can_update_permission(): void
{
$this->actingAs($this->user);
// Create permissions for the test group
$permissions = [
'test group.create',
'test group.read',
'test group.update',
'test group.delete',
'test group.export',
'test group.authorize',
'test group.report'
];
foreach ($permissions as $permission) {
Permission::create([
'name' => $permission,
'guard_name' => 'web',
'permission_group_id' => $this->permissionGroup->id
]);
}
$data = [
'name' => 'Updated Group'
];
$response = $this->put(route('users.permissions.update', $this->permissionGroup->id), $data);
$response->assertRedirect(route('users.permissions.index'));
$response->assertSessionHas('success');
$this->assertDatabaseHas('permission_groups', [
'id' => $this->permissionGroup->id,
'name' => 'Updated Group'
]);
// Check if all the permissions were updated
$this->assertDatabaseHas('permissions', ['name' => 'updated group.create']);
$this->assertDatabaseHas('permissions', ['name' => 'updated group.read']);
$this->assertDatabaseHas('permissions', ['name' => 'updated group.update']);
$this->assertDatabaseHas('permissions', ['name' => 'updated group.delete']);
$this->assertDatabaseHas('permissions', ['name' => 'updated group.export']);
$this->assertDatabaseHas('permissions', ['name' => 'updated group.authorize']);
$this->assertDatabaseHas('permissions', ['name' => 'updated group.report']);
}
/**
* Test user without permission cannot update permission.
*/
public function test_user_without_permission_cannot_update_permission(): void
{
// Create user without permissions
$user = User::create([
'name' => 'Regular User',
'email' => 'user5@example.com',
'password' => bcrypt('password')
]);
$this->actingAs($user);
$data = [
'name' => 'Should Not Update'
];
$response = $this->put(route('users.permissions.update', $this->permissionGroup->id), $data);
$response->assertStatus(403);
$this->assertDatabaseMissing('permission_groups', [
'id' => $this->permissionGroup->id,
'name' => 'Should Not Update'
]);
}
/**
* Test user with permission can delete permission.
*/
public function test_user_with_permission_can_delete_permission(): void
{
$this->actingAs($this->user);
$response = $this->delete(route('users.permissions.destroy', $this->permissionGroup->id));
$response->assertJson([
'message' => 'Permission deleted successfully.',
'success' => true
]);
$this->assertSoftDeleted('permission_groups', ['id' => $this->permissionGroup->id]);
}
/**
* Test user without permission cannot delete permission.
*/
public function test_user_without_permission_cannot_delete_permission(): void
{
// Create user without permissions
$user = User::create([
'name' => 'Regular User',
'email' => 'user6@example.com',
'password' => bcrypt('password')
]);
$this->actingAs($user);
$response = $this->delete(route('users.permissions.destroy', $this->permissionGroup->id));
$response->assertStatus(403);
$this->assertDatabaseHas('permission_groups', ['id' => $this->permissionGroup->id, 'deleted_at' => null]);
}
/**
* Test user with permission can restore permission.
*/
public function test_user_with_permission_can_restore_permission(): void
{
$this->actingAs($this->user);
// First delete the permission group
$this->permissionGroup->delete();
$this->assertSoftDeleted('permission_groups', ['id' => $this->permissionGroup->id]);
$response = $this->get(route('users.permissions.restore', $this->permissionGroup->id));
$response->assertRedirect(route('users.permissions.index'));
$response->assertSessionHas('success');
$this->assertDatabaseHas('permission_groups', ['id' => $this->permissionGroup->id, 'deleted_at' => null]);
}
/**
* Test user without permission cannot restore permission.
*/
public function test_user_without_permission_cannot_restore_permission(): void
{
// Create user without permissions
$user = User::create([
'name' => 'Regular User',
'email' => 'user7@example.com',
'password' => bcrypt('password')
]);
// First delete the permission group
$this->permissionGroup->delete();
$this->assertSoftDeleted('permission_groups', ['id' => $this->permissionGroup->id]);
$this->actingAs($user);
$response = $this->get(route('users.permissions.restore', $this->permissionGroup->id));
$response->assertStatus(403);
$this->assertSoftDeleted('permission_groups', ['id' => $this->permissionGroup->id]);
}
/**
* Test user with permission can access datatables data.
*/
public function test_user_with_permission_can_access_datatables_data(): void
{
$this->actingAs($this->user);
$response = $this->getJson(route('users.permissions.datatables') . '?page=1&size=10');
$response->assertStatus(200);
$response->assertJsonStructure([
'draw',
'recordsTotal',
'recordsFiltered',
'pageCount',
'page',
'totalCount',
'data'
]);
}
/**
* Test user without permission cannot access datatables data.
*/
public function test_user_without_permission_cannot_access_datatables_data(): void
{
// Create user without permissions
$user = User::create([
'name' => 'Regular User',
'email' => 'user8@example.com',
'password' => bcrypt('password')
]);
$this->actingAs($user);
$response = $this->getJson(route('users.permissions.datatables') . '?page=1&size=10');
$response->assertStatus(403);
}
/**
* Test datatables search filters permissions correctly.
*/
public function test_datatables_search_filters_permissions_correctly(): void
{
$this->actingAs($this->user);
// Create additional permission groups for testing search
PermissionGroup::create(['name' => 'SearchTest1']);
PermissionGroup::create(['name' => 'SearchTest2']);
PermissionGroup::create(['name' => 'DifferentName']);
$response = $this->getJson(route('users.permissions.datatables') . '?page=1&size=10&search=SearchTest');
$response->assertStatus(200);
$response->assertJsonCount(2, 'data');
$response->assertJsonPath('data.0.name', 'SearchTest1');
$response->assertJsonPath('data.1.name', 'SearchTest2');
}
/**
* Test datatables sorting works correctly.
*/
public function test_datatables_sorting_works_correctly(): void
{
$this->actingAs($this->user);
// Create additional permission groups for testing sorting
PermissionGroup::create(['name' => 'A-Group']);
PermissionGroup::create(['name' => 'Z-Group']);
// Test ascending order
$response = $this->getJson(route('users.permissions.datatables') . '?page=1&size=10&sortField=name&sortOrder=asc');
$response->assertStatus(200);
$response->assertJsonPath('data.0.name', 'A-Group');
// Test descending order
$response = $this->getJson(route('users.permissions.datatables') . '?page=1&size=10&sortField=name&sortOrder=desc');
$response->assertStatus(200);
$response->assertJsonPath('data.0.name', 'Z-Group');
}
/**
* Test export functionality.
*/
public function test_export_functionality(): void
{
$this->actingAs($this->user);
$response = $this->get(route('users.permissions.export'));
$response->assertStatus(200);
$response->assertHeader('content-type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
}
}