FEAT_RBAC
This commit is contained in:
parent
29417e9438
commit
b078bfe837
@ -108,3 +108,5 @@ CORS_MAX_AGE=7200
|
|||||||
CORS_DEBUG=true
|
CORS_DEBUG=true
|
||||||
|
|
||||||
APP_SIGNATURE =
|
APP_SIGNATURE =
|
||||||
|
TOKENTIMEOUT =
|
||||||
|
JWT_SECRET =
|
||||||
157
app/Config/Acl.php
Normal file
157
app/Config/Acl.php
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Config;
|
||||||
|
|
||||||
|
class Acl
|
||||||
|
{
|
||||||
|
public array $rules = [
|
||||||
|
|
||||||
|
// ===================== PUBLIC / AUTH =====================
|
||||||
|
'#^/login#' => ['public' => true],
|
||||||
|
'#^/logout#' => ['public' => true],
|
||||||
|
'#^/auth#' => ['public' => true],
|
||||||
|
'#^/oauth2callback#' => ['public' => true],
|
||||||
|
'#^/loginPos#' => ['public' => true],
|
||||||
|
'#^/getVerifyPosMobileNo#' => ['public' => true],
|
||||||
|
'#^/getVerifiedPosUserData#' => ['public' => true],
|
||||||
|
'#^/swagger#' => ['roles' => [ADMIN_ROLE_ID]],
|
||||||
|
'#^/fedeploy#' => ['roles' => [ADMIN_ROLE_ID]],
|
||||||
|
|
||||||
|
// ===================== PUBLIC DOWNLOADS / FORMS =====================
|
||||||
|
'#^/download-#' => ['public' => true],
|
||||||
|
'#^/claim-form-download#' => ['public' => true],
|
||||||
|
'#^/claims-feedback-form#' => ['public' => true],
|
||||||
|
'#^/autobookstackLogin#' => ['public' => true],
|
||||||
|
|
||||||
|
// ===================== DASHBOARD =====================
|
||||||
|
'#^/dashboard#' => [
|
||||||
|
'roles' => [ ADMIN_ROLE_ID,HEAD_ROLE_ID, MANAGER_ROLE_ID, STAFF_ROLE_ID, ACCOUNT_MANAGER_ROLE_ID],
|
||||||
|
'teams' => []
|
||||||
|
],
|
||||||
|
|
||||||
|
// ===================== USER MANAGEMENT =====================
|
||||||
|
'#^/user#' => [
|
||||||
|
'roles' => [ HEAD_ROLE_ID,ADMIN_ROLE_ID],
|
||||||
|
'teams' => []
|
||||||
|
],
|
||||||
|
|
||||||
|
// ===================== CLIENT =====================
|
||||||
|
'#^/client#' => [
|
||||||
|
'roles' => [ HEAD_ROLE_ID,ADMIN_ROLE_ID, MANAGER_ROLE_ID, ACCOUNT_MANAGER_ROLE_ID],
|
||||||
|
'teams' => []
|
||||||
|
],
|
||||||
|
|
||||||
|
// ===================== EMPLOYEE / ENROLLMENT =====================
|
||||||
|
'#^/employee#' => [
|
||||||
|
'roles' => [ HEAD_ROLE_ID,ADMIN_ROLE_ID, MANAGER_ROLE_ID, ACCOUNT_MANAGER_ROLE_ID],
|
||||||
|
'teams' => [ENROLLMENT_TEAM_ID]
|
||||||
|
],
|
||||||
|
|
||||||
|
// ===================== MASTERS =====================
|
||||||
|
'#^/master#' => [
|
||||||
|
'roles' => [ HEAD_ROLE_ID,ADMIN_ROLE_ID, MANAGER_ROLE_ID, ACCOUNT_MANAGER_ROLE_ID],
|
||||||
|
'teams' => []
|
||||||
|
],
|
||||||
|
'#^/util#' => [
|
||||||
|
'roles' => [ HEAD_ROLE_ID,ADMIN_ROLE_ID, MANAGER_ROLE_ID, ACCOUNT_MANAGER_ROLE_ID,STAFF_ROLE_ID],
|
||||||
|
'teams' => []
|
||||||
|
],
|
||||||
|
|
||||||
|
// ===================== POLICY TRANSACTION / BDS =====================
|
||||||
|
'#^/policy_tranction#' => [
|
||||||
|
'roles' => [ HEAD_ROLE_ID,ADMIN_ROLE_ID, MANAGER_ROLE_ID, ACCOUNT_MANAGER_ROLE_ID],
|
||||||
|
'teams' => [FINANCE_TEAM_ID,POS_TEAM_ID]
|
||||||
|
],
|
||||||
|
'#^/bds_upload#' => [
|
||||||
|
'roles' => [ HEAD_ROLE_ID,ADMIN_ROLE_ID, MANAGER_ROLE_ID, ACCOUNT_MANAGER_ROLE_ID],
|
||||||
|
'teams' => [FINANCE_TEAM_ID,POS_TEAM_ID]
|
||||||
|
],
|
||||||
|
|
||||||
|
// ===================== REPORTS =====================
|
||||||
|
'#^/bdsReport#' => [
|
||||||
|
'roles' => [ HEAD_ROLE_ID,ADMIN_ROLE_ID, MANAGER_ROLE_ID, ACCOUNT_MANAGER_ROLE_ID],
|
||||||
|
'teams' => [FINANCE_TEAM_ID,POS_TEAM_ID]
|
||||||
|
],
|
||||||
|
|
||||||
|
// ===================== PAYOUT / COMMISSION =====================
|
||||||
|
'#^/payout#' => [
|
||||||
|
'roles' => [ HEAD_ROLE_ID,ADMIN_ROLE_ID, MANAGER_ROLE_ID, ACCOUNT_MANAGER_ROLE_ID],
|
||||||
|
'teams' => [FINANCE_TEAM_ID,POS_TEAM_ID]
|
||||||
|
],
|
||||||
|
'#^/commission#' => [
|
||||||
|
'roles' => [ HEAD_ROLE_ID,ADMIN_ROLE_ID, MANAGER_ROLE_ID, ACCOUNT_MANAGER_ROLE_ID],
|
||||||
|
'teams' => [FINANCE_TEAM_ID,POS_TEAM_ID]
|
||||||
|
],
|
||||||
|
|
||||||
|
// ===================== CLAIMS / TICKETS =====================
|
||||||
|
'#^/ticket#' => [
|
||||||
|
'roles' => [ HEAD_ROLE_ID,ADMIN_ROLE_ID, MANAGER_ROLE_ID, ACCOUNT_MANAGER_ROLE_ID],
|
||||||
|
'teams' => [CLAIMS_TEAM_ID]
|
||||||
|
],
|
||||||
|
'#^/claim_mis#' => [
|
||||||
|
'roles' => [ HEAD_ROLE_ID,ADMIN_ROLE_ID, MANAGER_ROLE_ID, ACCOUNT_MANAGER_ROLE_ID],
|
||||||
|
'teams' => [CLAIMS_TEAM_ID]
|
||||||
|
],
|
||||||
|
|
||||||
|
// ===================== LEADS / SALES =====================
|
||||||
|
'#^/leads#' => [
|
||||||
|
'roles' => [ HEAD_ROLE_ID,ADMIN_ROLE_ID, MANAGER_ROLE_ID, ACCOUNT_MANAGER_ROLE_ID],
|
||||||
|
'teams' => [BUSINESS_SUPPORT_TEAM_ID, SALES_TEAM_ID]
|
||||||
|
],
|
||||||
|
'#^/rfq#' => [
|
||||||
|
'roles' => [ HEAD_ROLE_ID,ADMIN_ROLE_ID, MANAGER_ROLE_ID, ACCOUNT_MANAGER_ROLE_ID],
|
||||||
|
'teams' => [SALES_TEAM_ID]
|
||||||
|
],
|
||||||
|
'#^/sales#' => [
|
||||||
|
'roles' => [ HEAD_ROLE_ID,ADMIN_ROLE_ID, MANAGER_ROLE_ID],
|
||||||
|
'teams' => [SALES_TEAM_ID]
|
||||||
|
],
|
||||||
|
|
||||||
|
// ===================== CMS / CONTENT =====================
|
||||||
|
'#^/add_image_index#' => [
|
||||||
|
'roles' => [ HEAD_ROLE_ID,ADMIN_ROLE_ID, MANAGER_ROLE_ID, ACCOUNT_MANAGER_ROLE_ID,STAFF_ROLE_ID],
|
||||||
|
'teams' => []
|
||||||
|
],
|
||||||
|
'#^/frontend_content#' => [
|
||||||
|
'roles' => [ HEAD_ROLE_ID,ADMIN_ROLE_ID, MANAGER_ROLE_ID, ACCOUNT_MANAGER_ROLE_ID,STAFF_ROLE_ID],
|
||||||
|
'teams' => []
|
||||||
|
],
|
||||||
|
'#^/FAQ#' => [
|
||||||
|
'roles' => [ HEAD_ROLE_ID,ADMIN_ROLE_ID, MANAGER_ROLE_ID, ACCOUNT_MANAGER_ROLE_ID,STAFF_ROLE_ID],
|
||||||
|
'teams' => []
|
||||||
|
],
|
||||||
|
|
||||||
|
// ===================== LOGS =====================
|
||||||
|
'#^/logs#' => [
|
||||||
|
'roles' => [ADMIN_ROLE_ID],
|
||||||
|
'teams' => []
|
||||||
|
],
|
||||||
|
|
||||||
|
// ===================== INTERNAL TEST =====================
|
||||||
|
'#^/test#' => [
|
||||||
|
'roles' => [ADMIN_ROLE_ID],
|
||||||
|
'teams' => []
|
||||||
|
],
|
||||||
|
|
||||||
|
// ===================== API (JWT / SIGNED) =====================
|
||||||
|
'#^/api#' => ['public' => true],
|
||||||
|
'#^/employeeRest#' => ['public' => true],
|
||||||
|
'#^/clientApi#' => ['public' => true],
|
||||||
|
|
||||||
|
// ===================== WEBHOOKS / 3RD PARTY =====================
|
||||||
|
'#^/dispatchWebhookData#' => ['public' => true],
|
||||||
|
'#^/retrieveWebhookData#' => ['public' => true],
|
||||||
|
// '#^/ICICI#' => ['public' => true],
|
||||||
|
// '#^/Vidal#' => ['public' => true],
|
||||||
|
// '#^/MediAssist#' => ['public' => true],
|
||||||
|
|
||||||
|
// ===================== CLI =====================
|
||||||
|
'#^/cli/#' => ['public' => true],
|
||||||
|
|
||||||
|
// ===================== DEFAULT DENY (ZERO TRUST) =====================
|
||||||
|
'#^/#' => [
|
||||||
|
'roles' => [ADMIN_ROLE_ID],
|
||||||
|
'teams' => []
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
@ -18,6 +18,7 @@ use App\Filters\VerifyAppSignature;
|
|||||||
use App\Filters\Cors;
|
use App\Filters\Cors;
|
||||||
use App\Filters\SecurityInputFilter;
|
use App\Filters\SecurityInputFilter;
|
||||||
use App\Filters\GlobalPostFileUploadGuard;
|
use App\Filters\GlobalPostFileUploadGuard;
|
||||||
|
use App\Filters\AclFilter;
|
||||||
|
|
||||||
use App\Filters\AuthJWT;
|
use App\Filters\AuthJWT;
|
||||||
|
|
||||||
@ -47,6 +48,7 @@ class Filters extends BaseConfig
|
|||||||
'Cors' => Cors::class,
|
'Cors' => Cors::class,
|
||||||
'SecurityInputFilter' => SecurityInputFilter::class,
|
'SecurityInputFilter' => SecurityInputFilter::class,
|
||||||
'GlobalPostFileUploadGuard' => GlobalPostFileUploadGuard::class,
|
'GlobalPostFileUploadGuard' => GlobalPostFileUploadGuard::class,
|
||||||
|
'AclFilter' => AclFilter::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -60,6 +62,7 @@ class Filters extends BaseConfig
|
|||||||
'before' => [
|
'before' => [
|
||||||
'HttpRequestLog' => ['except' => 'cli/*'],
|
'HttpRequestLog' => ['except' => 'cli/*'],
|
||||||
'Cors',
|
'Cors',
|
||||||
|
'AclFilter' => ['except' => 'login', 'logout', 'auth/*', 'oauth2callback', 'download-*', 'claim-form-download', 'claims-feedback-form', 'autobookstackLogin'],
|
||||||
'SecurityInputFilter',
|
'SecurityInputFilter',
|
||||||
'GlobalPostFileUploadGuard'
|
'GlobalPostFileUploadGuard'
|
||||||
// 'csrf',
|
// 'csrf',
|
||||||
|
|||||||
156
app/Filters/AclFilter.php
Normal file
156
app/Filters/AclFilter.php
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filters;
|
||||||
|
|
||||||
|
use CodeIgniter\HTTP\RequestInterface;
|
||||||
|
use CodeIgniter\HTTP\ResponseInterface;
|
||||||
|
use CodeIgniter\Filters\FilterInterface;
|
||||||
|
use Config\Acl;
|
||||||
|
|
||||||
|
use App\Libraries\AuthLogout;
|
||||||
|
|
||||||
|
class AclFilter implements FilterInterface
|
||||||
|
{
|
||||||
|
public function before(RequestInterface $request, $arguments = null)
|
||||||
|
{
|
||||||
|
// ===================== CLI BYPASS =====================
|
||||||
|
if (is_cli()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ===================== PATH NORMALIZATION =====================
|
||||||
|
$uri = service('uri');
|
||||||
|
|
||||||
|
// Raw path: /PHP828APPS/ruc/nhance/index.php/dashboard/view
|
||||||
|
$fullPath = '/' . ltrim($uri->getPath(), '/');
|
||||||
|
|
||||||
|
// Base path: /PHP828APPS/ruc/nhance
|
||||||
|
$basePath = rtrim(parse_url(base_url(), PHP_URL_PATH), '/');
|
||||||
|
|
||||||
|
// Remove base path
|
||||||
|
if ($basePath && str_starts_with($fullPath, $basePath)) {
|
||||||
|
$path = substr($fullPath, strlen($basePath));
|
||||||
|
} else {
|
||||||
|
$path = $fullPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove index.php if present
|
||||||
|
if (str_starts_with($path, '/index.php')) {
|
||||||
|
$path = substr($path, strlen('/index.php'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize
|
||||||
|
$path = '/' . ltrim($path, '/');
|
||||||
|
|
||||||
|
// Fallback
|
||||||
|
if ($path === '') {
|
||||||
|
$path = '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
// echo'<br>BASH PATH: ' . base_url();
|
||||||
|
// echo'<br>ACL RAW PATH: ' . $fullPath;
|
||||||
|
// echo'<br>ACL BASE PATH: ' . $basePath;
|
||||||
|
// echo'<br>ACL FINAL PATH: ' . $path;
|
||||||
|
// ===================== LOAD ACL =====================
|
||||||
|
$acl = new Acl();
|
||||||
|
$rules = $acl->rules;
|
||||||
|
// print_rr($rules);die;
|
||||||
|
// ===================== MATCH RULE =====================
|
||||||
|
$matchedRule = null;
|
||||||
|
|
||||||
|
foreach ($rules as $pattern => $rule) {
|
||||||
|
// echo "$pattern".'---------<br>';
|
||||||
|
if (preg_match($pattern, $path)) {
|
||||||
|
// echo "matched - $pattern";
|
||||||
|
$matchedRule = $rule;
|
||||||
|
break; // FIRST MATCH WINS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// print_r($matchedRule);//die;
|
||||||
|
// ===================== NO RULE = DENY =====================
|
||||||
|
if ($matchedRule === null) {
|
||||||
|
return $this->deny($path, 'No ACL rule matched');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================== PUBLIC ROUTE =====================
|
||||||
|
if (!empty($matchedRule['public'])) {
|
||||||
|
return; // ALLOW
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================== AUTH CHECK =====================
|
||||||
|
if (!check_session()) {
|
||||||
|
// For API requests return 401 JSON
|
||||||
|
if ($request->isAJAX() || str_starts_with($path, '/api') || str_starts_with($path, '/employeeRest')) {
|
||||||
|
return service('response')
|
||||||
|
->setStatusCode(401)
|
||||||
|
->setJSON(['error' => 'Unauthorized']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For web redirect to login
|
||||||
|
return AuthLogout::logout();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================== GET USER CONTEXT =====================
|
||||||
|
|
||||||
|
$userRole = check_role(); //
|
||||||
|
$userTeams = user_team(); // must return array of TEAM IDs
|
||||||
|
|
||||||
|
$allowedRoles = $matchedRule['roles'] ?? [];
|
||||||
|
$allowedTeams = $matchedRule['teams'] ?? [];
|
||||||
|
// ===================== ROLE FIRST =====================
|
||||||
|
if (!empty($allowedRoles) && in_array((int)$userRole, $allowedRoles, true)) {
|
||||||
|
return; // ALLOW
|
||||||
|
}
|
||||||
|
// ===================== TEAM FALLBACK =====================
|
||||||
|
if (!empty($allowedTeams) && is_array($userTeams)) {
|
||||||
|
foreach ($userTeams as $teamId) {
|
||||||
|
if (in_array($teamId, $allowedTeams, true)) {
|
||||||
|
return; // ALLOW
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ===================== DENY =====================
|
||||||
|
return $this->deny($path, 'Role/Team not permitted');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
|
||||||
|
{
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================== DENY HANDLER =====================
|
||||||
|
protected function deny(string $path, string $reason)
|
||||||
|
{
|
||||||
|
log_message('error', 'ACL BLOCKED: {user} {path} - {reason}', [
|
||||||
|
'user' => session()->get('userid') ?? 'guest',
|
||||||
|
'path' => $path,
|
||||||
|
'reason' => $reason,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// API / AJAX → JSON
|
||||||
|
$request = service('request');
|
||||||
|
if ($request->isAJAX() || str_starts_with($path, '/api') || str_starts_with($path, '/employeeRest')) {
|
||||||
|
return service('response')
|
||||||
|
->setStatusCode(403)
|
||||||
|
->setJSON([
|
||||||
|
'error' => 'Forbidden',
|
||||||
|
'message' => 'You do not have permission to access this resource'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = service('response');
|
||||||
|
$response->setStatusCode(403);
|
||||||
|
$response->setBody(view('errors/404', [
|
||||||
|
'message' => '403 Access denied - You do not have permission to access this resource'
|
||||||
|
]));
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
// Web → nice 403 page or simple text
|
||||||
|
return service('response')
|
||||||
|
->setStatusCode(403)
|
||||||
|
->setBody('403 Forbidden - Access denied - You do not have permission to access this resource');
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -118,7 +118,8 @@ if (!function_exists('check_role')) {
|
|||||||
{
|
{
|
||||||
// $ci =& get_instance();
|
// $ci =& get_instance();
|
||||||
$session = \Config\Services::session();
|
$session = \Config\Services::session();
|
||||||
return $session->get('role');
|
return $role_id = isset(get_session_userdata()->role) ? get_session_userdata()->role : null;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -22,7 +22,7 @@ class AuthLogout
|
|||||||
|
|
||||||
setcookie(
|
setcookie(
|
||||||
session_name(), // DO NOT hardcode cookie name
|
session_name(), // DO NOT hardcode cookie name
|
||||||
'',
|
null,
|
||||||
time() - 42000,
|
time() - 42000,
|
||||||
$params['path'],
|
$params['path'],
|
||||||
$params['domain'],
|
$params['domain'],
|
||||||
|
|||||||
@ -162,7 +162,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<section>
|
<section>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="text">
|
<div class="text" style="text-align: center;">
|
||||||
<h1><?= isset($message) && !empty($message) ? $message : '404 PAGE NOT FOUND' ?></h1>
|
<h1><?= isset($message) && !empty($message) ? $message : '404 PAGE NOT FOUND' ?></h1>
|
||||||
</div>
|
</div>
|
||||||
<div><img class="image" src="https://omjsblog.files.wordpress.com/2023/07/errorimg.png" alt=""></div>
|
<div><img class="image" src="https://omjsblog.files.wordpress.com/2023/07/errorimg.png" alt=""></div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user