252 lines
8.4 KiB
PHP
252 lines
8.4 KiB
PHP
<?php
|
|
|
|
namespace KTXC\Controllers;
|
|
|
|
use KTXC\Http\Response\JsonResponse;
|
|
use KTXC\Service\UserAccountsService;
|
|
use KTXC\SessionIdentity;
|
|
use KTXC\SessionTenant;
|
|
use KTXF\Controller\ControllerAbstract;
|
|
use KTXF\Routing\Attributes\AuthenticatedRoute;
|
|
use Psr\Log\LoggerInterface;
|
|
|
|
/**
|
|
* User Accounts Controller
|
|
* Core administrative user management operations
|
|
*/
|
|
class UserAccountsController extends ControllerAbstract
|
|
{
|
|
public function __construct(
|
|
private readonly SessionTenant $tenantIdentity,
|
|
private readonly SessionIdentity $userIdentity,
|
|
private readonly UserAccountsService $userService,
|
|
private readonly LoggerInterface $logger
|
|
) {}
|
|
|
|
/**
|
|
* Main versioned endpoint for user management
|
|
*/
|
|
#[AuthenticatedRoute('/user/accounts/v1', name: 'user.accounts.v1', methods: ['POST'])]
|
|
public function index(int $version, string $transaction, string $operation, array $data = []): JsonResponse
|
|
{
|
|
try {
|
|
// Check admin permission
|
|
if (!$this->userIdentity->hasPermission('user.admin')) {
|
|
return new JsonResponse([
|
|
'status' => 'error',
|
|
'data' => ['code' => 403, 'message' => 'Insufficient permissions']
|
|
], JsonResponse::HTTP_FORBIDDEN);
|
|
}
|
|
|
|
$result = $this->process($operation, $data);
|
|
|
|
return new JsonResponse([
|
|
'version' => $version,
|
|
'transaction' => $transaction,
|
|
'operation' => $operation,
|
|
'status' => 'success',
|
|
'data' => $result,
|
|
], JsonResponse::HTTP_OK);
|
|
|
|
} catch (\InvalidArgumentException $e) {
|
|
return new JsonResponse([
|
|
'version' => $version,
|
|
'transaction' => $transaction,
|
|
'operation' => $operation,
|
|
'status' => 'error',
|
|
'data' => ['code' => 400, 'message' => $e->getMessage()]
|
|
], JsonResponse::HTTP_BAD_REQUEST);
|
|
|
|
} catch (\Throwable $e) {
|
|
$this->logger->error('User manager operation failed', [
|
|
'operation' => $operation,
|
|
'error' => $e->getMessage()
|
|
]);
|
|
|
|
return new JsonResponse([
|
|
'version' => $version,
|
|
'transaction' => $transaction,
|
|
'operation' => $operation,
|
|
'status' => 'error',
|
|
'data' => ['code' => $e->getCode(), 'message' => $e->getMessage()]
|
|
], JsonResponse::HTTP_INTERNAL_SERVER_ERROR);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process operation
|
|
*/
|
|
private function process(string $operation, array $data): mixed
|
|
{
|
|
return match ($operation) {
|
|
'user.list' => $this->userList($data),
|
|
'user.fetch' => $this->userFetch($data),
|
|
'user.create' => $this->userCreate($data),
|
|
'user.update' => $this->userUpdate($data),
|
|
'user.delete' => $this->userDelete($data),
|
|
'user.provider.unlink' => $this->userProviderUnlink($data),
|
|
default => throw new \InvalidArgumentException("Invalid operation: {$operation}"),
|
|
};
|
|
}
|
|
|
|
// =========================================================================
|
|
// User Operations
|
|
// =========================================================================
|
|
|
|
/**
|
|
* List all users for tenant
|
|
*/
|
|
private function userList(array $data): array
|
|
{
|
|
return $this->userService->listUsers($data);
|
|
}
|
|
|
|
/**
|
|
* Fetch single user by UID
|
|
*/
|
|
private function userFetch(array $data): array
|
|
{
|
|
$uid = $data['uid'] ?? throw new \InvalidArgumentException('User ID required');
|
|
|
|
$user = $this->userService->fetchByIdentifier($uid);
|
|
if (!$user) {
|
|
throw new \InvalidArgumentException('User not found');
|
|
}
|
|
|
|
// Get editable fields for profile
|
|
$editableFields = $this->userService->getEditableFields($uid);
|
|
$user['profile_editable'] = $editableFields;
|
|
|
|
return $user;
|
|
}
|
|
|
|
/**
|
|
* Create new user
|
|
*/
|
|
private function userCreate(array $data): array
|
|
{
|
|
if (!$this->userIdentity->hasPermission('user.create')) {
|
|
throw new \InvalidArgumentException('Insufficient permissions to create users');
|
|
}
|
|
|
|
$userData = [
|
|
'identity' => $data['identity'] ?? throw new \InvalidArgumentException('Identity required'),
|
|
'label' => $data['label'] ?? $data['identity'],
|
|
'enabled' => $data['enabled'] ?? true,
|
|
'roles' => $data['roles'] ?? [],
|
|
'profile' => $data['profile'] ?? [],
|
|
'settings' => [],
|
|
'provider' => null,
|
|
'provider_subject' => null,
|
|
'provider_managed_fields' => []
|
|
];
|
|
|
|
$this->logger->info('Creating user', [
|
|
'tenant' => $this->tenantIdentity->identifier(),
|
|
'identity' => $userData['identity'],
|
|
'actor' => $this->userIdentity->identifier()
|
|
]);
|
|
|
|
return $this->userService->createUser($userData);
|
|
}
|
|
|
|
/**
|
|
* Update existing user
|
|
*/
|
|
private function userUpdate(array $data): bool
|
|
{
|
|
if (!$this->userIdentity->hasPermission('user.update')) {
|
|
throw new \InvalidArgumentException('Insufficient permissions to update users');
|
|
}
|
|
|
|
$uid = $data['uid'] ?? throw new \InvalidArgumentException('User ID required');
|
|
|
|
// Build updates (exclude sensitive fields)
|
|
$updates = [];
|
|
$allowedFields = ['label', 'enabled', 'roles', 'profile'];
|
|
|
|
foreach ($allowedFields as $field) {
|
|
if (isset($data[$field])) {
|
|
$updates[$field] = $data[$field];
|
|
}
|
|
}
|
|
|
|
if (empty($updates)) {
|
|
throw new \InvalidArgumentException('No valid fields to update');
|
|
}
|
|
|
|
// Special handling for profile updates (respect managed fields)
|
|
if (isset($updates['profile'])) {
|
|
$user = $this->userService->fetchByIdentifier($uid);
|
|
$managedFields = $user['provider_managed_fields'] ?? [];
|
|
|
|
foreach ($managedFields as $field) {
|
|
unset($updates['profile'][$field]);
|
|
}
|
|
}
|
|
|
|
$this->logger->info('Updating user', [
|
|
'tenant' => $this->tenantIdentity->identifier(),
|
|
'uid' => $uid,
|
|
'actor' => $this->userIdentity->identifier()
|
|
]);
|
|
|
|
return $this->userService->updateUser($uid, $updates);
|
|
}
|
|
|
|
/**
|
|
* Delete user
|
|
*/
|
|
private function userDelete(array $data): bool
|
|
{
|
|
if (!$this->userIdentity->hasPermission('user.delete')) {
|
|
throw new \InvalidArgumentException('Insufficient permissions to delete users');
|
|
}
|
|
|
|
$uid = $data['uid'] ?? throw new \InvalidArgumentException('User ID required');
|
|
|
|
// Prevent self-deletion
|
|
if ($uid === $this->userIdentity->identifier()) {
|
|
throw new \InvalidArgumentException('Cannot delete your own account');
|
|
}
|
|
|
|
$this->logger->info('Deleting user', [
|
|
'tenant' => $this->tenantIdentity->identifier(),
|
|
'uid' => $uid,
|
|
'actor' => $this->userIdentity->identifier()
|
|
]);
|
|
|
|
return $this->userService->deleteUser($uid);
|
|
}
|
|
|
|
// =========================================================================
|
|
// Security Operations
|
|
// =========================================================================
|
|
|
|
/**
|
|
* Unlink external provider
|
|
*/
|
|
private function userProviderUnlink(array $data): bool
|
|
{
|
|
if (!$this->userIdentity->hasPermission('user.admin')) {
|
|
throw new \InvalidArgumentException('Insufficient permissions');
|
|
}
|
|
|
|
$uid = $data['uid'] ?? throw new \InvalidArgumentException('User ID required');
|
|
|
|
$updates = [
|
|
'provider' => null,
|
|
'provider_subject' => null,
|
|
'provider_managed_fields' => []
|
|
];
|
|
|
|
$this->logger->info('Unlinking provider', [
|
|
'tenant' => $this->tenantIdentity->identifier(),
|
|
'uid' => $uid,
|
|
'actor' => $this->userIdentity->identifier()
|
|
]);
|
|
|
|
return $this->userService->updateUser($uid, $updates);
|
|
}
|
|
}
|