550 lines
18 KiB
PHP
550 lines
18 KiB
PHP
<?php
|
|
|
|
namespace traits;
|
|
|
|
use classes\authentication;
|
|
use classes\recaptcha;
|
|
use classes\response;
|
|
use Exception;
|
|
use objects\logs_o;
|
|
|
|
trait route_t
|
|
{
|
|
protected array $permissions = [];
|
|
private string $route;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->route = $_SERVER['REQUEST_URI'];
|
|
}
|
|
|
|
public function run(): void
|
|
{
|
|
// Add the routes here
|
|
}
|
|
|
|
/**
|
|
* Is authenticated?
|
|
* This function returns a boolean indicating if the user is authenticated
|
|
* @return bool
|
|
*/
|
|
public function isAuthenticated(): bool
|
|
{
|
|
global $response;
|
|
try {
|
|
$user = (new authentication())->get_user();
|
|
// If there is no user, return false
|
|
if (!$user) {
|
|
return false;
|
|
}
|
|
return true;
|
|
} catch (Exception $e) {
|
|
$response->error($e->getMessage(), 400);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Require type
|
|
* @param mixed $value
|
|
* @param string $type
|
|
* @return bool
|
|
*/
|
|
public function requireType(mixed $value, string $type): bool
|
|
{
|
|
// Get the type of the value
|
|
$value_type = gettype($value);
|
|
// If the type is Array, or Object, json_decode the value to check if it is a valid JSON
|
|
if ($type === 'array' || $type === 'object') {
|
|
if (is_array($value)) {
|
|
$value = json_encode($value);
|
|
} elseif (is_object($value)) {
|
|
$value = json_encode($value);
|
|
}
|
|
$value = json_decode($value, true);
|
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
|
global $response;
|
|
$response->error('Invalid JSON. Value: ' . $value, 400);
|
|
}
|
|
$value_type = gettype($value);
|
|
}
|
|
// Check if the value is of the required type
|
|
if ($value_type !== $type) {
|
|
global $response;
|
|
$response->error('Invalid type. Expected: ' . $type . ' Got: ' . $value_type . ' Value: ' . (is_array($value) ? json_encode($value) : $value), 400);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Require a value to be in the given array
|
|
* @param mixed $value The value to check
|
|
* @param array $array The array to check against
|
|
* @return bool
|
|
* @throws Exception If the value is not in the array
|
|
*/
|
|
public function requireInArray(mixed $value, array $array): bool
|
|
{
|
|
global $response;
|
|
// Check if the value is in the array
|
|
if (!in_array($value, $array)) {
|
|
$response->error(json_encode([
|
|
'message' => 'Unexpected value',
|
|
'expected' => $array,
|
|
'got' => $value,
|
|
'type' => gettype($value)
|
|
]), 400);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public function requireSameLength(mixed $arg1, mixed $arg2): bool
|
|
{
|
|
global $response;
|
|
if (strlen($arg1) !== strlen($arg2)) {
|
|
$response->error('Invalid length. Expected: ' . strlen($arg2) . ' Got: ' . strlen($arg1), 400);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Require parameter to be one of the given types
|
|
* @param mixed $value The value to check
|
|
* @param array $types The types to check against
|
|
* @return bool
|
|
*/
|
|
public function requireTypeIn(mixed $value, array $types): bool
|
|
{
|
|
// Get the type of the value
|
|
$value_type = gettype($value);
|
|
// Check if the value is of the required type
|
|
if (!in_array($value_type, $types)) {
|
|
global $response;
|
|
$response->error('Invalid type. Expected: ' . implode(', ', $types) . ' Got: ' . $value_type . ' Value: ' . $value, 400);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* TYPE_ARRAY
|
|
* @note This is used to check if the value is an array, this is used in routes to validate input
|
|
* @return string
|
|
*/
|
|
public function TYPE_ARRAY(): string
|
|
{
|
|
return 'array';
|
|
}
|
|
|
|
/**
|
|
* TYPE_NULL
|
|
* @note This is used to check if the value is null, this is used in routes to validate input
|
|
* @return string
|
|
*/
|
|
public function TYPE_NULL(): string
|
|
{
|
|
return 'NULL';
|
|
}
|
|
|
|
/**
|
|
* The default date format (Y-m-d) Example: 2023-01-01
|
|
* @note This is used to format dates in the API
|
|
* @return string
|
|
*/
|
|
public function FORMAT_DATE(): string
|
|
{
|
|
return 'Y-m-d';
|
|
}
|
|
|
|
/**
|
|
* TYPE_BOOL
|
|
* @note This is used to check if the value is a boolean, this is used in routes to validate input
|
|
* @return string
|
|
*/
|
|
public function TYPE_BOOL(): string
|
|
{
|
|
return 'boolean';
|
|
}
|
|
|
|
/**
|
|
* @param string $date
|
|
* @param string $format
|
|
* @return void
|
|
*/
|
|
public function requireDateFormat(string $date, string $format): void
|
|
{
|
|
global $response;
|
|
$d = \DateTime::createFromFormat($format, $date);
|
|
if (!$d || $d->format($format) !== $date) {
|
|
$response->error('Invalid date format. Expected: ' . $format . ' Got: ' . $date, 400);
|
|
}
|
|
}
|
|
|
|
public function requireMinValue(int $value, int $min): void
|
|
{
|
|
global $response;
|
|
if ($value < $min) {
|
|
$response->error('Parameter must be at least ' . $min, 400);
|
|
}
|
|
}
|
|
|
|
public function type_int(): string
|
|
{
|
|
return 'integer';
|
|
}
|
|
|
|
public function type_string(): string
|
|
{
|
|
return 'string';
|
|
}
|
|
|
|
public function requireParameters(array $parameters): void
|
|
{
|
|
global $response;
|
|
$missing_parameters = [];
|
|
// Check if all required parameters are set
|
|
foreach ( $parameters as $parameter ) {
|
|
if (!$response->getRequestParameter($parameter) && !$response->isRequestParameterSet($parameter)) {
|
|
$missing_parameters[] = $parameter;
|
|
}
|
|
}
|
|
if (count($missing_parameters) > 0) {
|
|
$response->error('Missing required parameters: ' . implode(', ', $missing_parameters), 400);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Require the max value of the given value
|
|
* @param int $value - The value to check
|
|
* @param int $max - The max value
|
|
*/
|
|
public function requireMaxValue(int $value, int $max): void
|
|
{
|
|
global
|
|
/** @var response $response */
|
|
$response;
|
|
if ($value > $max) {
|
|
$response->error('Parameter must be at most ' . $max, 400);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Require department access
|
|
* @note This checks if the user has access to the department by checking if the user has the permission department_access_{department} (_{permission} if provided)
|
|
* @param string $department
|
|
* @param string|null $permission
|
|
*/
|
|
public function requireDepartmentAccess(string $department, string|null $permission = null): void
|
|
{
|
|
self::requirePermission('department_access_' . $department . ($permission ? '_' . $permission : ''));
|
|
}
|
|
|
|
/**
|
|
* Has department access?
|
|
* @note This checks if the user has access to the department by checking if the user has the permission department_access_{department} (_{permission} if provided)
|
|
* @param string $department
|
|
* @param string|null $permission
|
|
* @return bool
|
|
*/
|
|
public function hasDepartmentAccess(string $department, string|null $permission = null): bool
|
|
{
|
|
return self::hasPermission('department_access_' . $department . ($permission ? '_' . $permission : ''));
|
|
}
|
|
|
|
/**
|
|
* Require permission
|
|
* @param string $permission
|
|
* @return bool
|
|
*/
|
|
public function requirePermission(string $permission): bool
|
|
{
|
|
global $response;
|
|
// Check if the users authorization token has the required permission
|
|
try {
|
|
$user = (new authentication())->get_user();
|
|
// If there is no user, return an error
|
|
if (!$user) {
|
|
(new logs_o())->add('global', 'global', 1, 0, 'AUTHENTICATION_FAILED', 'Authentication failed. Invalid, or missing token');
|
|
$response->error('Authentication failed. Invalid or missing token.', 401);
|
|
}
|
|
if (!$user->hasPermission($permission)) {
|
|
(new logs_o())->add('global', 'global', 1, $user->id, 'PERMISSION_DENIED', 'Permission denied. Missing permission: ' . $permission);
|
|
$response->error('Permission denied. Missing permission: ' . $permission . ' for user: ' . $user->id . ' In group: ' . $user->group_id->value(), 403);
|
|
}
|
|
} catch (Exception $e) {
|
|
$response->error($e->getMessage(), 400);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Check if the user has a permission
|
|
* @param string $permission
|
|
* @return bool
|
|
*/
|
|
public function hasPermission(string $permission): bool
|
|
{
|
|
global $response;
|
|
// Check if the users authorization token has the required permission
|
|
try {
|
|
$user = (new authentication())->get_user();
|
|
// If there is no user, return an error
|
|
if (!$user) {
|
|
(new logs_o())->add('global', 'global', 1, 0, 'AUTHENTICATION_FAILED', 'Authentication failed. Invalid, or missing token');
|
|
$response->error('Authentication failed. Invalid or missing token.', 401);
|
|
}
|
|
return $user->hasPermission($permission);
|
|
} catch (Exception $e) {
|
|
$response->error($e->getMessage(), 400);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Require parameter to be a positive integer
|
|
* @param int $value The value to check
|
|
* @param string|null $parameter The name of the parameter
|
|
* @return void
|
|
*/
|
|
public function requireParameterIntPositive(int $value, string $parameter = null): void
|
|
{
|
|
global $response;
|
|
if ($value <= 0) {
|
|
$response->error('Parameter ' . $parameter . ' must be a positive integer', 400);
|
|
}
|
|
}
|
|
|
|
public function requireMinLength(string $parameter, int $length): void
|
|
{
|
|
global $response;
|
|
$value = self::getParameter($parameter);
|
|
if (strlen($value) < $length) {
|
|
$response->error('Parameter ' . $parameter . ' must be at least ' . $length . ' characters long', 400);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the parameter from the request by name
|
|
* This is a shorthand for the response class method
|
|
* @param string $parameter
|
|
* @return mixed|null
|
|
*/
|
|
public function getParameter(string $parameter): mixed
|
|
{
|
|
global $response;
|
|
return $response->getRequestParameter($parameter);
|
|
}
|
|
|
|
public function requireMaxLength(string $parameter, int $length): void
|
|
{
|
|
global $response;
|
|
$value = self::getParameter($parameter);
|
|
if (strlen($value) > $length) {
|
|
$response->error('Parameter ' . $parameter . ' must be at most ' . $length . ' characters long', 400);
|
|
}
|
|
}
|
|
|
|
public function isParametersSet(array $parameters): bool
|
|
{
|
|
global $response;
|
|
// Check if all required parameters are set
|
|
foreach ( $parameters as $parameter ) {
|
|
if (!$response->isRequestParameterSet($parameter)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* GET route
|
|
* @param string $route Example: /home, /home/{id}
|
|
* @param callable $callback
|
|
* @param array $permissions
|
|
* @return void
|
|
*/
|
|
public function get(string $route, callable $callback, array $permissions = []): void
|
|
{
|
|
$this->registerRoute($route, 'GET', $callback, $permissions);
|
|
}
|
|
|
|
private function registerRoute($route, $method, $callback, $permissions = []): void
|
|
{
|
|
global $router;
|
|
$router->add($route, $method, $callback, self::registerPermissions($permissions, $route, $method));
|
|
}
|
|
|
|
/**
|
|
* Register permissions for the route
|
|
* @param array $permissions The permissions to register
|
|
* @param null $endpoint
|
|
* @param null $method
|
|
* @return array
|
|
*/
|
|
public function registerPermissions(array $permissions, $endpoint = null, $method = null): array
|
|
{
|
|
foreach ( $permissions as $permission => $description ) {
|
|
$this->registerPermission($permission, $description, $endpoint, $method);
|
|
}
|
|
return $this->permissions;
|
|
}
|
|
|
|
/**
|
|
* Register a permission
|
|
* @param string $permission The permission to register (Example: 'modules_motorapi_lookup')
|
|
* @param string $description The description of the permission (Example: 'Lookup license plate information')
|
|
* @param null $endpoint The endpoint of the permission (Example: '/department/license-plate/lookup')
|
|
* @param null $method The method of the permission (Example: 'GET')
|
|
* @return void
|
|
*/
|
|
public function registerPermission(string $permission, string $description = 'No description provided.', $endpoint = null, $method = null): void
|
|
{
|
|
$this->permissions[$endpoint ?? $this->route][$method ?? 'UNKNOWN_METHOD'][$permission] = $description;
|
|
}
|
|
|
|
/**
|
|
* POST route
|
|
* @param string $route Example: /home, /home/{id}
|
|
*/
|
|
public function post(string $route, callable $callback, array $permissions = []): void
|
|
{
|
|
$this->registerRoute($route, 'POST', $callback, $permissions);
|
|
}
|
|
|
|
/**
|
|
* PUT route
|
|
* @param string $route Example: /home, /home/{id}
|
|
*/
|
|
public function put(string $route, callable $callback, array $permissions = []): void
|
|
{
|
|
$this->registerRoute($route, 'PUT', $callback, $permissions);
|
|
}
|
|
|
|
/**
|
|
* DELETE route
|
|
* @param string $route Example: /home, /home/{id}
|
|
*/
|
|
public function delete(string $route, callable $callback, array $permissions = []): void
|
|
{
|
|
$this->registerRoute($route, 'DELETE', $callback, $permissions);
|
|
}
|
|
|
|
/**
|
|
* OPTIONS route
|
|
* @param string $route Example: /home, /home/{id}
|
|
*/
|
|
public function options(string $route, callable $callback, array $permissions = []): void
|
|
{
|
|
$this->registerRoute($route, 'OPTIONS', $callback, $permissions);
|
|
}
|
|
|
|
/**
|
|
* PATCH route
|
|
* @param string $route Example: /home, /home/{id}
|
|
*/
|
|
public function patch(string $route, callable $callback, array $permissions = []): void
|
|
{
|
|
$this->registerRoute($route, 'PATCH', $callback, $permissions);
|
|
}
|
|
|
|
/**
|
|
* Get the parameter from the route URL by index
|
|
* @param string $index
|
|
* @return string|null
|
|
*/
|
|
public function fromRoute(string $index): ?string
|
|
{
|
|
$params = explode('/', $this->route);
|
|
$index = array_search($index, $params);
|
|
return $params[$index] ?? null;
|
|
}
|
|
|
|
/**
|
|
* Require plate scanner authentication
|
|
* @return bool
|
|
*/
|
|
|
|
public function requirePlateScannerAuth(): bool
|
|
{
|
|
global $response;
|
|
// Check if the plate scanner has a valid API key
|
|
try {
|
|
$plate_scanner = (new authentication())->get_plate_scanner();
|
|
if (!$plate_scanner) {
|
|
(new logs_o())->add('global', 'global', 1, 0, 'AUTHENTICATION_FAILED', 'Authentication failed. Invalid, or missing API key');
|
|
$response->error('Authentication failed. Invalid or missing API key.', 401);
|
|
}
|
|
} catch (Exception $e) {
|
|
$response->error($e->getMessage(), 400);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Require order access
|
|
* @param int $order_id The order ID
|
|
* @param string|null $permission The permission to check
|
|
* @return bool
|
|
*/
|
|
public function requireOrderAccess(int $order_id, string|null $permission = null): bool
|
|
{
|
|
// TODO: Implement indirect order access, where the user has access to the department, and the department has access to the order
|
|
self::requirePermission('order_access_' . $order_id . ($permission ? '_' . $permission : ''));
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Require reCAPTCHA for the request
|
|
* @return bool
|
|
*/
|
|
public function requireRecaptcha(): bool
|
|
{
|
|
global $response;
|
|
// Check if the reCAPTCHA is valid
|
|
try {
|
|
$recaptcha_response = $response->getRequestParameter('g_recaptcha_response');
|
|
$recaptcha = (new recaptcha())->validate($recaptcha_response);
|
|
if (!$recaptcha) {
|
|
(new logs_o())->add('global', 'global', 1, 0, 'AUTHENTICATION_FAILED', 'Authentication failed. Invalid, or missing reCAPTCHA');
|
|
$response->error('Authentication failed. Invalid or missing reCAPTCHA.', 401);
|
|
}
|
|
} catch (Exception $e) {
|
|
$response->error($e->getMessage(), 400);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Get data from the request body or query string by name
|
|
* @param string $name
|
|
* @return string|null
|
|
*/
|
|
public function fromRequest(string $name): ?string
|
|
{
|
|
// If the $_POST variable is set, return the value from the POST variable, otherwise return the value from the query string
|
|
if (isset($_POST)) {
|
|
$data = json_decode(file_get_contents('php://input'), true);
|
|
if (isset($data[$name])) {
|
|
return $data[$name];
|
|
}
|
|
}
|
|
return (isset($_POST[$name])) ? $_POST[$name] : $this->fromQuery($name);
|
|
}
|
|
|
|
/**
|
|
* Get the parameter from the query string by name
|
|
* @param string $name
|
|
* @return string|null
|
|
*/
|
|
public function fromQuery(string $name): ?string
|
|
{
|
|
return (isset($_GET[$name])) ? $_GET[$name] : null;
|
|
}
|
|
|
|
/**
|
|
* Match route
|
|
* @param string $route Example: /home, /home/{id}
|
|
*/
|
|
private function match_route(string $route): bool
|
|
{
|
|
// Check if route is the same, or if it matches the regex pattern
|
|
return $route === $this->route || preg_match($route, $this->route);
|
|
}
|
|
} |