- Added `priority`-based access logic to restrict role and user management actions. - Introduced `Permission Manager` UI and routes for permission access control. - Updated `RoleService` and `AppRoleService` to include `visible` scope for priority filtering. - Enhanced seeding to assign priorities to default roles and ensure proper hierarchy. - Updated sidebar, routes, and data structure to handle new permission enums and resource segmentation. - Improved UI components to dynamically restrict visibility and actions based on role priority.
139 lines
4.0 KiB
PHP
139 lines
4.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Data\Permissions\DeactivatedPermissionData;
|
|
use App\Data\User\UserIndexData;
|
|
use App\Models\{RestrictedUserPermission, User};
|
|
use Illuminate\Support\Facades\DB;
|
|
use InvalidArgumentException;
|
|
use NoDiscard;
|
|
use Spatie\LaravelData\{DataCollection, PaginatedDataCollection};
|
|
use Throwable;
|
|
|
|
final class UserService
|
|
{
|
|
/**
|
|
* Returns a Paginated Collection of users with their roles and connected apps DTO
|
|
*
|
|
* @return PaginatedDataCollection<int, UserIndexData>
|
|
*/
|
|
#[NoDiscard]
|
|
public function getAll(): PaginatedDataCollection
|
|
{
|
|
$users = User::query()
|
|
->with([
|
|
'roles:id,name,priority',
|
|
'roles.apps:id,name',
|
|
'roles.permissions:id,name',
|
|
'restrictedPermissions',
|
|
])
|
|
->orderBy('id')
|
|
->paginate();
|
|
|
|
return UserIndexData::collect($users, PaginatedDataCollection::class);
|
|
}
|
|
|
|
/**
|
|
* Get the restricted permission IDs for a user.
|
|
*
|
|
* @return array<int>
|
|
*/
|
|
public function getRestrictedPermissionIds(int $userId): array
|
|
{
|
|
if ($userId <= 0) {
|
|
return [];
|
|
}
|
|
|
|
return RestrictedUserPermission::query()
|
|
->where('user_id', $userId)
|
|
->pluck('permission_id')
|
|
->toArray();
|
|
}
|
|
|
|
/**
|
|
* Assign roles to a user.
|
|
*/
|
|
public function assignRoles(int $userId, array $roleIds): void
|
|
{
|
|
if ($userId <= 0) {
|
|
throw new InvalidArgumentException('Invalid id');
|
|
}
|
|
|
|
DB::transaction(function () use ($userId, $roleIds): void {
|
|
$user = User::query()->findOrFail($userId);
|
|
|
|
$userPriority = auth()->user()?->roles()->min('priority') ?? 999999;
|
|
|
|
// Get user's existing roles that have priority <= $userPriority (which are forbidden to the logged in user)
|
|
$forbiddenRoleIds = $user->roles()
|
|
->where('priority', '<=', $userPriority)
|
|
->pluck('id')
|
|
->toArray();
|
|
|
|
// Merge them with the newly assigned roles so they are preserved
|
|
$mergedRoleIds = array_values(array_unique(array_merge($forbiddenRoleIds, $roleIds)));
|
|
|
|
$user->syncRoles($mergedRoleIds);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Sync restricted permissions for a user.
|
|
*
|
|
* @param DataCollection<int, DeactivatedPermissionData> $deactivatedPermissions
|
|
*
|
|
* @throws Throwable
|
|
*/
|
|
public function syncRestrictedPermissions(int $userId, DataCollection $deactivatedPermissions): void
|
|
{
|
|
if ($userId <= 0) {
|
|
throw new InvalidArgumentException('Invalid user ID');
|
|
}
|
|
|
|
$payload = $deactivatedPermissions->toCollection()->map(fn ($data) => [
|
|
'user_id' => $userId,
|
|
'role_id' => $data->roleId,
|
|
'permission_id' => $data->permissionId,
|
|
])->toArray();
|
|
|
|
DB::transaction(function () use ($userId, $payload): void {
|
|
|
|
RestrictedUserPermission::query()
|
|
->where('user_id', $userId)
|
|
->delete();
|
|
|
|
if (! empty($payload)) {
|
|
RestrictedUserPermission::query()->insert($payload);
|
|
}
|
|
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @throws InvalidArgumentException when id is invalid.
|
|
* @throws Throwable when an error occurs during the deletion.
|
|
*/
|
|
public function delete(int $id): void
|
|
{
|
|
if ($id <= 0) {
|
|
throw new InvalidArgumentException('Invalid id');
|
|
}
|
|
|
|
DB::transaction(function () use ($id): void {
|
|
$user = User::query()->findOrFail($id);
|
|
|
|
$userPriority = auth()->user()?->roles()->min('priority') ?? 999999;
|
|
$targetUserMinPriority = $user->roles()->min('priority') ?? 999999;
|
|
|
|
if ($targetUserMinPriority <= $userPriority) {
|
|
abort(403, 'Unauthorized to delete this user due to role priority hierarchy.');
|
|
}
|
|
|
|
$user->delete();
|
|
});
|
|
}
|
|
}
|