update hak akses role: view-point-audit terkunci 'Khusus Admin' (badge + kunci di UI, proteksi di backend update()) + test
This commit is contained in:
@@ -11,6 +11,13 @@ use Illuminate\View\View;
|
|||||||
|
|
||||||
class PermissionController extends Controller
|
class PermissionController extends Controller
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Permission yang TIDAK bisa diubah via halaman ini — khusus admin.
|
||||||
|
*/
|
||||||
|
public const ADMIN_ONLY_PERMISSIONS = [
|
||||||
|
'view-point-audit',
|
||||||
|
];
|
||||||
|
|
||||||
public function index(): View
|
public function index(): View
|
||||||
{
|
{
|
||||||
$permissions = DB::table('permissions')
|
$permissions = DB::table('permissions')
|
||||||
@@ -29,7 +36,9 @@ class PermissionController extends Controller
|
|||||||
->toArray();
|
->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
return view('settings.permissions', compact('permissions', 'roles', 'rolePermissions'));
|
$adminOnlyKeys = self::ADMIN_ONLY_PERMISSIONS;
|
||||||
|
|
||||||
|
return view('settings.permissions', compact('permissions', 'roles', 'rolePermissions', 'adminOnlyKeys'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update(Request $request): RedirectResponse
|
public function update(Request $request): RedirectResponse
|
||||||
@@ -46,10 +55,27 @@ class PermissionController extends Controller
|
|||||||
|
|
||||||
if (! empty($data['permissions'])) {
|
if (! empty($data['permissions'])) {
|
||||||
foreach ($data['permissions'] as $role => $permissionIds) {
|
foreach ($data['permissions'] as $role => $permissionIds) {
|
||||||
|
// Permission khusus admin tidak boleh diberikan ke role lain
|
||||||
|
if ($role !== 'admin') {
|
||||||
|
$permissionIds = array_filter($permissionIds, function ($pid) {
|
||||||
|
$pid = (int) $pid;
|
||||||
|
if (! $pid) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$key = DB::table('permissions')->where('id', $pid)->value('key');
|
||||||
|
|
||||||
|
return ! in_array($key, self::ADMIN_ONLY_PERMISSIONS, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($permissionIds as $permissionId) {
|
foreach ($permissionIds as $permissionId) {
|
||||||
|
$permissionId = (int) $permissionId;
|
||||||
|
if (! $permissionId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
DB::table('role_permissions')->insert([
|
DB::table('role_permissions')->insert([
|
||||||
'role' => $role,
|
'role' => $role,
|
||||||
'permission_id' => (int) $permissionId,
|
'permission_id' => $permissionId,
|
||||||
'created_at' => $now,
|
'created_at' => $now,
|
||||||
'updated_at' => $now,
|
'updated_at' => $now,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -96,11 +96,18 @@
|
|||||||
<tr class="hover:bg-gradient-to-r hover:from-blue-50/30 hover:to-white transition-all duration-100 group">
|
<tr class="hover:bg-gradient-to-r hover:from-blue-50/30 hover:to-white transition-all duration-100 group">
|
||||||
<td class="px-5 py-3.5">
|
<td class="px-5 py-3.5">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<div class="w-8 h-8 rounded-lg bg-gray-100 flex items-center justify-center group-hover:bg-blue-100 transition-all duration-200">
|
<div class="w-8 h-8 rounded-lg {{ in_array($perm->key, $adminOnlyKeys) ? 'bg-amber-50' : 'bg-gray-100' }} flex items-center justify-center group-hover:bg-blue-100 transition-all duration-200">
|
||||||
<i class="fa-solid fa-check text-xs text-gray-400 group-hover:text-blue-500 transition-colors"></i>
|
<i class="fa-solid {{ in_array($perm->key, $adminOnlyKeys) ? 'fa-crown text-amber-500' : 'fa-check text-xs text-gray-400 group-hover:text-blue-500' }} transition-colors"></i>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="text-sm font-semibold text-gray-800">{{ $perm->label }}</div>
|
<div class="text-sm font-semibold text-gray-800 flex items-center gap-2">
|
||||||
|
{{ $perm->label }}
|
||||||
|
@if (in_array($perm->key, $adminOnlyKeys))
|
||||||
|
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-bold bg-amber-50 text-amber-700 border border-amber-200">
|
||||||
|
<i class="fa-solid fa-lock text-[9px]"></i> Khusus Admin
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
<div class="text-[10px] text-gray-400 font-mono mt-0.5">{{ $perm->key }}</div>
|
<div class="text-[10px] text-gray-400 font-mono mt-0.5">{{ $perm->key }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -109,11 +116,12 @@
|
|||||||
@php
|
@php
|
||||||
$checked = in_array($perm->id, $rolePermissions[$role] ?? []);
|
$checked = in_array($perm->id, $rolePermissions[$role] ?? []);
|
||||||
$disabled = ($role === 'admin');
|
$disabled = ($role === 'admin');
|
||||||
|
$adminOnly = in_array($perm->key, $adminOnlyKeys);
|
||||||
@endphp
|
@endphp
|
||||||
<td class="text-center px-3 py-3.5">
|
<td class="text-center px-3 py-3.5">
|
||||||
@if($disabled)
|
@if($disabled || $adminOnly)
|
||||||
<div class="inline-flex items-center justify-center w-9 h-9 rounded-lg bg-blue-50 border border-blue-200">
|
<div class="inline-flex items-center justify-center w-9 h-9 rounded-lg {{ $adminOnly ? 'bg-amber-50 border border-amber-200' : 'bg-blue-50 border border-blue-200' }}">
|
||||||
<i class="fa-solid fa-lock text-xs text-blue-400"></i>
|
<i class="fa-solid {{ $adminOnly ? 'fa-crown' : 'fa-lock' }} text-xs {{ $adminOnly ? 'text-amber-500' : 'text-blue-400' }}"></i>
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class PermissionManagementTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
$this->admin = User::factory()->create(['roles' => ['admin']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_permission_khusus_admin_tidak_bisa_diberikan_ke_role_lain(): void
|
||||||
|
{
|
||||||
|
$auditId = DB::table('permissions')->where('key', 'view-point-audit')->value('id');
|
||||||
|
$normalId = DB::table('permissions')->where('key', 'input-violations')->value('id');
|
||||||
|
|
||||||
|
$this->actingAs($this->admin)
|
||||||
|
->post('/settings/permissions', [
|
||||||
|
'permissions' => [
|
||||||
|
'bk' => [$auditId, $normalId],
|
||||||
|
'ketua_tim' => [$auditId],
|
||||||
|
],
|
||||||
|
])
|
||||||
|
->assertRedirect()
|
||||||
|
->assertSessionHas('success');
|
||||||
|
|
||||||
|
// view-point-audit TIDAK boleh jatuh ke role non-admin
|
||||||
|
$this->assertDatabaseMissing('role_permissions', ['role' => 'bk', 'permission_id' => $auditId]);
|
||||||
|
$this->assertDatabaseMissing('role_permissions', ['role' => 'ketua_tim', 'permission_id' => $auditId]);
|
||||||
|
|
||||||
|
// permission normal tetap tersimpan
|
||||||
|
$this->assertDatabaseHas('role_permissions', ['role' => 'bk', 'permission_id' => $normalId]);
|
||||||
|
|
||||||
|
// admin selalu dapat semua
|
||||||
|
$this->assertDatabaseHas('role_permissions', ['role' => 'admin', 'permission_id' => $auditId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_halaman_hak_akses_menampilkan_permission_khusus_admin(): void
|
||||||
|
{
|
||||||
|
$response = $this->actingAs($this->admin)
|
||||||
|
->get('/settings/permissions')
|
||||||
|
->assertOk()
|
||||||
|
->assertSee('Hak Akses Role')
|
||||||
|
->assertSee('view-point-audit')
|
||||||
|
->assertSee('Khusus Admin');
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user