added user and roles controllers and unified naming
This commit is contained in:
@@ -5,7 +5,7 @@ namespace KTXC\Controllers;
|
|||||||
use KTXC\Http\Response\JsonResponse;
|
use KTXC\Http\Response\JsonResponse;
|
||||||
use KTXC\Module\ModuleManager;
|
use KTXC\Module\ModuleManager;
|
||||||
use KTXC\Security\Authorization\PermissionChecker;
|
use KTXC\Security\Authorization\PermissionChecker;
|
||||||
use KTXC\Service\UserService;
|
use KTXC\Service\UserAccountsService;
|
||||||
use KTXC\SessionIdentity;
|
use KTXC\SessionIdentity;
|
||||||
use KTXF\Controller\ControllerAbstract;
|
use KTXF\Controller\ControllerAbstract;
|
||||||
use KTXC\SessionTenant;
|
use KTXC\SessionTenant;
|
||||||
@@ -17,7 +17,7 @@ class InitController extends ControllerAbstract
|
|||||||
private readonly SessionTenant $tenant,
|
private readonly SessionTenant $tenant,
|
||||||
private readonly SessionIdentity $userIdentity,
|
private readonly SessionIdentity $userIdentity,
|
||||||
private readonly ModuleManager $moduleManager,
|
private readonly ModuleManager $moduleManager,
|
||||||
private readonly UserService $userService,
|
private readonly UserAccountsService $userService,
|
||||||
private readonly PermissionChecker $permissionChecker,
|
private readonly PermissionChecker $permissionChecker,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
|||||||
251
core/lib/Controllers/UserAccountsController.php
Normal file
251
core/lib/Controllers/UserAccountsController.php
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
<?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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
namespace KTXC\Controllers;
|
namespace KTXC\Controllers;
|
||||||
|
|
||||||
use KTXC\Http\Response\JsonResponse;
|
use KTXC\Http\Response\JsonResponse;
|
||||||
use KTXC\Service\UserService;
|
use KTXC\Service\UserAccountsService;
|
||||||
use KTXC\SessionIdentity;
|
use KTXC\SessionIdentity;
|
||||||
use KTXC\SessionTenant;
|
use KTXC\SessionTenant;
|
||||||
use KTXF\Controller\ControllerAbstract;
|
use KTXF\Controller\ControllerAbstract;
|
||||||
@@ -14,7 +14,7 @@ class UserProfileController extends ControllerAbstract
|
|||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly SessionTenant $tenantIdentity,
|
private readonly SessionTenant $tenantIdentity,
|
||||||
private readonly SessionIdentity $userIdentity,
|
private readonly SessionIdentity $userIdentity,
|
||||||
private readonly UserService $userService
|
private readonly UserAccountsService $userService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
201
core/lib/Controllers/UserRolesController.php
Normal file
201
core/lib/Controllers/UserRolesController.php
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace KTXC\Controllers;
|
||||||
|
|
||||||
|
use KTXC\Http\Response\JsonResponse;
|
||||||
|
use KTXC\SessionIdentity;
|
||||||
|
use KTXC\SessionTenant;
|
||||||
|
use KTXC\Service\UserRolesService;
|
||||||
|
use KTXF\Controller\ControllerAbstract;
|
||||||
|
use KTXF\Routing\Attributes\AuthenticatedRoute;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User Roles Controller
|
||||||
|
* Core administrative role management operations
|
||||||
|
*/
|
||||||
|
class UserRolesController extends ControllerAbstract
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly SessionTenant $tenantIdentity,
|
||||||
|
private readonly SessionIdentity $userIdentity,
|
||||||
|
private readonly UserRolesService $roleService,
|
||||||
|
private readonly LoggerInterface $logger
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main versioned endpoint for role management
|
||||||
|
*/
|
||||||
|
#[AuthenticatedRoute('/user/roles/v1', name: 'user.roles.v1', methods: ['POST'])]
|
||||||
|
public function index(int $version, string $transaction, string $operation, array $data = []): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Check role admin permission
|
||||||
|
if (!$this->userIdentity->hasPermission('role.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) {
|
||||||
|
$this->logger->error('Role manager validation error', [
|
||||||
|
'operation' => $operation,
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
'trace' => $e->getTraceAsString()
|
||||||
|
]);
|
||||||
|
|
||||||
|
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('Role manager operation failed', [
|
||||||
|
'operation' => $operation,
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
'trace' => $e->getTraceAsString()
|
||||||
|
]);
|
||||||
|
|
||||||
|
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) {
|
||||||
|
'role.list' => $this->roleList($data),
|
||||||
|
'role.fetch' => $this->roleFetch($data),
|
||||||
|
'role.create' => $this->roleCreate($data),
|
||||||
|
'role.update' => $this->roleUpdate($data),
|
||||||
|
'role.delete' => $this->roleDelete($data),
|
||||||
|
'permissions.list' => $this->permissionsList($data),
|
||||||
|
default => throw new \InvalidArgumentException("Invalid operation: {$operation}"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Role Operations
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all roles
|
||||||
|
*/
|
||||||
|
private function roleList(array $data): array
|
||||||
|
{
|
||||||
|
$roles = $this->roleService->listRoles();
|
||||||
|
|
||||||
|
// Add user count to each role
|
||||||
|
foreach ($roles as &$role) {
|
||||||
|
$role['user_count'] = $this->roleService->getRoleUserCount($role['rid']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch single role
|
||||||
|
*/
|
||||||
|
private function roleFetch(array $data): array
|
||||||
|
{
|
||||||
|
$rid = $data['rid'] ?? throw new \InvalidArgumentException('Role ID required');
|
||||||
|
|
||||||
|
$role = $this->roleService->getRole($rid);
|
||||||
|
if (!$role) {
|
||||||
|
throw new \InvalidArgumentException('Role not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
$role['user_count'] = $this->roleService->getRoleUserCount($rid);
|
||||||
|
|
||||||
|
return $role;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create new role
|
||||||
|
*/
|
||||||
|
private function roleCreate(array $data): array
|
||||||
|
{
|
||||||
|
if (!$this->userIdentity->hasPermission('role.manage')) {
|
||||||
|
throw new \InvalidArgumentException('Insufficient permissions to create roles');
|
||||||
|
}
|
||||||
|
|
||||||
|
$roleData = [
|
||||||
|
'label' => $data['label'] ?? throw new \InvalidArgumentException('Role label required'),
|
||||||
|
'description' => $data['description'] ?? '',
|
||||||
|
'permissions' => $data['permissions'] ?? []
|
||||||
|
];
|
||||||
|
|
||||||
|
return $this->roleService->createRole($roleData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update existing role
|
||||||
|
*/
|
||||||
|
private function roleUpdate(array $data): bool
|
||||||
|
{
|
||||||
|
if (!$this->userIdentity->hasPermission('role.manage')) {
|
||||||
|
throw new \InvalidArgumentException('Insufficient permissions to update roles');
|
||||||
|
}
|
||||||
|
|
||||||
|
$rid = $data['rid'] ?? throw new \InvalidArgumentException('Role ID required');
|
||||||
|
|
||||||
|
$updates = [];
|
||||||
|
$allowedFields = ['label', 'description', 'permissions'];
|
||||||
|
|
||||||
|
foreach ($allowedFields as $field) {
|
||||||
|
if (isset($data[$field])) {
|
||||||
|
$updates[$field] = $data[$field];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($updates)) {
|
||||||
|
throw new \InvalidArgumentException('No valid fields to update');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->roleService->updateRole($rid, $updates);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete role
|
||||||
|
*/
|
||||||
|
private function roleDelete(array $data): bool
|
||||||
|
{
|
||||||
|
if (!$this->userIdentity->hasPermission('role.manage')) {
|
||||||
|
throw new \InvalidArgumentException('Insufficient permissions to delete roles');
|
||||||
|
}
|
||||||
|
|
||||||
|
$rid = $data['rid'] ?? throw new \InvalidArgumentException('Role ID required');
|
||||||
|
|
||||||
|
return $this->roleService->deleteRole($rid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get available permissions
|
||||||
|
*/
|
||||||
|
private function permissionsList(array $data): array
|
||||||
|
{
|
||||||
|
return $this->roleService->availablePermissions();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
namespace KTXC\Controllers;
|
namespace KTXC\Controllers;
|
||||||
|
|
||||||
use KTXC\Http\Response\JsonResponse;
|
use KTXC\Http\Response\JsonResponse;
|
||||||
use KTXC\Service\UserService;
|
use KTXC\Service\UserAccountsService;
|
||||||
use KTXC\SessionIdentity;
|
use KTXC\SessionIdentity;
|
||||||
use KTXC\SessionTenant;
|
use KTXC\SessionTenant;
|
||||||
use KTXF\Controller\ControllerAbstract;
|
use KTXF\Controller\ControllerAbstract;
|
||||||
@@ -14,7 +14,7 @@ class UserSettingsController extends ControllerAbstract
|
|||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly SessionTenant $tenantIdentity,
|
private readonly SessionTenant $tenantIdentity,
|
||||||
private readonly SessionIdentity $userIdentity,
|
private readonly SessionIdentity $userIdentity,
|
||||||
private readonly UserService $userService
|
private readonly UserAccountsService $userService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use KTXC\Resource\ProviderManager;
|
|||||||
use KTXC\Security\Authentication\AuthenticationRequest;
|
use KTXC\Security\Authentication\AuthenticationRequest;
|
||||||
use KTXC\Security\Authentication\AuthenticationResponse;
|
use KTXC\Security\Authentication\AuthenticationResponse;
|
||||||
use KTXC\Service\TokenService;
|
use KTXC\Service\TokenService;
|
||||||
use KTXC\Service\UserService;
|
use KTXC\Service\UserAccountsService;
|
||||||
use KTXC\SessionTenant;
|
use KTXC\SessionTenant;
|
||||||
use KTXF\Cache\CacheScope;
|
use KTXF\Cache\CacheScope;
|
||||||
use KTXF\Cache\EphemeralCacheInterface;
|
use KTXF\Cache\EphemeralCacheInterface;
|
||||||
@@ -30,7 +30,7 @@ class AuthenticationManager
|
|||||||
private readonly EphemeralCacheInterface $cache,
|
private readonly EphemeralCacheInterface $cache,
|
||||||
private readonly ProviderManager $providerManager,
|
private readonly ProviderManager $providerManager,
|
||||||
private readonly TokenService $tokenService,
|
private readonly TokenService $tokenService,
|
||||||
private readonly UserService $userService,
|
private readonly UserAccountsService $userService,
|
||||||
) {
|
) {
|
||||||
$this->securityCode = $this->tenant->configuration()->security()->code();
|
$this->securityCode = $this->tenant->configuration()->security()->code();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class SecurityService
|
|||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly TokenService $tokenService,
|
private readonly TokenService $tokenService,
|
||||||
private readonly UserService $userService,
|
private readonly UserAccountsService $userService,
|
||||||
private readonly SessionTenant $sessionTenant
|
private readonly SessionTenant $sessionTenant
|
||||||
) {
|
) {
|
||||||
$this->securityCode = $this->sessionTenant->configuration()->security()->code();
|
$this->securityCode = $this->sessionTenant->configuration()->security()->code();
|
||||||
|
|||||||
@@ -5,15 +5,15 @@ namespace KTXC\Service;
|
|||||||
use KTXC\Models\Identity\User;
|
use KTXC\Models\Identity\User;
|
||||||
use KTXC\SessionIdentity;
|
use KTXC\SessionIdentity;
|
||||||
use KTXC\SessionTenant;
|
use KTXC\SessionTenant;
|
||||||
use KTXC\Stores\UserStore;
|
use KTXC\Stores\UserAccountsStore;
|
||||||
|
|
||||||
class UserService
|
class UserAccountsService
|
||||||
{
|
{
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly SessionTenant $tenantIdentity,
|
private readonly SessionTenant $tenantIdentity,
|
||||||
private readonly SessionIdentity $userIdentity,
|
private readonly SessionIdentity $userIdentity,
|
||||||
private readonly UserStore $userStore
|
private readonly UserAccountsStore $userStore
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,6 +21,21 @@ class UserService
|
|||||||
// User Operations
|
// User Operations
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all users with optional filters
|
||||||
|
*/
|
||||||
|
public function listUsers(array $filters = []): array
|
||||||
|
{
|
||||||
|
$users = $this->userStore->listUsers($this->tenantIdentity->identifier(), $filters);
|
||||||
|
|
||||||
|
// Remove sensitive data
|
||||||
|
foreach ($users as &$user) {
|
||||||
|
unset($user['settings']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $users;
|
||||||
|
}
|
||||||
|
|
||||||
public function fetchByIdentity(string $identifier): User | null
|
public function fetchByIdentity(string $identifier): User | null
|
||||||
{
|
{
|
||||||
$data = $this->userStore->fetchByIdentity($this->tenantIdentity->identifier(), $identifier);
|
$data = $this->userStore->fetchByIdentity($this->tenantIdentity->identifier(), $identifier);
|
||||||
143
core/lib/Service/UserRolesService.php
Normal file
143
core/lib/Service/UserRolesService.php
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace KTXC\Service;
|
||||||
|
|
||||||
|
use KTXC\SessionTenant;
|
||||||
|
use KTXC\Stores\UserRolesStore;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User Roles Service - Business logic for user role management
|
||||||
|
*/
|
||||||
|
class UserRolesService
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly SessionTenant $tenantIdentity,
|
||||||
|
private readonly UserRolesStore $roleStore,
|
||||||
|
private readonly LoggerInterface $logger
|
||||||
|
) {}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Role Operations
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all roles for current tenant
|
||||||
|
*/
|
||||||
|
public function listRoles(): array
|
||||||
|
{
|
||||||
|
return $this->roleStore->listRoles($this->tenantIdentity->identifier());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get role by ID
|
||||||
|
*/
|
||||||
|
public function getRole(string $rid): ?array
|
||||||
|
{
|
||||||
|
return $this->roleStore->fetchByRid($this->tenantIdentity->identifier(), $rid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new role
|
||||||
|
*/
|
||||||
|
public function createRole(array $roleData): array
|
||||||
|
{
|
||||||
|
$this->validateRoleData($roleData);
|
||||||
|
|
||||||
|
$this->logger->info('Creating role', [
|
||||||
|
'tenant' => $this->tenantIdentity->identifier(),
|
||||||
|
'label' => $roleData['label'] ?? 'Unnamed'
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $this->roleStore->createRole($this->tenantIdentity->identifier(), $roleData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update existing role
|
||||||
|
*/
|
||||||
|
public function updateRole(string $rid, array $updates): bool
|
||||||
|
{
|
||||||
|
// Verify role exists and is not system role
|
||||||
|
$role = $this->getRole($rid);
|
||||||
|
if (!$role) {
|
||||||
|
throw new \InvalidArgumentException('Role not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($role['system'] ?? false) {
|
||||||
|
throw new \InvalidArgumentException('Cannot modify system roles');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->validateRoleData($updates, false);
|
||||||
|
|
||||||
|
$this->logger->info('Updating role', [
|
||||||
|
'tenant' => $this->tenantIdentity->identifier(),
|
||||||
|
'rid' => $rid
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $this->roleStore->updateRole($this->tenantIdentity->identifier(), $rid, $updates);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a role
|
||||||
|
*/
|
||||||
|
public function deleteRole(string $rid): bool
|
||||||
|
{
|
||||||
|
// Verify role exists and is not system role
|
||||||
|
$role = $this->getRole($rid);
|
||||||
|
if (!$role) {
|
||||||
|
throw new \InvalidArgumentException('Role not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($role['system'] ?? false) {
|
||||||
|
throw new \InvalidArgumentException('Cannot delete system roles');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if role is assigned to users
|
||||||
|
$userCount = $this->roleStore->countUsersInRole($this->tenantIdentity->identifier(), $rid);
|
||||||
|
if ($userCount > 0) {
|
||||||
|
throw new \InvalidArgumentException("Cannot delete role assigned to {$userCount} user(s)");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logger->info('Deleting role', [
|
||||||
|
'tenant' => $this->tenantIdentity->identifier(),
|
||||||
|
'rid' => $rid
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $this->roleStore->deleteRole($this->tenantIdentity->identifier(), $rid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user count for a role
|
||||||
|
*/
|
||||||
|
public function getRoleUserCount(string $rid): int
|
||||||
|
{
|
||||||
|
return $this->roleStore->countUsersInRole($this->tenantIdentity->identifier(), $rid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all available permissions from modules
|
||||||
|
* Grouped by category with metadata
|
||||||
|
*/
|
||||||
|
public function availablePermissions(): array
|
||||||
|
{
|
||||||
|
return $this->roleStore->availablePermissions();
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Validation
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate role data
|
||||||
|
*/
|
||||||
|
private function validateRoleData(array $data, bool $isCreate = true): void
|
||||||
|
{
|
||||||
|
if ($isCreate && empty($data['label'])) {
|
||||||
|
throw new \InvalidArgumentException('Role label is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($data['permissions']) && !is_array($data['permissions'])) {
|
||||||
|
throw new \InvalidArgumentException('Permissions must be an array');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ namespace KTXC\Stores;
|
|||||||
use KTXC\Db\DataStore;
|
use KTXC\Db\DataStore;
|
||||||
use KTXF\Utile\UUID;
|
use KTXF\Utile\UUID;
|
||||||
|
|
||||||
class UserStore
|
class UserAccountsStore
|
||||||
{
|
{
|
||||||
|
|
||||||
public function __construct(protected DataStore $store)
|
public function __construct(protected DataStore $store)
|
||||||
@@ -15,6 +15,64 @@ class UserStore
|
|||||||
// User Operations (Full User Object)
|
// User Operations (Full User Object)
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all users for a tenant with optional filters
|
||||||
|
*/
|
||||||
|
public function listUsers(string $tenant, array $filters = []): array
|
||||||
|
{
|
||||||
|
// Build filter
|
||||||
|
$filter = ['tid' => $tenant];
|
||||||
|
|
||||||
|
if (isset($filters['enabled'])) {
|
||||||
|
$filter['enabled'] = (bool)$filters['enabled'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($filters['role'])) {
|
||||||
|
$filter['roles'] = $filters['role'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch users with aggregated role data
|
||||||
|
$pipeline = [
|
||||||
|
['$match' => $filter],
|
||||||
|
[
|
||||||
|
'$lookup' => [
|
||||||
|
'from' => 'user_roles',
|
||||||
|
'localField' => 'roles',
|
||||||
|
'foreignField' => 'rid',
|
||||||
|
'as' => 'role_details'
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'$addFields' => [
|
||||||
|
'permissions' => [
|
||||||
|
'$reduce' => [
|
||||||
|
'input' => [
|
||||||
|
'$map' => [
|
||||||
|
'input' => '$role_details',
|
||||||
|
'as' => 'r',
|
||||||
|
'in' => ['$ifNull' => ['$$r.permissions', []]]
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'initialValue' => [],
|
||||||
|
'in' => ['$setUnion' => ['$$value', '$$this']]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
],
|
||||||
|
['$unset' => 'role_details'],
|
||||||
|
['$sort' => ['label' => 1]]
|
||||||
|
];
|
||||||
|
|
||||||
|
$cursor = $this->store->selectCollection('user_accounts')->aggregate($pipeline);
|
||||||
|
|
||||||
|
$users = [];
|
||||||
|
foreach ($cursor as $entry) {
|
||||||
|
$users[] = (array)$entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $users;
|
||||||
|
}
|
||||||
|
|
||||||
public function fetchByIdentity(string $tenant, string $identity): array | null
|
public function fetchByIdentity(string $tenant, string $identity): array | null
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -55,7 +113,7 @@ class UserStore
|
|||||||
[ '$unset' => 'role_details' ]
|
[ '$unset' => 'role_details' ]
|
||||||
];
|
];
|
||||||
|
|
||||||
$entry = $this->store->selectCollection('users')->aggregate($pipeline)->toArray()[0] ?? null;
|
$entry = $this->store->selectCollection('user_accounts')->aggregate($pipeline)->toArray()[0] ?? null;
|
||||||
if (!$entry) { return null; }
|
if (!$entry) { return null; }
|
||||||
return (array)$entry;
|
return (array)$entry;
|
||||||
}
|
}
|
||||||
@@ -97,14 +155,14 @@ class UserStore
|
|||||||
[ '$unset' => 'role_details' ]
|
[ '$unset' => 'role_details' ]
|
||||||
];
|
];
|
||||||
|
|
||||||
$entry = $this->store->selectCollection('users')->aggregate($pipeline)->toArray()[0] ?? null;
|
$entry = $this->store->selectCollection('user_accounts')->aggregate($pipeline)->toArray()[0] ?? null;
|
||||||
if (!$entry) { return null; }
|
if (!$entry) { return null; }
|
||||||
return (array)$entry;
|
return (array)$entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function fetchByProviderSubject(string $tenant, string $provider, string $subject): array | null
|
public function fetchByProviderSubject(string $tenant, string $provider, string $subject): array | null
|
||||||
{
|
{
|
||||||
$entry = $this->store->selectCollection('users')->findOne([
|
$entry = $this->store->selectCollection('user_accounts')->findOne([
|
||||||
'tid' => $tenant,
|
'tid' => $tenant,
|
||||||
'provider' => $provider,
|
'provider' => $provider,
|
||||||
'provider_subject' => $subject
|
'provider_subject' => $subject
|
||||||
@@ -122,14 +180,14 @@ class UserStore
|
|||||||
$userData['profile'] = $userData['profile'] ?? [];
|
$userData['profile'] = $userData['profile'] ?? [];
|
||||||
$userData['settings'] = $userData['settings'] ?? [];
|
$userData['settings'] = $userData['settings'] ?? [];
|
||||||
|
|
||||||
$this->store->selectCollection('users')->insertOne($userData);
|
$this->store->selectCollection('user_accounts')->insertOne($userData);
|
||||||
|
|
||||||
return $this->fetchByIdentifier($tenant, $userData['uid']);
|
return $this->fetchByIdentifier($tenant, $userData['uid']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updateUser(string $tenant, string $uid, array $updates): bool
|
public function updateUser(string $tenant, string $uid, array $updates): bool
|
||||||
{
|
{
|
||||||
$result = $this->store->selectCollection('users')->updateOne(
|
$result = $this->store->selectCollection('user_accounts')->updateOne(
|
||||||
['tid' => $tenant, 'uid' => $uid],
|
['tid' => $tenant, 'uid' => $uid],
|
||||||
['$set' => $updates]
|
['$set' => $updates]
|
||||||
);
|
);
|
||||||
@@ -139,7 +197,7 @@ class UserStore
|
|||||||
|
|
||||||
public function deleteUser(string $tenant, string $uid): bool
|
public function deleteUser(string $tenant, string $uid): bool
|
||||||
{
|
{
|
||||||
$result = $this->store->selectCollection('users')->deleteOne([
|
$result = $this->store->selectCollection('user_accounts')->deleteOne([
|
||||||
'tid' => $tenant,
|
'tid' => $tenant,
|
||||||
'uid' => $uid
|
'uid' => $uid
|
||||||
]);
|
]);
|
||||||
@@ -153,7 +211,7 @@ class UserStore
|
|||||||
|
|
||||||
public function fetchProfile(string $tenant, string $uid): ?array
|
public function fetchProfile(string $tenant, string $uid): ?array
|
||||||
{
|
{
|
||||||
$user = $this->store->selectCollection('users')->findOne(
|
$user = $this->store->selectCollection('user_accounts')->findOne(
|
||||||
['tid' => $tenant, 'uid' => $uid],
|
['tid' => $tenant, 'uid' => $uid],
|
||||||
['projection' => ['profile' => 1, 'provider_managed_fields' => 1]]
|
['projection' => ['profile' => 1, 'provider_managed_fields' => 1]]
|
||||||
);
|
);
|
||||||
@@ -179,7 +237,7 @@ class UserStore
|
|||||||
$updates["profile.{$key}"] = $value;
|
$updates["profile.{$key}"] = $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = $this->store->selectCollection('users')->updateOne(
|
$result = $this->store->selectCollection('user_accounts')->updateOne(
|
||||||
['tid' => $tenant, 'uid' => $uid],
|
['tid' => $tenant, 'uid' => $uid],
|
||||||
['$set' => $updates]
|
['$set' => $updates]
|
||||||
);
|
);
|
||||||
@@ -194,7 +252,7 @@ class UserStore
|
|||||||
public function fetchSettings(string $tenant, string $uid, array $settings = []): ?array
|
public function fetchSettings(string $tenant, string $uid, array $settings = []): ?array
|
||||||
{
|
{
|
||||||
// Only fetch the settings field from the database
|
// Only fetch the settings field from the database
|
||||||
$user = $this->store->selectCollection('users')->findOne(
|
$user = $this->store->selectCollection('user_accounts')->findOne(
|
||||||
['tid' => $tenant, 'uid' => $uid],
|
['tid' => $tenant, 'uid' => $uid],
|
||||||
['projection' => ['settings' => 1]]
|
['projection' => ['settings' => 1]]
|
||||||
);
|
);
|
||||||
@@ -227,7 +285,7 @@ class UserStore
|
|||||||
$updates["settings.{$key}"] = $value;
|
$updates["settings.{$key}"] = $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = $this->store->selectCollection('users')->updateOne(
|
$result = $this->store->selectCollection('user_accounts')->updateOne(
|
||||||
['tid' => $tenant, 'uid' => $uid],
|
['tid' => $tenant, 'uid' => $uid],
|
||||||
['$set' => $updates]
|
['$set' => $updates]
|
||||||
);
|
);
|
||||||
142
core/lib/Stores/UserRolesStore.php
Normal file
142
core/lib/Stores/UserRolesStore.php
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace KTXC\Stores;
|
||||||
|
|
||||||
|
use KTXC\Db\DataStore;
|
||||||
|
use KTXC\Module\ModuleManager;
|
||||||
|
use KTXF\Utile\UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Role Store - Database operations for user roles
|
||||||
|
*/
|
||||||
|
class UserRolesStore
|
||||||
|
{
|
||||||
|
protected const COLLECTION_NAME = 'user_roles';
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
protected readonly DataStore $store,
|
||||||
|
protected readonly ModuleManager $moduleManager
|
||||||
|
) {}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Role Operations
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all roles for a tenant
|
||||||
|
*/
|
||||||
|
public function listRoles(string $tenant): array
|
||||||
|
{
|
||||||
|
$cursor = $this->store->selectCollection(self::COLLECTION_NAME)->find(
|
||||||
|
['tid' => $tenant],
|
||||||
|
['sort' => ['label' => 1]]
|
||||||
|
);
|
||||||
|
|
||||||
|
$roles = [];
|
||||||
|
foreach ($cursor as $entry) {
|
||||||
|
$role = (array)$entry;
|
||||||
|
// Ensure permissions is an array
|
||||||
|
if (isset($role['permissions'])) {
|
||||||
|
$role['permissions'] = (array)$role['permissions'];
|
||||||
|
}
|
||||||
|
$roles[] = $role;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch role by tenant and role ID
|
||||||
|
*/
|
||||||
|
public function fetchByRid(string $tenant, string $rid): ?array
|
||||||
|
{
|
||||||
|
$entry = $this->store->selectCollection(self::COLLECTION_NAME)->findOne([
|
||||||
|
'tid' => $tenant,
|
||||||
|
'rid' => $rid
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!$entry) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (array)$entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new role
|
||||||
|
*/
|
||||||
|
public function createRole(string $tenant, array $roleData): array
|
||||||
|
{
|
||||||
|
$roleData['tid'] = $tenant;
|
||||||
|
$roleData['rid'] = $roleData['rid'] ?? UUID::v4();
|
||||||
|
$roleData['label'] = $roleData['label'] ?? 'Unnamed Role';
|
||||||
|
$roleData['description'] = $roleData['description'] ?? '';
|
||||||
|
$roleData['permissions'] = $roleData['permissions'] ?? [];
|
||||||
|
$roleData['system'] = $roleData['system'] ?? false;
|
||||||
|
|
||||||
|
$this->store->selectCollection(self::COLLECTION_NAME)->insertOne($roleData);
|
||||||
|
|
||||||
|
return $this->fetchByRid($tenant, $roleData['rid']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update an existing role
|
||||||
|
*/
|
||||||
|
public function updateRole(string $tenant, string $rid, array $updates): bool
|
||||||
|
{
|
||||||
|
// Prevent updating system flag
|
||||||
|
unset($updates['tid'], $updates['rid'], $updates['system']);
|
||||||
|
|
||||||
|
if (empty($updates)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $this->store->selectCollection(self::COLLECTION_NAME)->updateOne(
|
||||||
|
['tid' => $tenant, 'rid' => $rid],
|
||||||
|
['$set' => $updates]
|
||||||
|
);
|
||||||
|
|
||||||
|
return $result->getModifiedCount() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a role
|
||||||
|
*/
|
||||||
|
public function deleteRole(string $tenant, string $rid): bool
|
||||||
|
{
|
||||||
|
// Check if role is system role
|
||||||
|
$role = $this->fetchByRid($tenant, $rid);
|
||||||
|
if (!$role || ($role['system'] ?? false)) {
|
||||||
|
return false; // Cannot delete system roles
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $this->store->selectCollection(self::COLLECTION_NAME)->deleteOne([
|
||||||
|
'tid' => $tenant,
|
||||||
|
'rid' => $rid
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $result->getDeletedCount() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count users assigned to a role
|
||||||
|
*/
|
||||||
|
public function countUsersInRole(string $tenant, string $rid): int
|
||||||
|
{
|
||||||
|
$count = $this->store->selectCollection('user_accounts')->countDocuments([
|
||||||
|
'tid' => $tenant,
|
||||||
|
'roles' => $rid
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (int)$count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all available permissions from modules
|
||||||
|
* Grouped by category with metadata
|
||||||
|
*/
|
||||||
|
public function availablePermissions(): array
|
||||||
|
{
|
||||||
|
return $this->moduleManager->availablePermissions();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user