Skip to content

Commit a1ca7a7

Browse files
Merge branch 'feature/FOUR-18078' into FOUR-18603
2 parents 55d771c + 81eca76 commit a1ca7a7

File tree

119 files changed

+6526
-558
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

119 files changed

+6526
-558
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,4 @@ coverage
4343
resources/lang/de
4444
resources/lang/es
4545
resources/lang/fr
46+
devhub/pm-font/dist

ProcessMaker/Console/Commands/BuildScriptExecutors.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,8 @@ public function buildExecutor()
147147
$this->info("SDK is at {$sdkDir}");
148148
}
149149

150-
$dockerfile = ScriptExecutor::initDockerfile($lang) . "\n" . $scriptExecutor->config;
150+
// add the first lines for the Docker file, then the script executor config and then the rest last part of the Dockerfile
151+
$dockerfile = $this->getDockerfileContent($scriptExecutor);
151152

152153
$this->info("Dockerfile:\n " . implode("\n ", explode("\n", $dockerfile)));
153154
file_put_contents($packagePath . '/Dockerfile.custom', $dockerfile);
@@ -166,6 +167,15 @@ public function buildExecutor()
166167
}
167168
}
168169

170+
public function getDockerfileContent(ScriptExecutor $scriptExecutor): string
171+
{
172+
$lang = $scriptExecutor->language;
173+
174+
return ScriptExecutor::initDockerfile($lang) . "\n"
175+
. $scriptExecutor->config . "\n"
176+
. ScriptExecutor::finalInstructions($lang);
177+
}
178+
169179
public function execCommand(string $command)
170180
{
171181
if ($this->userId) {

ProcessMaker/Http/Controllers/Api/PermissionController.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace ProcessMaker\Http\Controllers\Api;
44

55
use Illuminate\Http\Request;
6+
use Illuminate\Support\Facades\Cache;
67
use ProcessMaker\Events\PermissionChanged;
78
use ProcessMaker\Events\PermissionUpdated;
89
use ProcessMaker\Http\Controllers\Controller;
@@ -114,6 +115,16 @@ public function update(Request $request)
114115
//Sync the entity's permissions with the database
115116
$entity->permissions()->sync($permissions->pluck('id')->toArray());
116117

118+
// Clear user permissions cache and rebuild
119+
$this->clearAndRebuildCache($entity);
120+
117121
return response([], 204);
118122
}
123+
124+
private function clearAndRebuildCache($user)
125+
{
126+
// Rebuild and update the permissions cache
127+
$permissions = $user->permissions()->pluck('name')->toArray();
128+
Cache::put("user_{$user->id}_permissions", $permissions, 86400);
129+
}
119130
}

ProcessMaker/Http/Controllers/Api/ProcessController.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,13 @@
3939
use ProcessMaker\Package\WebEntry\Models\WebentryRoute;
4040
use ProcessMaker\Providers\WorkflowServiceProvider;
4141
use ProcessMaker\Rules\BPMNValidation;
42+
use ProcessMaker\Traits\ProjectAssetTrait;
4243
use Throwable;
4344

4445
class ProcessController extends Controller
4546
{
47+
use ProjectAssetTrait;
48+
4649
const CAROUSEL_TYPES = [
4750
'IMAGE' => 'image',
4851
'EMBED' => 'embed',
@@ -390,6 +393,7 @@ public function store(Request $request)
390393
422
391394
);
392395
}
396+
self::clearAndRebuildUserProjectAssetsCache();
393397
// Register the Event
394398
ProcessCreated::dispatch($process->refresh(), $processCreated);
395399

