diff --git a/ProcessMaker/Http/Controllers/Api/TaskController.php b/ProcessMaker/Http/Controllers/Api/TaskController.php index cd4cb5fd1c..ee80c79975 100644 --- a/ProcessMaker/Http/Controllers/Api/TaskController.php +++ b/ProcessMaker/Http/Controllers/Api/TaskController.php @@ -146,15 +146,15 @@ public function index(Request $request, $getTotal = false, User $user = null) $this->applyAdvancedFilter($query, $request); - $this->applyForCurrentUser($query, $user); - - // Apply filter overdue - $query->overdue($request->input('overdue')); - if ($request->input('processesIManage') === 'true') { $this->applyProcessManager($query, $user); + } else { + $this->applyForCurrentUser($query, $user); } + // Apply filter overdue + $query->overdue($request->input('overdue')); + // If only the total is being requested (by a Saved Search), send it now if ($getTotal === true) { return $query->count(); @@ -168,6 +168,11 @@ public function index(Request $request, $getTotal = false, User $user = null) $response = $this->applyUserFilter($response, $request, $user); + if ($response->total() > 0 && $request->input('processesIManage') === 'true') { + // enable user manager in cache + $this->enableUserManager($user); + } + $inOverdueQuery = ProcessRequestToken::query() ->whereIn('id', $response->pluck('id')) ->where('due_at', '<', Carbon::now()); diff --git a/ProcessMaker/Http/Kernel.php b/ProcessMaker/Http/Kernel.php index f8a5888430..66cf5f2144 100644 --- a/ProcessMaker/Http/Kernel.php +++ b/ProcessMaker/Http/Kernel.php @@ -91,6 +91,7 @@ class Kernel extends HttpKernel 'session_kill' => Middleware\SessionControlKill::class, 'no-cache' => Middleware\NoCache::class, 'admin' => Middleware\IsAdmin::class, + 'manager' => Middleware\IsManager::class, 'etag' => Middleware\Etag\HandleEtag::class, 'file_size_check' => Middleware\FileSizeCheck::class, ]; diff --git a/ProcessMaker/Http/Middleware/IsManager.php b/ProcessMaker/Http/Middleware/IsManager.php new file mode 100644 index 0000000000..638072f2be --- /dev/null +++ b/ProcessMaker/Http/Middleware/IsManager.php @@ -0,0 +1,195 @@ +user(); + + if (!$user) { + return abort(401, 'Unauthenticated'); + } + + // if user is administrator, allow access + if ($user->is_administrator) { + return $next($request); + } + + if (!Cache::get("user_{$user->id}_manager")) { + // if user is not manager, continue + return $next($request); + } + + // get the required permissions for this specific URL + $requiredPermissions = $this->getRequiredPermissionsForRequest($request); + + if (empty($requiredPermissions)) { + // if no required permissions, continue + return $next($request); + } + + // simulate that the user has all the necessary permissions for this request + $this->simulateRequiredPermissionsForRequest($user, $requiredPermissions); + + try { + // process the request - the internal endpoints will handle the permission validation + $response = $next($request); + + // clean up the simulated permissions after processing the request + $this->cleanupSimulatedPermission($user); + + return $response; + } catch (\Exception $e) { + // make sure to clean up the simulated permissions even if there is an exception + $this->cleanupSimulatedPermission($user); + throw $e; + } + } + + /** + * Simula que el usuario tiene los permisos requeridos solo para esta solicitud + */ + private function simulateRequiredPermissionsForRequest($user, array $requiredPermissions) + { + try { + // get the current permissions of the user + $currentPermissions = $user->loadPermissions(); + + // filter only the permissions that the user does not have + $permissionsToAdd = array_diff($requiredPermissions, $currentPermissions); + + if (empty($permissionsToAdd)) { + return; + } + + // simulate the permissions by adding them temporarily to the cache of permissions + $cacheKey = "user_{$user->id}_permissions"; + $simulatedPermissions = array_merge($currentPermissions, $permissionsToAdd); + + // save in cache temporarily (only for this request) + // use a very short time to expire quickly if not cleaned manually + Cache::put($cacheKey, $simulatedPermissions, 5); // 5 segundos como fallback + } catch (\Exception $e) { + Log::error('IsManager middleware - Error simulating permissions: ' . $e->getMessage()); + } + } + + /** + * clean up the simulated permissions from the cache after processing the request + */ + private function cleanupSimulatedPermission($user) + { + try { + $cacheKey = "user_{$user->id}_permissions"; + + // delete the cache to force the reload of real permissions + Cache::forget($cacheKey); + } catch (\Exception $e) { + Log::error('IsManager middleware - Error cleaning up simulated permissions: ' . $e->getMessage()); + } + } + + /** + * get the required permissions for the current URL + */ + private function getRequiredPermissionsForRequest(Request $request): array + { + $permissions = []; + + try { + $url = $request->fullUrl(); + $path = $request->path(); + $method = $request->method(); + + // first, get permissions from middlewares of the route + $middlewarePermissions = $this->getPermissionsFromMiddlewares($request); + $permissions = array_merge($permissions, $middlewarePermissions); + + // then, get permissions based on URL patterns + $urlPermissions = $this->getPermissionsFromUrlPatterns($url, $path, $method); + $permissions = array_merge($permissions, $urlPermissions); + } catch (\Exception $e) { + Log::error('IsManager middleware - Error getting required permissions: ' . $e->getMessage()); + } + + return array_unique($permissions); + } + + /** + * get permissions from the middlewares of the route + */ + private function getPermissionsFromMiddlewares(Request $request): array + { + $permissions = []; + + try { + // get all the middlewares of the route + $middlewares = $request->route()->middleware(); + + // filter only the middlewares that contain 'can:' + $permissionMiddlewares = array_filter($middlewares, function ($middleware) { + return str_contains($middleware, 'can:'); + }); + + // extract the permissions from each middleware + foreach ($permissionMiddlewares as $middleware) { + // format: "can:permission" or "can:permission,model" + if (preg_match('/can:([^,]+)/', $middleware, $matches)) { + $permissions[] = $matches[1]; + } + } + } catch (\Exception $e) { + Log::error('IsManager middleware - Error getting permissions from middlewares: ' . $e->getMessage()); + } + + return $permissions; + } + + /** + * get permissions based on URL patterns + */ + private function getPermissionsFromUrlPatterns(string $url, string $path, string $method): array + { + $permissions = []; + + // for now we only support GET methods + if ($method !== 'GET') { + return $permissions; + } + + try { + // URL patterns and their corresponding permissions + $urlPatterns = [ + // patterns for users + '/api\/.*\/users(\?.*)?$/' => 'view-users', + + // patterns for saved searches + '/api\/.*\/saved-searches\/columns(\?.*)?$/' => 'view-saved-searches-columns', + ]; + + // check each pattern + foreach ($urlPatterns as $pattern => $permission) { + if (preg_match($pattern, $url)) { + $permissions[] = $permission; + } + } + } catch (\Exception $e) { + Log::error('IsManager middleware - Error getting permissions from URL patterns: ' . $e->getMessage()); + } + + return $permissions; + } +} diff --git a/ProcessMaker/Traits/TaskControllerIndexMethods.php b/ProcessMaker/Traits/TaskControllerIndexMethods.php index 0461c94ec1..849eaae2ef 100644 --- a/ProcessMaker/Traits/TaskControllerIndexMethods.php +++ b/ProcessMaker/Traits/TaskControllerIndexMethods.php @@ -6,6 +6,7 @@ use DB; use Illuminate\Database\QueryException; use Illuminate\Support\Arr; +use Illuminate\Support\Facades\Cache; use Illuminate\Support\Str; use ProcessMaker\Filters\Filter; use ProcessMaker\Managers\DataManager; @@ -344,8 +345,14 @@ public function applyProcessManager($query, $user) }); } + private function enableUserManager($user) + { + // enable user in cache + Cache::put("user_{$user->id}_manager", true); + } + /** - * Get the ID of the default saved search for tasks. + * Get the ID of the default saved search for tasks * * @return int|null */ diff --git a/routes/api.php b/routes/api.php index 62fa62cde1..053de82bcc 100644 --- a/routes/api.php +++ b/routes/api.php @@ -45,7 +45,7 @@ use ProcessMaker\Http\Controllers\Auth\TwoFactorAuthController; use ProcessMaker\Http\Controllers\TestStatusController; -Route::middleware('auth:api', 'setlocale', 'bindings', 'sanitize')->prefix('api/1.0')->name('api.')->group(function () { +Route::middleware('auth:api', 'setlocale', 'bindings', 'sanitize', 'manager')->prefix('api/1.0')->name('api.')->group(function () { // Users Route::get('users', [UserController::class, 'index'])->name('users.index'); // Permissions handled in the controller Route::get('users/{user}', [UserController::class, 'show'])->name('users.show'); // Permissions handled in the controller @@ -448,6 +448,6 @@ }); // Slack Connector Validation - Route::post('connector-slack/validate-token', [\ProcessMaker\Packages\Connectors\Slack\Controllers\SlackController::class, 'validateToken'])->name('connector-slack.validate-token'); + Route::post('connector-slack/validate-token', [ProcessMaker\Packages\Connectors\Slack\Controllers\SlackController::class, 'validateToken'])->name('connector-slack.validate-token'); }); Route::post('devlink/bundle-updated/{bundle}/{token}', [DevLinkController::class, 'bundleUpdated'])->name('devlink.bundle-updated');