244 lines
11 KiB
PHP
244 lines
11 KiB
PHP
<?php
|
|
|
|
namespace App\Providers;
|
|
|
|
use App\Auth\DockerSessionGuard;
|
|
use App\Models\Bank;
|
|
use App\Policies\BankPolicy;
|
|
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\Gate;
|
|
|
|
class AuthServiceProvider extends ServiceProvider
|
|
{
|
|
/**
|
|
* Register any authentication / authorization services.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function boot()
|
|
{
|
|
$this->registerPolicies();
|
|
|
|
// Set remember me duration from platform configuration
|
|
$rememberMeDays = timebank_config('auth.remember_me_days', 90);
|
|
$rememberMeMinutes = $rememberMeDays * 24 * 60; // Convert days to minutes
|
|
|
|
// Use custom guard in Docker that doesn't migrate sessions
|
|
if (env('IS_DOCKER', false)) {
|
|
Auth::extend('session', function ($app, $name, array $config) use ($rememberMeMinutes) {
|
|
$provider = Auth::createUserProvider($config['provider']);
|
|
$guard = new DockerSessionGuard($name, $provider, $app['session.store']);
|
|
|
|
// Set the cookie jar on the guard
|
|
$guard->setCookieJar($app['cookie']);
|
|
|
|
// If a request is available, set it on the guard
|
|
if (method_exists($guard, 'setRequest')) {
|
|
$guard->setRequest($app->refresh('request', $guard, 'setRequest'));
|
|
}
|
|
|
|
// Set remember me duration
|
|
$guard->setRememberDuration($rememberMeMinutes);
|
|
|
|
return $guard;
|
|
});
|
|
}
|
|
|
|
// Set remember me duration for all standard guards
|
|
foreach (['web', 'organization', 'bank', 'admin'] as $guardName) {
|
|
$guard = Auth::guard($guardName);
|
|
if (method_exists($guard, 'setRememberDuration')) {
|
|
$guard->setRememberDuration($rememberMeMinutes);
|
|
}
|
|
}
|
|
|
|
// All permissions are strictly scoped to the active profile session.
|
|
//
|
|
// Rules:
|
|
// - Active profile = Admin{id}: only the scoped role Admin\{id}\{roleName}
|
|
// determines permissions. Roles from other linked admin profiles are ignored.
|
|
// - Active profile = Organization/Bank: only scoped Org/Bank roles apply.
|
|
// Admin-level permissions are NOT accessible when acting as Org/Bank.
|
|
// - Active profile = User or null/unknown: no admin permissions granted.
|
|
|
|
// Shared factory: returns a Gate closure that checks $permission on the active
|
|
// profile's single scoped role. Used for both manage and granular verbs.
|
|
$makeGate = function (string $permission) {
|
|
return function ($user) use ($permission) {
|
|
// Roles live on the User model (web guard). When the active guard is
|
|
// 'admin' (or another profile guard), Gate receives an Admin/Bank/Org
|
|
// model instead. Always resolve the web user explicitly.
|
|
if (!($user instanceof \App\Models\User)) {
|
|
$user = \Illuminate\Support\Facades\Auth::guard('web')->user();
|
|
if (!($user instanceof \App\Models\User)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
try {
|
|
$activeType = session('activeProfileType');
|
|
$activeId = session('activeProfileId');
|
|
|
|
if (!$activeType || !$activeId) {
|
|
return false;
|
|
}
|
|
|
|
$prefixMap = [
|
|
'Admin' => "Admin\\{$activeId}\\",
|
|
'Organization' => "Organization\\{$activeId}\\",
|
|
'Bank' => "Bank\\{$activeId}\\",
|
|
];
|
|
|
|
$activeClass = class_basename($activeType);
|
|
$prefix = $prefixMap[$activeClass] ?? null;
|
|
|
|
if (!$prefix) {
|
|
return false; // User profile or unknown: no permissions
|
|
}
|
|
|
|
$scopedRoles = $user->roles
|
|
->filter(fn($r) => str_starts_with($r->name, $prefix));
|
|
|
|
if ($scopedRoles->isEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
// Grant if any of the user's scoped roles for this profile has the permission.
|
|
// Also honour the manage→verb implication for granular checks.
|
|
$managePermission = null;
|
|
$parts = explode(' ', $permission, 2);
|
|
if (count($parts) === 2 && $parts[0] !== 'manage') {
|
|
$managePermission = 'manage ' . $parts[1];
|
|
}
|
|
|
|
foreach ($scopedRoles as $scopedRole) {
|
|
try {
|
|
if ($scopedRole->hasPermissionTo($permission, 'web')) {
|
|
return true;
|
|
}
|
|
} catch (\Spatie\Permission\Exceptions\PermissionDoesNotExist $e) {
|
|
// permission doesn't exist in DB, skip
|
|
}
|
|
|
|
if ($managePermission) {
|
|
try {
|
|
if ($scopedRole->hasPermissionTo($managePermission, 'web')) {
|
|
return true;
|
|
}
|
|
} catch (\Spatie\Permission\Exceptions\PermissionDoesNotExist $e) {
|
|
// manage permission doesn't exist, skip
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
|
|
} catch (\Spatie\Permission\Exceptions\PermissionDoesNotExist $e) {
|
|
return false;
|
|
} catch (\Exception $e) {
|
|
return false;
|
|
}
|
|
};
|
|
};
|
|
|
|
// manage {resource} — umbrella permission (checked by route middleware + mount())
|
|
foreach ([
|
|
'banks', 'organizations', 'admins', 'calls', 'posts', 'polls', 'tags',
|
|
'categories', 'mailings', 'profiles', 'roles', 'permissions',
|
|
'users', 'user_projects', 'accounts', 'maintenance',
|
|
] as $resource) {
|
|
Gate::define("manage {$resource}", $makeGate("manage {$resource}"));
|
|
}
|
|
|
|
// Granular action Gates — checked inside individual Livewire methods and blade views.
|
|
// Each resource lists only the verbs that make sense for it.
|
|
$granular = [
|
|
'posts' => ['view', 'create', 'update', 'publish', 'unpublish', 'delete'],
|
|
// Polls: 'export' covers who voted (Results tab) and the CSV export
|
|
'polls' => ['view', 'create', 'update', 'export', 'delete'],
|
|
'tags' => ['view', 'create', 'update', 'delete'],
|
|
'categories' => ['view', 'create', 'update', 'delete'],
|
|
'calls' => ['view', 'create', 'update', 'publish', 'unpublish', 'delete'],
|
|
'mailings' => ['view', 'create', 'update', 'send', 'delete'],
|
|
'users' => ['view', 'create', 'update', 'delete'],
|
|
'user_projects' => ['view', 'create', 'update', 'delete'],
|
|
'organizations' => ['view', 'create', 'update', 'delete'],
|
|
'banks' => ['view', 'create', 'update', 'delete'],
|
|
'admins' => ['view', 'create', 'update', 'delete'],
|
|
'accounts' => ['view', 'create', 'update', 'delete'],
|
|
'profiles' => ['view', 'create', 'update', 'delete'],
|
|
'permissions' => ['view', 'create', 'update', 'delete'],
|
|
'roles' => ['view', 'create', 'update', 'delete'],
|
|
'search-metrics' => ['view'],
|
|
'application-log' => ['view'],
|
|
'inactive-profiles-log' => ['view'],
|
|
'search-reindex-log' => ['view'],
|
|
'stats' => ['view'],
|
|
];
|
|
|
|
// Org/bank capability gates — checked inside org-context Livewire components.
|
|
foreach (['pay', 'transactions', 'reports', 'stats', 'contacts', 'calls', 'messenger', 'edit-profile', 'settings', 'change-password'] as $cap) {
|
|
Gate::define("org {$cap}", $makeGate("org {$cap}"));
|
|
}
|
|
|
|
// Admin capability gates — checked inside admin-context components.
|
|
foreach (['reports', 'contacts', 'messenger', 'settings', 'change-password'] as $cap) {
|
|
Gate::define("admin {$cap}", $makeGate("admin {$cap}"));
|
|
}
|
|
|
|
foreach ($granular as $resource => $verbs) {
|
|
foreach ($verbs as $verb) {
|
|
Gate::define("{$verb} {$resource}", $makeGate("{$verb} {$resource}"));
|
|
}
|
|
}
|
|
|
|
// Regular users can always manage their own calls — no role/permission needed.
|
|
// Ownership enforcement is done at the component level (query scoped to own calls).
|
|
$userCallVerbs = ['create', 'update', 'publish', 'unpublish', 'delete'];
|
|
foreach ($userCallVerbs as $verb) {
|
|
Gate::define("{$verb} calls", function ($user) use ($verb, $makeGate) {
|
|
$activeType = session('activeProfileType');
|
|
if ($activeType && class_basename($activeType) === 'User') {
|
|
return true;
|
|
}
|
|
return $makeGate("{$verb} calls")($user);
|
|
});
|
|
}
|
|
|
|
// access {resource} — page-level gate used on routes.
|
|
// 'view {resource}' grants read-only page entry; 'manage {resource}' grants full access.
|
|
// Roles without any permission for a resource cannot reach the page.
|
|
$accessResources = array_keys($granular);
|
|
foreach ($accessResources as $resource) {
|
|
Gate::define("access {$resource}", function ($user) use ($resource, $makeGate) {
|
|
return $makeGate("manage {$resource}")($user) || $makeGate("view {$resource}")($user);
|
|
});
|
|
}
|
|
|
|
// Route-level gate for the scoped bank-manager "manage accounts" surface
|
|
// (Profiles\Manage.php, non-central banks). Bank-scoped roles created via
|
|
// Roles\Manage.php only ever carry 'org *' style capability permissions —
|
|
// never the Admin-style 'manage accounts' permission the granular gates
|
|
// above check for — so this checks bank-manager role membership directly,
|
|
// the same convention getCanCreatePayments() already uses for banks.
|
|
Gate::define('bank manage accounts', function ($user) {
|
|
if (!($user instanceof \App\Models\User)) {
|
|
$user = \Illuminate\Support\Facades\Auth::guard('web')->user();
|
|
if (!($user instanceof \App\Models\User)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
$activeType = session('activeProfileType');
|
|
$activeId = session('activeProfileId');
|
|
|
|
if ($activeType !== \App\Models\Bank::class || !$activeId) {
|
|
return false;
|
|
}
|
|
|
|
return $user->hasRole("Bank\\{$activeId}\\bank-manager");
|
|
});
|
|
}
|
|
}
|