ProcessMaker/Http/Controllers/Api/ProcessRequestFileController.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,12 @@ public function store(Request $laravel_request, FileReceiver $receiver, ProcessR
290290
*/
291291
private function saveUploadedFile(UploadedFile $file, ProcessRequest $processRequest, Request $laravelRequest)
292292
{
293+
$errors = [];
294+
$this->validateFile($file, $errors);
295+
if (count($errors) > 0) {
296+
return abort(response($errors , 422));
297+
}
298+
293299
$parentId = $processRequest->parent_request_id;
294300
$parentRequest = $processRequest;
295301

@@ -411,4 +417,29 @@ public function destroy(Request $laravel_request, ProcessRequest $request, $file
411417

412418
return response([], 204);
413419
}
420+
421+
private function validateFile(UploadedFile $file, &$errors)
422+
{
423+
if (strtolower($file->getClientOriginalExtension() === 'pdf')) {
424+
$this->validatePDFFile($file, $errors);
425+
}
426+
427+
return $errors;
428+
}
429+
430+
private function validatePDFFile(UploadedFile $file, &$errors)
431+
{
432+
$text = $file->get();
433+
434+
$jsKeywords = ['/JavaScript', '/JS', '<< /S /JavaScript'];
435+
436+
foreach ($jsKeywords as $keyword) {
437+
if (strpos($text, $keyword) !== false) {
438+
$errors[] = __('Dangerous PDF file content');
439+
break;
440+
}
441+
}
442+
443+
return $errors;
444+
}
414445
}

ProcessMaker/Http/Controllers/Api/ScreenController.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@
1818
use ProcessMaker\Models\ScreenTemplates;
1919
use ProcessMaker\Models\ScreenType;
2020
use ProcessMaker\Query\SyntaxError;
21+
use ProcessMaker\Traits\ProjectAssetTrait;
2122

2223
class ScreenController extends Controller
2324
{
25+
use ProjectAssetTrait;
26+
2427
/**
2528
* A whitelist of attributes that should not be
2629
* sanitized by our SanitizeInput middleware.
@@ -238,6 +241,7 @@ public function store(Request $request)
238241

239242
// Creating temporary Key to store multiple id categories
240243
$newScreen['tmp_screen_category_id'] = $request->input('screen_category_id');
244+
self::clearAndRebuildUserProjectAssetsCache();
241245
// Call event to store New Screen data in LOG
242246
ScreenCreated::dispatch($newScreen->getAttributes());
243247

ProcessMaker/Http/Controllers/Api/ScriptController.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@
1818
use ProcessMaker\Models\ScriptCategory;
1919
use ProcessMaker\Models\User;
2020
use ProcessMaker\Query\SyntaxError;
21+
use ProcessMaker\Traits\ProjectAssetTrait;
2122

2223
class ScriptController extends Controller
2324
{
25+
use ProjectAssetTrait;
26+
2427
/**
2528
* A whitelist of attributes that should not be
2629
* sanitized by our SanitizeInput middleware.
@@ -346,6 +349,9 @@ public function store(Request $request)
346349
$changes = $script->getChanges();
347350
//Creating temporary Key to store multiple id categories
348351
$changes['tmp_script_category_id'] = $request->input('script_category_id');
352+
353+
self::clearAndRebuildUserProjectAssetsCache();
354+
349355
ScriptCreated::dispatch($script, $changes);
350356

351357
return new ScriptResource($script);

ProcessMaker/Http/Controllers/Api/TaskController.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
use ProcessMaker\Models\Setting;
2828
use ProcessMaker\Models\TaskDraft;
2929
use ProcessMaker\Models\User;
30+
use ProcessMaker\Models\UserResourceView;
3031
use ProcessMaker\Notifications\TaskReassignmentNotification;
3132
use ProcessMaker\Query\SyntaxError;
3233
use ProcessMaker\SanitizeHelper;
@@ -284,6 +285,11 @@ public function getScreen(Request $request, ProcessRequestToken $task, Screen $s
284285
return new ApiResource($screen->versionFor($task->processRequest));
285286
}
286287

288+
public function setViewed(Request $request, ProcessRequestToken $task)
289+
{
290+
return UserResourceView::setViewed(Auth::user(), $task);
291+
}
292+
287293
public function eligibleRollbackTask(Request $request, ProcessRequestToken $task)
288294
{
289295
$eligibleTask = RollbackProcessRequest::eligibleRollbackTask($task);

ProcessMaker/Http/Controllers/Api/V1_1/TaskController.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public function index()
6262
private function processFilters(Request $request, Builder $query)
6363
{
6464
if (request()->has('user_id')) {
65-
$query->where('user_id', request()->get('user_id'));
65+
ProcessRequestToken::scopeWhereUserAssigned($query, request()->get('user_id'));
6666
}
6767

6868
if ($request->has('process_request_id')) {

ProcessMaker/Http/Controllers/Auth/LoginController.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Illuminate\Foundation\Auth\AuthenticatesUsers;
77
use Illuminate\Http\Request;
88
use Illuminate\Support\Facades\Auth;
9+
use Illuminate\Support\Facades\Cache;
910
use Illuminate\Support\Facades\Cookie;
1011
use Illuminate\Validation\ValidationException;
1112
use ProcessMaker\Events\Logout;
@@ -241,6 +242,11 @@ public function beforeLogout(Request $request)
241242
//Clear the user permissions
242243
$request->session()->forget('permissions');
243244

245+
//Clear the user permissions
246+
$userId = Auth::user()->id;
247+
Cache::forget("user_{$userId}_permissions");
248+
Cache::forget("user_{$userId}_project_assets");
249+
244250
// Clear the user session
245251
$this->forgetUserSession();
246252

@@ -325,6 +331,10 @@ public function login(Request $request, User $user)
325331

326332
return redirect()->route('password.change');
327333
}
334+
// Cache user permissions for a day to improve performance
335+
Cache::remember("user_{$user->id}_permissions", 86400, function () use ($user) {
336+
return $user->permissions()->pluck('name')->toArray();
337+
});
328338

329339
return $this->sendLoginResponse($request);
330340
}

ProcessMaker/Http/Kernel.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ class Kernel extends HttpKernel
6565
'auth' => \ProcessMaker\Http\Middleware\ProcessMakerAuthenticate::class,
6666
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
6767
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
68-
'can' => \Illuminate\Auth\Middleware\Authorize::class,
68+
'can' => \ProcessMaker\Http\Middleware\CustomAuthorize::class,
6969
'force_change_password' => \ProcessMaker\Http\Middleware\VerifyChangePasswordNeeded::class,
7070
'guest' => \ProcessMaker\Http\Middleware\RedirectIfAuthenticated::class,
7171
'permission' => \ProcessMaker\Http\Middleware\PermissionCheck::class,
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<?php
2+
3+
namespace ProcessMaker\Http\Middleware;
4+
5+
use Closure;
6+
use Illuminate\Auth\Access\AuthorizationException;
7+
use Illuminate\Auth\Middleware\Authorize as Middleware;
8+
use Illuminate\Http\Request;
9+
use Illuminate\Support\Arr;
10+
use Illuminate\Support\Facades\Cache;
11+
use Illuminate\Support\Facades\DB;
12+
use Illuminate\Support\Facades\Log;
13+
use Illuminate\Support\Str;
14+
use ProcessMaker\Models\Group;
15+
use ProcessMaker\Models\Process;
16+
use ProcessMaker\Models\Screen;
17+
use ProcessMaker\Models\Script;
18+
use ProcessMaker\Models\User;
19+
use ProcessMaker\Traits\ProjectAssetTrait;
20+
use Symfony\Component\HttpFoundation\Response;
21+
22+
class CustomAuthorize extends Middleware
23+
{
24+
use ProjectAssetTrait;
25+
26+
public function handle($request, Closure $next, $ability, ...$models)
27+
{
28+
$modelsString = implode('-', $models);
29+
// Set the permission based on whether $modelsString is empty or not
30+
$permission = $modelsString ? $ability . '-' . $modelsString : $ability;
31+
32+
try {
33+
return parent::handle($request, $next, $ability, ...$models);
34+
} catch (AuthorizationException $e) {
35+
return $this->handleCustomLogic($request, $next, $permission, $e, ...$models);
36+
} catch (\Exception $e) {
37+
Log::error('An unexpected error occurred in CustomAuthorize middleware.', [
38+
'exception' => $e,
39+
'permission' => $permission,
40+
'models' => $models,
41+
]);
42+
43+
return $this->handleCustomLogic($request, $next, $permission, $e, ...$models);
44+
}
45+
}
46+
47+
private function handleCustomLogic($request, Closure $next, $permission, $error, ...$models)
48+
{
49+
$user = $request->user();
50+
$userPermissions = $this->getUserPermissions($user);
51+
if (!$this->hasPermission($userPermissions, $permission)) {
52+
if ($this->hasPermission($userPermissions, 'create-projects')) {
53+
// Handle middleware-based logic if no models are provided (indexes)
54+
if (empty($models) && $this->passesMiddlewareCheck($request) ||
55+
!empty($models) && $this->userHasAccessToProject($request, $user->id, ...$models)) {
56+
return $next($request);
57+
}
58+
}
59+
// Re-throw the original exception if permission is not allowed
60+
throw $error;
61+
}
62+
63+
return $next($request);
64+
}
65+
66+
private function passesMiddlewareCheck($request)
67+
{
68+
$projectAssets = ['process', 'screen', 'script', 'flow_genie', 'decision_table', 'data-source'];
69+
$middlewares = array_filter($request->route()->middleware(), function ($m) {
70+
return str_contains($m, 'can:');
71+
});
72+
73+
$middleware = array_shift($middlewares);
74+
75+
return Str::contains($middleware, $projectAssets);
76+
}
77+
78+
private function userHasAccessToProject($request, $userId, $models)
79+
{
80+
$projectAssets = self::getProjectAssetsForUser($userId);
81+
82+
// Extract the first model from the route parameters
83+
$model = $request->route()->parameter($models);
84+
85+
if ($model) {
86+
$modelClass = get_class($model);
87+
$modelId = $model->id;
88+
89+
return isset($projectAssets[$modelClass]) && in_array($modelId, $projectAssets[$modelClass]);
90+
}
91+
92+
return false;
93+
}
94+
95+
private function getUserPermissions($user)
96+
{
97+
return Cache::remember("user_{$user->id}_permissions", 86400, function () use ($user) {
98+
return $user->permissions()->pluck('name')->toArray();
99+
});
100+
}
101+
102+
private function hasPermission($userPermissions, $permission)
103+
{
104+
return in_array($permission, $userPermissions);
105+
}
106+
}

ProcessMaker/Http/Middleware/GenerateMenus.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ public static function userHasPermission($permission)
291291
return $user && $user->can($permission) && $user->hasPermission($permission);
292292
}
293293

294-
$userPermissions = $user->permissions->pluck('group')->unique()->toArray();
294+
$userPermissions = $user->permissions()->pluck('group')->unique()->toArray();
295295
$defaultPermissions = Permission::DEFAULT_PERMISSIONS;
296296
$userWithDefaultPermissions = empty(array_diff($userPermissions, $defaultPermissions));
297297

ProcessMaker/ImportExport/DependentType.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ abstract class DependentType
1010

1111
const SCREENS = 'screens';
1212

13+
const ABE_EMAIL_SCREEN = 'abe_email_screen';
14+
15+
const ABE_COMPLETED_SCREEN = 'abe_completed_screen';
16+
1317
const INTERSTITIAL_SCREEN = 'interstitial_screen';
1418

1519
const NOTIFICATION_SETTINGS = 'process_notification_settings';

0 commit comments

Comments
 (0)