implemented operation based permissions

This commit is contained in:
root
2025-12-24 19:22:20 -05:00
parent a9afa7ce13
commit 3d6aa856b4
18 changed files with 578 additions and 17 deletions

View File

@@ -4,6 +4,7 @@ namespace KTXC\Controllers;
use KTXC\Http\Response\JsonResponse;
use KTXC\Module\ModuleManager;
use KTXC\Security\Authorization\PermissionChecker;
use KTXC\Service\UserService;
use KTXC\SessionIdentity;
use KTXF\Controller\ControllerAbstract;
@@ -17,20 +18,33 @@ class InitController extends ControllerAbstract
private readonly SessionIdentity $userIdentity,
private readonly ModuleManager $moduleManager,
private readonly UserService $userService,
private readonly PermissionChecker $permissionChecker,
) {}
#[AuthenticatedRoute('/init', name: 'init', methods: ['GET'])]
#[AuthenticatedRoute(
'/init',
name: 'init',
methods: ['GET']
)]
public function index(): JsonResponse {
$configuration = [];
// modules
// modules - filter by permissions
$configuration['modules'] = [];
foreach ($this->moduleManager->list() as $module) {
if (!method_exists($module, 'bootUi')) {
continue;
}
$configuration['modules'][$module->handle()] = $module->bootUi();
// Check if user has permission to view this module
// Allow access if user has: {module_handle}, {module_handle}.*, or * permission
$handle = $module->handle();
if (!$this->hasModuleViewPermission($handle)) {
continue;
}
$configuration['modules'][$handle] = $module->bootUi();
}
// tenant
@@ -46,7 +60,8 @@ class InitController extends ControllerAbstract
'identifier' => $this->userIdentity->identifier(),
'identity' => $this->userIdentity->identity()->getIdentity(),
'label' => $this->userIdentity->label(),
'permissions' => [], // TODO: Implement permissions
'roles' => $this->userIdentity->identity()->getRoles(),
'permissions' => $this->userIdentity->identity()->getPermissions(),
],
'profile' => $this->userService->getEditableFields($this->userIdentity->identifier()),
'settings' => $this->userService->fetchSettings(),
@@ -56,4 +71,29 @@ class InitController extends ControllerAbstract
}
/**
* Check if user has permission to view a module
*
* Checks for the following permissions (in order):
* 1. {module_handle} - module access permission
* 2. {module_handle}.* - wildcard for the module
* 3. * - global wildcard
*
* @param string $moduleHandle The module handle to check
* @return bool
*/
private function hasModuleViewPermission(string $moduleHandle): bool
{
// Core module is always accessible to authenticated users
if ($moduleHandle === 'core') {
return true;
}
// Check for specific module permission or wildcard permissions
return $this->permissionChecker->canAny([
"{$moduleHandle}",
"{$moduleHandle}.*",
]);
}
}

View File

@@ -13,7 +13,12 @@ class ModuleController extends ControllerAbstract
private readonly ModuleManager $moduleManager
) { }
#[AuthenticatedRoute('/modules/list', name: 'modules.index', methods: ['GET'])]
#[AuthenticatedRoute(
'/modules/list',
name: 'modules.index',
methods: ['GET'],
permissions: ['module_manager.modules.view']
)]
public function index(): JsonResponse
{
$modules = $this->moduleManager->list(false);
@@ -21,7 +26,12 @@ class ModuleController extends ControllerAbstract
return new JsonResponse(['modules' => $modules]);
}
#[AuthenticatedRoute('/modules/manage', name: 'modules.manage', methods: ['POST'])]
#[AuthenticatedRoute(
'/modules/manage',
name: 'modules.manage',
methods: ['POST'],
permissions: ['module_manager.modules.manage']
)]
public function manage(string $handle, string $action): JsonResponse
{
// Verify module exists

View File

@@ -22,7 +22,12 @@ class UserProfileController extends ControllerAbstract
*
* @return JsonResponse Profile data with editability metadata
*/
#[AuthenticatedRoute('/user/profile', name: 'user.profile.read', methods: ['GET'])]
#[AuthenticatedRoute(
'/user/profile',
name: 'user.profile.read',
methods: ['GET'],
permissions: ['user.profile.read']
)]
public function read(): JsonResponse
{
$userId = $this->userIdentity->identifier();
@@ -50,7 +55,12 @@ class UserProfileController extends ControllerAbstract
*
* @return JsonResponse Updated profile data
*/
#[AuthenticatedRoute('/user/profile', name: 'user.profile.update', methods: ['PUT', 'PATCH'])]
#[AuthenticatedRoute(
'/user/profile',
name: 'user.profile.update',
methods: ['PUT', 'PATCH'],
permissions: ['user.profile.update']
)]
public function update(array $data): JsonResponse
{
$userId = $this->userIdentity->identifier();

View File

@@ -23,7 +23,12 @@ class UserSettingsController extends ControllerAbstract
*
* @return JsonResponse Settings data as key-value pairs
*/
#[AuthenticatedRoute('/user/settings', name: 'user.settings.read', methods: ['GET'])]
#[AuthenticatedRoute(
'/user/settings',
name: 'user.settings.read',
methods: ['GET'],
permissions: ['user.settings.read']
)]
public function read(): JsonResponse
{
// Fetch all settings (no filter)
@@ -48,7 +53,12 @@ class UserSettingsController extends ControllerAbstract
*
* @return JsonResponse Updated settings data
*/
#[AuthenticatedRoute('/user/settings', name: 'user.settings.update', methods: ['PUT', 'PATCH'])]
#[AuthenticatedRoute(
'/user/settings',
name: 'user.settings.update',
methods: ['PUT', 'PATCH'],
permissions: ['user.settings.update']
)]
public function update(array $data): JsonResponse
{
$this->userService->storeSettings($data);