275 lines
9.9 KiB
PHP
275 lines
9.9 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
namespace MintyPHP\Service\User;
|
||
|
|
|
||
|
|
use MintyPHP\Repository\Access\UserRoleRepository;
|
||
|
|
use MintyPHP\Repository\Org\UserDepartmentRepository;
|
||
|
|
use MintyPHP\Repository\Tenant\UserTenantRepository;
|
||
|
|
use MintyPHP\Repository\User\UserWriteRepository;
|
||
|
|
|
||
|
|
class UserAssignmentService
|
||
|
|
{
|
||
|
|
public function __construct(
|
||
|
|
private readonly UserWriteRepository $userWriteRepository,
|
||
|
|
private readonly UserTenantRepository $userTenantRepository,
|
||
|
|
private readonly UserRoleRepository $userRoleRepository,
|
||
|
|
private readonly UserDepartmentRepository $userDepartmentRepository,
|
||
|
|
private readonly UserDirectoryGateway $directoryGateway,
|
||
|
|
private readonly UserPermissionGateway $permissionGateway
|
||
|
|
) {
|
||
|
|
}
|
||
|
|
|
||
|
|
public function buildAssignmentsForUser(int $userId): array
|
||
|
|
{
|
||
|
|
if ($userId <= 0) {
|
||
|
|
return [
|
||
|
|
'tenants' => [],
|
||
|
|
'departments' => [],
|
||
|
|
'roles' => [],
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
$tenantIds = $this->userTenantRepository->listTenantIdsByUserId($userId);
|
||
|
|
$departmentIds = $this->userDepartmentRepository->listDepartmentIdsByUserId($userId);
|
||
|
|
$roleIds = $this->userRoleRepository->listRoleIdsByUserId($userId);
|
||
|
|
|
||
|
|
$tenantsById = [];
|
||
|
|
foreach ($this->directoryGateway->listTenantsByIds($tenantIds) as $tenant) {
|
||
|
|
$tenantId = (int) ($tenant['id'] ?? 0);
|
||
|
|
if ($tenantId <= 0) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
$tenantsById[$tenantId] = [
|
||
|
|
'id' => $tenantId,
|
||
|
|
'uuid' => (string) ($tenant['uuid'] ?? ''),
|
||
|
|
'description' => (string) ($tenant['description'] ?? ''),
|
||
|
|
'status' => (string) ($tenant['status'] ?? ''),
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
$departmentsById = [];
|
||
|
|
foreach ($this->directoryGateway->listDepartmentsByIds($departmentIds, true) as $department) {
|
||
|
|
$departmentId = (int) ($department['id'] ?? 0);
|
||
|
|
if ($departmentId <= 0) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
$departmentTenantId = (int) ($department['tenant_id'] ?? 0);
|
||
|
|
$departmentsById[$departmentId] = [
|
||
|
|
'id' => $departmentId,
|
||
|
|
'uuid' => (string) ($department['uuid'] ?? ''),
|
||
|
|
'description' => (string) ($department['description'] ?? ''),
|
||
|
|
'active' => (bool) ($department['active'] ?? 0),
|
||
|
|
'tenant_id' => $departmentTenantId,
|
||
|
|
'tenant_uuid' => (string) (($tenantsById[$departmentTenantId]['uuid'] ?? '')),
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
$rolesById = [];
|
||
|
|
foreach ($this->directoryGateway->listRolesByIds($roleIds) as $role) {
|
||
|
|
$roleId = (int) ($role['id'] ?? 0);
|
||
|
|
if ($roleId <= 0) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
$rolesById[$roleId] = [
|
||
|
|
'id' => $roleId,
|
||
|
|
'uuid' => (string) ($role['uuid'] ?? ''),
|
||
|
|
'description' => (string) ($role['description'] ?? ''),
|
||
|
|
'active' => (bool) ($role['active'] ?? 0),
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
$tenants = [];
|
||
|
|
foreach ($tenantIds as $tenantId) {
|
||
|
|
if (isset($tenantsById[$tenantId])) {
|
||
|
|
$tenants[] = $tenantsById[$tenantId];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
$departments = [];
|
||
|
|
foreach ($departmentIds as $departmentId) {
|
||
|
|
if (isset($departmentsById[$departmentId])) {
|
||
|
|
$departments[] = $departmentsById[$departmentId];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
$roles = [];
|
||
|
|
foreach ($roleIds as $roleId) {
|
||
|
|
if (isset($rolesById[$roleId])) {
|
||
|
|
$roles[] = $rolesById[$roleId];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return [
|
||
|
|
'tenants' => $tenants,
|
||
|
|
'departments' => $departments,
|
||
|
|
'roles' => $roles,
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
public function findTenantSummaryInAssignments(array $assignments, int $tenantId): ?array
|
||
|
|
{
|
||
|
|
if ($tenantId <= 0) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
foreach (($assignments['tenants'] ?? []) as $tenant) {
|
||
|
|
$currentTenantId = (int) ($tenant['id'] ?? 0);
|
||
|
|
if ($currentTenantId !== $tenantId) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
return [
|
||
|
|
'uuid' => (string) ($tenant['uuid'] ?? ''),
|
||
|
|
'description' => (string) ($tenant['description'] ?? ''),
|
||
|
|
'status' => (string) ($tenant['status'] ?? ''),
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
public function mapAssignmentsToPublic(array $assignments): array
|
||
|
|
{
|
||
|
|
return [
|
||
|
|
'tenants' => array_values(array_map(
|
||
|
|
static fn (array $tenant): array => [
|
||
|
|
'uuid' => (string) ($tenant['uuid'] ?? ''),
|
||
|
|
'description' => (string) ($tenant['description'] ?? ''),
|
||
|
|
'status' => (string) ($tenant['status'] ?? ''),
|
||
|
|
],
|
||
|
|
$assignments['tenants'] ?? []
|
||
|
|
)),
|
||
|
|
'departments' => array_values(array_map(
|
||
|
|
static fn (array $department): array => [
|
||
|
|
'uuid' => (string) ($department['uuid'] ?? ''),
|
||
|
|
'description' => (string) ($department['description'] ?? ''),
|
||
|
|
'active' => (bool) ($department['active'] ?? 0),
|
||
|
|
'tenant_uuid' => (string) ($department['tenant_uuid'] ?? ''),
|
||
|
|
],
|
||
|
|
$assignments['departments'] ?? []
|
||
|
|
)),
|
||
|
|
'roles' => array_values(array_map(
|
||
|
|
static fn (array $role): array => [
|
||
|
|
'uuid' => (string) ($role['uuid'] ?? ''),
|
||
|
|
'description' => (string) ($role['description'] ?? ''),
|
||
|
|
'active' => (bool) ($role['active'] ?? 0),
|
||
|
|
],
|
||
|
|
$assignments['roles'] ?? []
|
||
|
|
)),
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
public function syncTenants(int $userId, array $tenantIds, bool $bumpAuthz = true): bool
|
||
|
|
{
|
||
|
|
$ids = $this->normalizeTenantIds($tenantIds);
|
||
|
|
$result = $this->userTenantRepository->replaceForUser($userId, $ids);
|
||
|
|
if ($result && $bumpAuthz) {
|
||
|
|
$this->bumpAuthzVersion($userId);
|
||
|
|
}
|
||
|
|
return $result;
|
||
|
|
}
|
||
|
|
|
||
|
|
public function syncRoles(int $userId, array $roleIds, bool $bumpAuthz = true): bool
|
||
|
|
{
|
||
|
|
$ids = array_values(array_unique(array_map('intval', $roleIds)));
|
||
|
|
$ids = array_values(array_filter($ids, static fn ($id) => $id > 0));
|
||
|
|
$validIds = $this->directoryGateway->listActiveRoleIds();
|
||
|
|
if ($validIds) {
|
||
|
|
$validMap = array_fill_keys($validIds, true);
|
||
|
|
$ids = array_values(array_filter($ids, static fn ($id) => isset($validMap[$id])));
|
||
|
|
}
|
||
|
|
$result = $this->userRoleRepository->replaceForUser($userId, $ids);
|
||
|
|
$this->permissionGateway->clearUserCache($userId);
|
||
|
|
if ($result && $bumpAuthz) {
|
||
|
|
$this->bumpAuthzVersion($userId);
|
||
|
|
}
|
||
|
|
return $result;
|
||
|
|
}
|
||
|
|
|
||
|
|
public function syncDepartments(int $userId, array $departmentIds, bool $bumpAuthz = true): bool
|
||
|
|
{
|
||
|
|
$ids = array_values(array_unique(array_map('intval', $departmentIds)));
|
||
|
|
$ids = array_values(array_filter($ids, static fn ($id) => $id > 0));
|
||
|
|
// Important: use the user's assigned tenants directly (no permission bypass),
|
||
|
|
// otherwise privileged users (e.g. admins) would incorrectly allow departments
|
||
|
|
// from tenants that were just removed from their assignments.
|
||
|
|
$userTenantIds = array_values(array_unique(array_map(
|
||
|
|
'intval',
|
||
|
|
$this->userTenantRepository->listTenantIdsByUserId($userId)
|
||
|
|
)));
|
||
|
|
$userTenantIds = array_values(array_filter($userTenantIds, static fn ($id) => $id > 0));
|
||
|
|
if (!$userTenantIds) {
|
||
|
|
$ids = [];
|
||
|
|
} else {
|
||
|
|
$allowedIds = $this->directoryGateway->listActiveDepartmentIdsByTenantIds($userTenantIds);
|
||
|
|
if ($allowedIds) {
|
||
|
|
$allowedMap = array_fill_keys($allowedIds, true);
|
||
|
|
$ids = array_values(array_filter($ids, static fn ($id) => isset($allowedMap[$id])));
|
||
|
|
} else {
|
||
|
|
$ids = [];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
$result = $this->userDepartmentRepository->replaceForUser($userId, $ids);
|
||
|
|
if ($result && $bumpAuthz) {
|
||
|
|
$this->bumpAuthzVersion($userId);
|
||
|
|
}
|
||
|
|
return $result;
|
||
|
|
}
|
||
|
|
|
||
|
|
public function bumpAuthzVersion(int $userId): void
|
||
|
|
{
|
||
|
|
if ($userId <= 0) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
$this->userWriteRepository->bumpAuthzVersion($userId);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function normalizeIdInput($value): array
|
||
|
|
{
|
||
|
|
$raw = is_array($value) ? $value : [$value];
|
||
|
|
$flat = [];
|
||
|
|
|
||
|
|
$collect = static function ($item) use (&$collect, &$flat): void {
|
||
|
|
if (is_array($item)) {
|
||
|
|
foreach ($item as $nested) {
|
||
|
|
$collect($nested);
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
$text = trim((string) $item);
|
||
|
|
if ($text === '') {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (str_contains($text, ',')) {
|
||
|
|
foreach (explode(',', $text) as $part) {
|
||
|
|
$part = trim($part);
|
||
|
|
if ($part !== '') {
|
||
|
|
$flat[] = $part;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
$flat[] = $text;
|
||
|
|
};
|
||
|
|
|
||
|
|
foreach ($raw as $item) {
|
||
|
|
$collect($item);
|
||
|
|
}
|
||
|
|
|
||
|
|
$ids = array_values(array_unique(array_map('intval', $flat)));
|
||
|
|
return array_values(array_filter($ids, static fn ($id) => $id > 0));
|
||
|
|
}
|
||
|
|
|
||
|
|
public function normalizeTenantIds(array $tenantIds): array
|
||
|
|
{
|
||
|
|
$ids = array_values(array_unique(array_map('intval', $tenantIds)));
|
||
|
|
$ids = array_filter($ids, static fn ($id) => $id > 0);
|
||
|
|
$validIds = $this->directoryGateway->listTenantIds();
|
||
|
|
if ($validIds) {
|
||
|
|
$validMap = array_fill_keys($validIds, true);
|
||
|
|
$ids = array_values(array_filter($ids, static fn ($id) => isset($validMap[$id])));
|
||
|
|
}
|
||
|
|
return $ids;
|
||
|
|
}
|
||
|
|
}
|