Add permission node system and extend subuser grants functionality

- Introduce a comprehensive permission system for subusers, including permission nodes and types with support for vehicles, bookings, orders, subusers, and self-serve modules.
- Implement `subusers_user`, `subuser_user_grant`, and `subusers_permission_node` classes for managing subuser permissions and grants.
- Extend `subuser_grants_o` with methods to retrieve permissions for subusers linked to customers.
- Add traits and enumerations to streamline permission handling across modules.
- Update subuser session handling to include token-based subuser lookups.
This commit is contained in:
Jeppe Bundgaard
2026-02-11 17:13:25 +01:00
parent 6ad28a26eb
commit c83b7d49e4
23 changed files with 587 additions and 0 deletions
@@ -0,0 +1,59 @@
<?php
namespace modules\subusers\classes;
use modules\subusers\interfaces\subusers_user_grant_i;
use modules\subusers\traits\subuser_user_grant_t;
use modules\subusers\traits\subusers_user_permissions_t;
use objects\subuser_grants_o;
class subuser_user_grant implements subusers_user_grant_i
{
use subuser_user_grant_t,
subusers_user_permissions_t;
public int $subuser_id;
public int $customer_number;
/**
* @var subusers_permission_node[] $permission_nodes An array of permission nodes that define the permissions granted to the subuser for this customer
*/
protected array $permission_nodes;
public function __construct(int $subuser_id, int $customer_number)
{
$this->subuser_id = $subuser_id;
$this->customer_number = $customer_number;
// Get the grants and enable the permission nodes based on the grants
$this->loadGrants();
}
public function loadGrants(): void
{
$subuser_grants_o = new subuser_grants_o();
$subuser_grants = $subuser_grants_o->getGrantsForSubuserAndCustomer($this->subuser_id, $this->customer_number);
// Loop through the permissions and enable the corresponding permission nodes
foreach ($subuser_grants as $permission) {
$this->enablePermissionNode($permission);
}
}
private function enablePermissionNode(string $permission): void
{
// Get all properties extending the subusers_permission_nodes class
$reflection = new \ReflectionClass($this);
$properties = $reflection->getProperties();
foreach ($properties as $property) {
$propertyType = $property->getType();
if ($propertyType && is_a($propertyType->getName(), subusers_permission_nodes::class, true)) {
$permissionNodes = $property->getValue($this);
if (is_array($permissionNodes)) {
foreach ($permissionNodes as $node) {
if ($node->nodeKey->name === $permission) {
$node->value = true;
return;
}
}
}
}
}
}
}
@@ -0,0 +1,53 @@
<?php
namespace modules\subusers\classes;
use modules\subusers\helpers\subusers_permission_node_key;
use modules\subusers\helpers\subusers_permission_type;
use modules\subusers\interfaces\subusers_permission_node_i;
use modules\subusers\traits\subusers_permission_node_t;
class subusers_permission_node implements subusers_permission_node_i
{
use subusers_permission_node_t;
/**
* The node key of the permission node, used for internal reference and storage
* @var subusers_permission_node_key $nodeKey
*/
protected subusers_permission_node_key $nodeKey;
/**
* The name of the permission node
* @var string $name
*/
public string $name;
/**
* The description of the permission node
* @var string $description
*/
public string $description;
/**
* The type of permission this node represents
* @var subusers_permission_type $type
*/
public subusers_permission_type $type;
/**
* The value of the permission node, true if the permission is granted, false if not
* @var bool|null $value
*/
public ?bool $value;
public function __construct(
subusers_permission_node_key $nodeKey,
string $name,
string $description,
subusers_permission_type $type,
?bool $value = null
) {
$this->nodeKey = $nodeKey;
$this->name = $name;
$this->description = $description;
$this->type = $type;
$this->value = $value;
}
}
@@ -0,0 +1,11 @@
<?php
namespace modules\subusers\classes;
use modules\subusers\interfaces\subusers_permission_nodes_i;
use modules\subusers\traits\subusers_permission_nodes_t;
class subusers_permission_nodes implements subusers_permission_nodes_i
{
use subusers_permission_nodes_t;
}
@@ -0,0 +1,50 @@
<?php
namespace modules\subusers\classes;
use Exception;
use modules\subusers\interfaces\subusers_user_i;
use modules\subusers\traits\subusers_user_t;
use objects\subusers_o;
class subusers_user implements subusers_user_i
{
use subusers_user_t;
public int $subuserId;
/**
* Get a subuser by their ID.
* @param int $subuserId
* @return subusers_user|null
*/
public static function bySubuserId(int $subuserId): ?subusers_user
{
$subuser = new subusers_user();
$subuser->subuserId = $subuserId;
return $subuser;
}
/**
* Get a subuser by their username.
* @param string $username
* @return subusers_user
* @throws Exception If there was an error getting the subuser by username or if the subuser does not exist
*/
public static function byUsername(string $username): subusers_user
{
return self::bySubuserId((new subusers_o())->getSubuserByUsername($username)->id);
}
/**
* Get a subuser by their session token.
* @param string $token
* @return subusers_user|null
* @throws Exception If there was an error getting the subuser by session token
*/
public static function bySessionToken(string $token): ?subusers_user
{
$subusers_o = new subusers_o();
$subusers_o->getSubuserBySessionToken($token);
return self::bySubuserId($subusers_o->id);
}
}
@@ -0,0 +1,61 @@
<?php
namespace modules\subusers\helpers;
enum subusers_permission_node_key
{
/** Vehicles */
case VEHICLES_LIST;
case VEHICLES_EDIT;
case VEHICLES_DELETE;
case VEHICLES_ADD;
/** Self-Serve */
case SELFSERVE_ADD;
/** Bookings */
case BOOKINGS_LIST;
case BOOKINGS_EDIT;
case BOOKINGS_DELETE;
case BOOKINGS_ADD;
/** Orders */
case ORDERS_LIST;
case ORDERS_EDIT;
/** Subusers */
case SUBUSERS_LIST;
case SUBUSERS_EDIT;
case SUBUSERS_DELETE;
case SUBUSERS_ADD;
public static function tryFrom(string $param): ?subusers_permission_node_key
{
return match (strtoupper($param)) {
/** Vehicles */
'VEHCILES_LIST' => subusers_permission_node_key::VEHICLES_LIST,
'VEHCILES_EDIT' => subusers_permission_node_key::VEHICLES_EDIT,
'VEHCILES_DELETE' => subusers_permission_node_key::VEHICLES_DELETE,
'VEHCILES_ADD' => subusers_permission_node_key::VEHICLES_ADD,
/** Self-Serve */
'SELFSERVE_ADD' => subusers_permission_node_key::SELFSERVE_ADD,
/** Bookings */
'BOOKINGS_LIST' => subusers_permission_node_key::BOOKINGS_LIST,
'BOOKINGS_EDIT' => subusers_permission_node_key::BOOKINGS_EDIT,
'BOOKINGS_DELETE' => subusers_permission_node_key::BOOKINGS_DELETE,
'BOOKINGS_ADD' => subusers_permission_node_key::BOOKINGS_ADD,
/** Orders */
'ORDERS_LIST' => subusers_permission_node_key::ORDERS_LIST,
'ORDERS_EDIT' => subusers_permission_node_key::ORDERS_EDIT,
/** Subusers */
'SUBUSERS_LIST' => subusers_permission_node_key::SUBUSERS_LIST,
'SUBUSERS_EDIT' => subusers_permission_node_key::SUBUSERS_EDIT,
'SUBUSERS_DELETE' => subusers_permission_node_key::SUBUSERS_DELETE,
'SUBUSERS_ADD' => subusers_permission_node_key::SUBUSERS_ADD,
default => null,
};
}
public function equals(subusers_permission_node_key $param): bool
{
return $this === $param;
}
}
@@ -0,0 +1,28 @@
<?php
namespace modules\subusers\helpers;
enum subusers_permission_type
{
case ADD;
case EDIT;
case DELETE;
case VIEW;
public static function tryFrom(string $param): ?subusers_permission_type
{
return match (strtoupper($param)) {
'DRIVER' => subusers_permission_type::ADD,
'EDIT' => subusers_permission_type::EDIT,
'DELETE' => subusers_permission_type::DELETE,
'VIEW' => subusers_permission_type::VIEW,
default => null,
};
}
public function equals(subusers_permission_type $param): bool
{
return $this === $param;
}
}
@@ -0,0 +1,7 @@
<?php
namespace modules\subusers\interfaces;
interface subusers_permission_node_i
{
}
@@ -0,0 +1,8 @@
<?php
namespace modules\subusers\interfaces;
interface subusers_permission_nodes_i
{
}
@@ -0,0 +1,8 @@
<?php
namespace modules\subusers\interfaces;
interface subusers_user_grant_i
{
}
@@ -0,0 +1,8 @@
<?php
namespace modules\subusers\interfaces;
interface subusers_user_i
{
}
@@ -0,0 +1,23 @@
<?php
namespace modules\subusers\permissions;
use modules\subusers\classes\subusers_permission_nodes;
use modules\subusers\helpers\subusers_permission_node_key;
use modules\subusers\helpers\subusers_permission_type;
class subusers_permission_nodes_bookings extends subusers_permission_nodes
{
public function __construct()
{
parent::__construct(
'Bookings',
'Bookings management permissions'
);
$this->addNode(subusers_permission_node_key::BOOKINGS_LIST, 'List Bookings', 'Allows the user to view the list of bookings', subusers_permission_type::VIEW, false);
$this->addNode(subusers_permission_node_key::BOOKINGS_EDIT, 'Edit Bookings', 'Allows the user to edit existing bookings', subusers_permission_type::EDIT, false);
$this->addNode(subusers_permission_node_key::BOOKINGS_DELETE, 'Delete Bookings', 'Allows the user to delete existing bookings', subusers_permission_type::DELETE, false);
$this->addNode(subusers_permission_node_key::BOOKINGS_ADD, 'Add Bookings', 'Allows the user to add new bookings', subusers_permission_type::ADD, false);
}
}
@@ -0,0 +1,21 @@
<?php
namespace modules\subusers\permissions;
use modules\subusers\classes\subusers_permission_nodes;
use modules\subusers\helpers\subusers_permission_node_key;
use modules\subusers\helpers\subusers_permission_type;
class subusers_permission_nodes_orders extends subusers_permission_nodes
{
public function __construct()
{
parent::__construct(
'Orders',
'Orders management permissions'
);
$this->addNode(subusers_permission_node_key::ORDERS_LIST, 'View Orders', 'Allows the user to view the list of orders and order details', subusers_permission_type::VIEW, false);
$this->addNode(subusers_permission_node_key::ORDERS_EDIT, 'Edit Orders', 'Allows the user to edit existing orders', subusers_permission_type::EDIT, false);
}
}
@@ -0,0 +1,20 @@
<?php
namespace modules\subusers\permissions;
use modules\subusers\classes\subusers_permission_nodes;
use modules\subusers\helpers\subusers_permission_node_key;
use modules\subusers\helpers\subusers_permission_type;
class subusers_permission_nodes_selfserve extends subusers_permission_nodes
{
public function __construct()
{
parent::__construct(
'Selfserve',
'Self Serve Permissions',
);
$this->addNode(subusers_permission_node_key::SELFSERVE_ADD, 'Self Serve Add', 'Add self serve', subusers_permission_type::ADD, false);
}
}
@@ -0,0 +1,23 @@
<?php
namespace modules\subusers\permissions;
use modules\subusers\classes\subusers_permission_nodes;
use modules\subusers\helpers\subusers_permission_node_key;
use modules\subusers\helpers\subusers_permission_type;
class subusers_permission_nodes_subusers extends subusers_permission_nodes
{
public function __construct()
{
parent::__construct(
'Subusers',
'Set permissions related to subusers, including managing subusers and their permissions'
);
$this->addNode(subusers_permission_node_key::SUBUSERS_LIST, 'List Subusers', 'Allows the user to view the list of subusers', subusers_permission_type::VIEW, false);
$this->addNode(subusers_permission_node_key::SUBUSERS_EDIT, 'Edit Subusers', 'Allows the user to edit existing subusers', subusers_permission_type::EDIT, false);
$this->addNode(subusers_permission_node_key::SUBUSERS_DELETE, 'Delete Subusers', 'Allows the user to delete existing subusers', subusers_permission_type::DELETE, false);
$this->addNode(subusers_permission_node_key::SUBUSERS_ADD, 'Add Subusers', 'Allows the user to add new subusers', subusers_permission_type::ADD, false);
}
}
@@ -0,0 +1,23 @@
<?php
namespace modules\subusers\permissions;
use modules\subusers\classes\subusers_permission_nodes;
use modules\subusers\helpers\subusers_permission_node_key;
use modules\subusers\helpers\subusers_permission_type;
class subusers_permission_nodes_vehicles extends subusers_permission_nodes
{
public function __construct()
{
parent::__construct(
'Vehicles',
'Vehicles management permissions'
);
$this->addNode(subusers_permission_node_key::VEHICLES_LIST, 'Vehicles List', 'View vehicles list', subusers_permission_type::VIEW, false);
$this->addNode(subusers_permission_node_key::VEHICLES_EDIT, 'Vehicles Edit', 'Edit vehicles', subusers_permission_type::EDIT, false);
$this->addNode(subusers_permission_node_key::VEHICLES_DELETE, 'Vehicles Delete', 'Delete vehicles', subusers_permission_type::DELETE, false);
$this->addNode(subusers_permission_node_key::VEHICLES_ADD, 'Vehicles Add', 'Add vehicles', subusers_permission_type::ADD, false);
}
}
@@ -0,0 +1,8 @@
<?php
namespace modules\subusers\traits;
trait subuser_user_grant_t
{
}
@@ -0,0 +1,7 @@
<?php
namespace modules\subusers\traits;
trait subusers_permission_node_t
{
}
@@ -0,0 +1,61 @@
<?php
namespace modules\subusers\traits;
use modules\subusers\classes\subusers_permission_node;
use modules\subusers\helpers\subusers_permission_node_key;
use modules\subusers\helpers\subusers_permission_type;
trait subusers_permission_nodes_t
{
/**
* Permission nodes group name
* @var string $group_name
*/
public string $group_name;
/**
* Description
* @var string $description
*/
public string $description;
public function __construct(string $group_name, string $description)
{
$this->group_name = $group_name;
$this->description = $description;
}
/**
* @var subusers_permission_node[]
*/
public array $nodes = [];
/**
* @param subusers_permission_node_key $nodeKey
* @param string $name
* @param string $description
* @param subusers_permission_type $type
* @param bool $value
* @return void
*/
public function addNode(subusers_permission_node_key $nodeKey, string $name, string $description, subusers_permission_type $type, bool $value): void
{
$this->nodes[$nodeKey->name] = new subusers_permission_node($nodeKey, $name, $description, $type, $value);
}
public function getNode(subusers_permission_node_key $nodeKey): ?subusers_permission_node
{
return $this->nodes[$nodeKey->name] ?? null;
}
public function getNodeByKey(string $nodeKey): ?subusers_permission_node
{
return $this->nodes[$nodeKey] ?? null;
}
public function getNodeByName(string $nodeKey): ?subusers_permission_node
{
return $this->nodes[$nodeKey] ?? null;
}
}
@@ -0,0 +1,39 @@
<?php
namespace modules\subusers\traits;
use modules\subusers\permissions\subusers_permission_nodes_bookings;
use modules\subusers\permissions\subusers_permission_nodes_orders;
use modules\subusers\permissions\subusers_permission_nodes_selfserve;
use modules\subusers\permissions\subusers_permission_nodes_subusers;
use modules\subusers\permissions\subusers_permission_nodes_vehicles;
trait subusers_user_permissions_t
{
/**
* Booking permissions
* @var subusers_permission_nodes_bookings $bookings
*/
public subusers_permission_nodes_bookings $bookings;
/**
* Order permissions
* @var subusers_permission_nodes_orders
*/
public subusers_permission_nodes_orders $orders;
/**
* Self-Serve permissions
* @var subusers_permission_nodes_selfserve $selfserve
*/
public subusers_permission_nodes_selfserve $selfserve;
/***
* Subuser permissions
* @var subusers_permission_nodes_subusers $subusers
*/
public subusers_permission_nodes_subusers $subusers;
/**
* Vehicle permissions
* @var subusers_permission_nodes_vehicles $vehicles
*/
public subusers_permission_nodes_vehicles $vehicles;
}
@@ -0,0 +1,8 @@
<?php
namespace modules\subusers\traits;
trait subusers_user_t
{
}
@@ -69,4 +69,23 @@ class subuser_grants_o extends db
$this->getObjectProperties();
return $this;
}
public function getGrantsForSubuserAndCustomer(int $subuser_id, int $customer_number): array
{
$grants = self::getFieldsWhere([
'billing_customer_number' => $customer_number,
'subuser' => $subuser_id,
'enabled' => 1,
'deleted_at' => null,
], ['permissions']);
// Extract permissions from the grants
$permissions = [];
foreach ($grants as $grant) {
$grant_permissions = json_decode($grant['permissions'], true);
if (is_array($grant_permissions)) {
$permissions = array_merge($permissions, $grant_permissions);
}
}
return $permissions;
}
}
+18
View File
@@ -230,4 +230,22 @@ class subusers_o extends db
$this->setCachedExpiration('session_token:' . $session_token, 7 * 24 * 60 * 60, 'subuser_sessions'); // Set the session to expire after 7 days
return $session_token;
}
/**
* @param string $token The session token
* @return subusers_o|null The subuser object or null if the token is invalid or expired
* @throws Exception
*/
public function getSubuserBySessionToken(string $token): ?subusers_o
{
$cache_key = 'session_token:' . $token;
$cache_object_id = 'subuser_sessions';
$subuser_id = $this->getCached($cache_key, $cache_object_id);
if ($subuser_id === null) {
return null;
}
$subuser = (new subusers_o())->select((int)$subuser_id);
$subuser->getObjectProperties();
return $subuser;
}
}
@@ -0,0 +1,24 @@
<?php
namespace routes;
use classes\authentication;
use classes\economic;
use classes\gatewayapi;
use classes\response;
use classes\virkdata;
use Exception;
use modules\virkdata\helpers\virkdata_response;
use objects\logs_o;
use objects\subuser_grants_o;
use objects\subusers_o;
use objects\users_o;
use traits\route_t;
class subuserGrantsRoute
{
use route_t;
public function run(): void
{
}
}