agent foundation
This commit is contained in:
@@ -2,11 +2,12 @@
|
||||
|
||||
namespace MintyPHP\Service\Import;
|
||||
|
||||
use MintyPHP\Http\SessionStoreInterface;
|
||||
use MintyPHP\Service\Access\PermissionGateway;
|
||||
use MintyPHP\Service\Access\PermissionService;
|
||||
use MintyPHP\Service\Audit\ImportAuditService;
|
||||
use MintyPHP\Service\Import\Profile\ImportProfileInterface;
|
||||
use MintyPHP\Service\Settings\SettingGateway;
|
||||
use MintyPHP\Service\Settings\SettingsDefaultsGateway;
|
||||
use MintyPHP\Service\User\UserScopeGateway;
|
||||
|
||||
class ImportService
|
||||
@@ -28,8 +29,9 @@ class ImportService
|
||||
private readonly ImportStateStoreService $importStateStoreService,
|
||||
private readonly array $profiles,
|
||||
private readonly PermissionGateway $permissionGateway,
|
||||
private readonly SettingGateway $settingGateway,
|
||||
private readonly UserScopeGateway $userScopeGateway
|
||||
private readonly SettingsDefaultsGateway $settingsDefaultsGateway,
|
||||
private readonly UserScopeGateway $userScopeGateway,
|
||||
private readonly SessionStoreInterface $sessionStore
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -139,6 +141,7 @@ class ImportService
|
||||
if (!$state) {
|
||||
return ['ok' => false, 'code' => 'invalid_file_type'];
|
||||
}
|
||||
// Prevent cross-user token reuse — the state token is session-scoped but user_id is verified explicitly.
|
||||
if ((int) ($state['user_id'] ?? 0) !== $currentUserId) {
|
||||
return ['ok' => false, 'code' => 'invalid_file_type'];
|
||||
}
|
||||
@@ -154,6 +157,8 @@ class ImportService
|
||||
}
|
||||
|
||||
$mappedTargets = $mappingResult['mapped_targets'] ?? [];
|
||||
// Tenant/role/department column mapping requires a separate permission — a user who can import
|
||||
// users generally may not be allowed to also assign them to tenants.
|
||||
if (
|
||||
$profile->requiresAssignmentPermission()
|
||||
&& $this->usesAssignmentTargets($mappedTargets)
|
||||
@@ -203,7 +208,7 @@ class ImportService
|
||||
$mappingResult['mapped_targets'] ?? [],
|
||||
isset($state['source_filename']) ? (string) $state['source_filename'] : null,
|
||||
$currentUserId,
|
||||
isset($_SESSION['current_tenant']['id']) ? (int) $_SESSION['current_tenant']['id'] : null
|
||||
$this->currentTenantId()
|
||||
);
|
||||
|
||||
$result = ['ok' => false, 'code' => 'unexpected_error', 'processed' => 0, 'created' => 0, 'skipped' => 0, 'failed' => 1];
|
||||
@@ -301,6 +306,7 @@ class ImportService
|
||||
|
||||
$normalized = $validation['normalized'];
|
||||
$identifier = $profile->rowIdentifier($normalized);
|
||||
// Detect duplicates within the file itself (e.g. same email appearing twice).
|
||||
$duplicateKey = $profile->inFileDuplicateKey($normalized);
|
||||
if ($duplicateKey !== null && $duplicateKey !== '') {
|
||||
if (isset($seenRowKeys[$duplicateKey])) {
|
||||
@@ -496,14 +502,28 @@ class ImportService
|
||||
return [
|
||||
'current_user_id' => $currentUserId,
|
||||
'is_global_admin' => $isGlobalAdmin,
|
||||
// Empty allowed_tenant_ids signals "no restriction" to profile validators for global admins.
|
||||
'allowed_tenant_ids' => $isGlobalAdmin ? [] : $this->userScopeGateway->getUserTenantIds($currentUserId),
|
||||
'default_tenant_id' => $this->settingGateway->getDefaultTenantId(),
|
||||
'default_role_id' => $this->settingGateway->getDefaultRoleId(),
|
||||
'default_department_id' => $this->settingGateway->getDefaultDepartmentId(),
|
||||
'default_tenant_id' => $this->settingsDefaultsGateway->getDefaultTenantId(),
|
||||
'default_role_id' => $this->settingsDefaultsGateway->getDefaultRoleId(),
|
||||
'default_department_id' => $this->settingsDefaultsGateway->getDefaultDepartmentId(),
|
||||
'can_import_assignments' => $this->canImportAssignments($currentUserId),
|
||||
];
|
||||
}
|
||||
|
||||
private function currentTenantId(): ?int
|
||||
{
|
||||
$tenant = $this->sessionStore->get('current_tenant', []);
|
||||
if (!is_array($tenant)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$tenantId = (int) ($tenant['id'] ?? 0);
|
||||
return $tenantId > 0 ? $tenantId : null;
|
||||
}
|
||||
|
||||
// Fallback to uniqid if random_bytes is unavailable — not cryptographically ideal but only
|
||||
// used for session-scoped state tokens, not for security-critical values.
|
||||
private function newToken(): string
|
||||
{
|
||||
try {
|
||||
@@ -530,6 +550,7 @@ class ImportService
|
||||
/**
|
||||
* @param array<int, array{row_number:int,identifier:string,code:string,message:string}> $errors
|
||||
*/
|
||||
// Cap the error list to avoid oversized responses — error_counts still reflects the full count.
|
||||
private function pushError(array &$errors, int $lineNumber, string $identifier, string $code, ?string $message): void
|
||||
{
|
||||
if (count($errors) >= self::MAX_ERROR_ROWS) {
|
||||
|
||||
Reference in New Issue
Block a user