= a37b6a91be Feat: Show Immutable Id field if user has a SAML App
- added a ImmuatableId field in manage roles
- Generate ImmutableId using user Id if not available
2026-05-27 10:43:44 +00:00

142 lines
4.1 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 and update their SSO settings.
*/
public function assignRoles(int $userId, array $roleIds, ?string $immutableId = null): void
{
if ($userId <= 0) {
throw new InvalidArgumentException('Invalid id');
}
DB::transaction(function () use ($userId, $roleIds, $immutableId): void {
$user = User::query()->findOrFail($userId);
$user->immutable_id = $immutableId ?: null;
$user->save();
$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();
});
}
}