FEAT_RBAC

This commit is contained in:
velz 2026-01-09 16:02:46 +05:30
parent 574f9963d1
commit 375d0b132e
7 changed files with 255 additions and 5 deletions

View File

@ -183,3 +183,5 @@ CORS_MAX_AGE=7200
CORS_DEBUG=true
APP_SIGNATURE =
TOKENTIMEOUT =
JWT_SECRET =

View File

@ -7,7 +7,7 @@ Options -Indexes
## ADDED for - block any script execution inside folder of public
<If "%{REQUEST_URI} =~ m#/(logo|add_image_upload|e_card_imgs|assets|claim_sample_forms|sample_import_excel|writable)/#">
<If "%{REQUEST_URI} =~ m#/(logo|add_image_upload|e_card_imgs|claim_sample_forms|sample_import_excel|writable)/#">
Deny from all
# Disable PHP engine
<IfModule mod_php.c>

85
app/Config/Acl.php Normal file
View File

@ -0,0 +1,85 @@
<?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' => [ HEAD_ROLE_ID,ADMIN_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' => []
],
// ===================== 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],
// ===================== CLI =====================
'#^/cli/#' => ['public' => true],
// ===================== DEFAULT DENY (ZERO TRUST) =====================
'#^/#' => [
'roles' => [ADMIN_ROLE_ID],
'teams' => []
],
];
}

View File

@ -18,6 +18,9 @@ use App\Filters\AuthJWT;
use App\Filters\Cors;
use App\Filters\GlobalPostFileUploadGuard;
use App\Filters\SecurityInputFilter;
use App\Filters\AclFilter;
class Filters extends BaseConfig
{
@ -43,6 +46,7 @@ class Filters extends BaseConfig
'appSignature' => VerifyAppSignature::class,
'GlobalPostFileUploadGuard' => GlobalPostFileUploadGuard::class,
'SecurityInputFilter' => SecurityInputFilter::class,
'AclFilter' => AclFilter::class,
];
@ -56,6 +60,7 @@ class Filters extends BaseConfig
public array $globals = [
'before' => [
'HttpRequestLog' => ['except' => 'cli/*'],
'AclFilter' => ['except' => 'login', 'logout', 'auth/*', 'oauth2callback', 'download-*', 'claim-form-download', 'claims-feedback-form', 'autobookstackLogin'],
'Cors',
'SecurityInputFilter',
'GlobalPostFileUploadGuard',

View File

@ -31,20 +31,22 @@ class LoginController extends BaseController
public function receiveGoogleOAuthResponse()
{
$UserModel = new UserModel();
//echo 'DONE';die();
// echo 'DONE';die();
if ($this->request->getGet('code')) {
$code = (string) $this->request->getGet('code');
log_message('error', 'Get OAuth Responce Code Sucessfully');
log_message('error', 'OAuthResponceCode : `'.$code.'`');
$value = googleOAuthLogin($this->request->getGet('code'));
// log_message('error', "OAuthResponceCode :" . json_encode($value));
if($value){
// print_r($value);//die;
$user = $UserModel->getUserByEmail($value->email);
if($user){
if($user->is_active !== '0'){
$user_team = $UserModel->getUserTeamsByUserID($user->id);
// dd($user_team);
session()->regenerate(true);
$session_data = [
'isLoggedIn' => True ,
'userid' => $user->id,
@ -66,7 +68,7 @@ class LoginController extends BaseController
log_message('error', 'User Login Sucessfully');
$this->getUserDeviceInfo($user->id, 'NhanceUser');
return AuthLogout::logout();
return redirect()->to(base_url('/dashboard/view'));
}else{
log_message('error', 'User Not Active');

156
app/Filters/AclFilter.php Normal file
View 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');
}
}

View File

@ -22,7 +22,7 @@ class AuthLogout
setcookie(
session_name(), // DO NOT hardcode cookie name
'',
null,
time() - 42000,
$params['path'],
$params['domain'],