Skip to content

Commit

Permalink
Merge branch 'feature/FOUR-18078' into FOUR-18603
Browse files Browse the repository at this point in the history
  • Loading branch information
devmiguelangel committed Sep 11, 2024
2 parents 55d771c + 81eca76 commit a1ca7a7
Show file tree
Hide file tree
Showing 119 changed files with 6,526 additions and 558 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,4 @@ coverage
resources/lang/de
resources/lang/es
resources/lang/fr
devhub/pm-font/dist
12 changes: 11 additions & 1 deletion ProcessMaker/Console/Commands/BuildScriptExecutors.php
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,8 @@ public function buildExecutor()
$this->info("SDK is at {$sdkDir}");
}

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

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

public function getDockerfileContent(ScriptExecutor $scriptExecutor): string
{
$lang = $scriptExecutor->language;

return ScriptExecutor::initDockerfile($lang) . "\n"
. $scriptExecutor->config . "\n"
. ScriptExecutor::finalInstructions($lang);
}

public function execCommand(string $command)
{
if ($this->userId) {
Expand Down
11 changes: 11 additions & 0 deletions ProcessMaker/Http/Controllers/Api/PermissionController.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace ProcessMaker\Http\Controllers\Api;

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

// Clear user permissions cache and rebuild
$this->clearAndRebuildCache($entity);

return response([], 204);
}

private function clearAndRebuildCache($user)
{
// Rebuild and update the permissions cache
$permissions = $user->permissions()->pluck('name')->toArray();
Cache::put("user_{$user->id}_permissions", $permissions, 86400);
}
}
4 changes: 4 additions & 0 deletions ProcessMaker/Http/Controllers/Api/ProcessController.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,13 @@
use ProcessMaker\Package\WebEntry\Models\WebentryRoute;
use ProcessMaker\Providers\WorkflowServiceProvider;
use ProcessMaker\Rules\BPMNValidation;
use ProcessMaker\Traits\ProjectAssetTrait;
use Throwable;

class ProcessController extends Controller
{
use ProjectAssetTrait;

const CAROUSEL_TYPES = [
'IMAGE' => 'image',
'EMBED' => 'embed',
Expand Down Expand Up @@ -390,6 +393,7 @@ public function store(Request $request)
422
);
}
self::clearAndRebuildUserProjectAssetsCache();
// Register the Event
ProcessCreated::dispatch($process->refresh(), $processCreated);

Expand Down
31 changes: 31 additions & 0 deletions ProcessMaker/Http/Controllers/Api/ProcessRequestFileController.php
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,12 @@ public function store(Request $laravel_request, FileReceiver $receiver, ProcessR
*/
private function saveUploadedFile(UploadedFile $file, ProcessRequest $processRequest, Request $laravelRequest)
{
$errors = [];
$this->validateFile($file, $errors);
if (count($errors) > 0) {
return abort(response($errors , 422));
}

$parentId = $processRequest->parent_request_id;
$parentRequest = $processRequest;

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

return response([], 204);
}

private function validateFile(UploadedFile $file, &$errors)
{
if (strtolower($file->getClientOriginalExtension() === 'pdf')) {
$this->validatePDFFile($file, $errors);
}

return $errors;
}

private function validatePDFFile(UploadedFile $file, &$errors)
{
$text = $file->get();

$jsKeywords = ['/JavaScript', '/JS', '<< /S /JavaScript'];

foreach ($jsKeywords as $keyword) {
if (strpos($text, $keyword) !== false) {
$errors[] = __('Dangerous PDF file content');
break;
}
}

return $errors;
}
}
4 changes: 4 additions & 0 deletions ProcessMaker/Http/Controllers/Api/ScreenController.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@
use ProcessMaker\Models\ScreenTemplates;
use ProcessMaker\Models\ScreenType;
use ProcessMaker\Query\SyntaxError;
use ProcessMaker\Traits\ProjectAssetTrait;

class ScreenController extends Controller
{
use ProjectAssetTrait;

/**
* A whitelist of attributes that should not be
* sanitized by our SanitizeInput middleware.
Expand Down Expand Up @@ -238,6 +241,7 @@ public function store(Request $request)

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

Expand Down
6 changes: 6 additions & 0 deletions ProcessMaker/Http/Controllers/Api/ScriptController.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@
use ProcessMaker\Models\ScriptCategory;
use ProcessMaker\Models\User;
use ProcessMaker\Query\SyntaxError;
use ProcessMaker\Traits\ProjectAssetTrait;

class ScriptController extends Controller
{
use ProjectAssetTrait;

/**
* A whitelist of attributes that should not be
* sanitized by our SanitizeInput middleware.
Expand Down Expand Up @@ -346,6 +349,9 @@ public function store(Request $request)
$changes = $script->getChanges();
//Creating temporary Key to store multiple id categories
$changes['tmp_script_category_id'] = $request->input('script_category_id');

self::clearAndRebuildUserProjectAssetsCache();

ScriptCreated::dispatch($script, $changes);

return new ScriptResource($script);
Expand Down
6 changes: 6 additions & 0 deletions ProcessMaker/Http/Controllers/Api/TaskController.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
use ProcessMaker\Models\Setting;
use ProcessMaker\Models\TaskDraft;
use ProcessMaker\Models\User;
use ProcessMaker\Models\UserResourceView;
use ProcessMaker\Notifications\TaskReassignmentNotification;
use ProcessMaker\Query\SyntaxError;
use ProcessMaker\SanitizeHelper;
Expand Down Expand Up @@ -284,6 +285,11 @@ public function getScreen(Request $request, ProcessRequestToken $task, Screen $s
return new ApiResource($screen->versionFor($task->processRequest));
}

public function setViewed(Request $request, ProcessRequestToken $task)
{
return UserResourceView::setViewed(Auth::user(), $task);
}

public function eligibleRollbackTask(Request $request, ProcessRequestToken $task)
{
$eligibleTask = RollbackProcessRequest::eligibleRollbackTask($task);
Expand Down
2 changes: 1 addition & 1 deletion ProcessMaker/Http/Controllers/Api/V1_1/TaskController.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public function index()
private function processFilters(Request $request, Builder $query)
{
if (request()->has('user_id')) {
$query->where('user_id', request()->get('user_id'));
ProcessRequestToken::scopeWhereUserAssigned($query, request()->get('user_id'));
}

if ($request->has('process_request_id')) {
Expand Down
10 changes: 10 additions & 0 deletions ProcessMaker/Http/Controllers/Auth/LoginController.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Cookie;
use Illuminate\Validation\ValidationException;
use ProcessMaker\Events\Logout;
Expand Down Expand Up @@ -241,6 +242,11 @@ public function beforeLogout(Request $request)
//Clear the user permissions
$request->session()->forget('permissions');

//Clear the user permissions
$userId = Auth::user()->id;
Cache::forget("user_{$userId}_permissions");
Cache::forget("user_{$userId}_project_assets");

// Clear the user session
$this->forgetUserSession();

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

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

return $this->sendLoginResponse($request);
}
Expand Down
2 changes: 1 addition & 1 deletion ProcessMaker/Http/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ class Kernel extends HttpKernel
'auth' => \ProcessMaker\Http\Middleware\ProcessMakerAuthenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'can' => \ProcessMaker\Http\Middleware\CustomAuthorize::class,
'force_change_password' => \ProcessMaker\Http\Middleware\VerifyChangePasswordNeeded::class,
'guest' => \ProcessMaker\Http\Middleware\RedirectIfAuthenticated::class,
'permission' => \ProcessMaker\Http\Middleware\PermissionCheck::class,
Expand Down
106 changes: 106 additions & 0 deletions ProcessMaker/Http/Middleware/CustomAuthorize.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<?php

namespace ProcessMaker\Http\Middleware;

use Closure;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Auth\Middleware\Authorize as Middleware;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use ProcessMaker\Models\Group;
use ProcessMaker\Models\Process;
use ProcessMaker\Models\Screen;
use ProcessMaker\Models\Script;
use ProcessMaker\Models\User;
use ProcessMaker\Traits\ProjectAssetTrait;
use Symfony\Component\HttpFoundation\Response;

class CustomAuthorize extends Middleware
{
use ProjectAssetTrait;

public function handle($request, Closure $next, $ability, ...$models)
{
$modelsString = implode('-', $models);
// Set the permission based on whether $modelsString is empty or not
$permission = $modelsString ? $ability . '-' . $modelsString : $ability;

try {
return parent::handle($request, $next, $ability, ...$models);
} catch (AuthorizationException $e) {
return $this->handleCustomLogic($request, $next, $permission, $e, ...$models);
} catch (\Exception $e) {
Log::error('An unexpected error occurred in CustomAuthorize middleware.', [
'exception' => $e,
'permission' => $permission,
'models' => $models,
]);

return $this->handleCustomLogic($request, $next, $permission, $e, ...$models);
}
}

private function handleCustomLogic($request, Closure $next, $permission, $error, ...$models)
{
$user = $request->user();
$userPermissions = $this->getUserPermissions($user);
if (!$this->hasPermission($userPermissions, $permission)) {
if ($this->hasPermission($userPermissions, 'create-projects')) {
// Handle middleware-based logic if no models are provided (indexes)
if (empty($models) && $this->passesMiddlewareCheck($request) ||
!empty($models) && $this->userHasAccessToProject($request, $user->id, ...$models)) {
return $next($request);
}
}
// Re-throw the original exception if permission is not allowed
throw $error;
}

return $next($request);
}

private function passesMiddlewareCheck($request)
{
$projectAssets = ['process', 'screen', 'script', 'flow_genie', 'decision_table', 'data-source'];
$middlewares = array_filter($request->route()->middleware(), function ($m) {
return str_contains($m, 'can:');
});

$middleware = array_shift($middlewares);

return Str::contains($middleware, $projectAssets);
}

private function userHasAccessToProject($request, $userId, $models)
{
$projectAssets = self::getProjectAssetsForUser($userId);

// Extract the first model from the route parameters
$model = $request->route()->parameter($models);

if ($model) {
$modelClass = get_class($model);
$modelId = $model->id;

return isset($projectAssets[$modelClass]) && in_array($modelId, $projectAssets[$modelClass]);
}

return false;
}

private function getUserPermissions($user)
{
return Cache::remember("user_{$user->id}_permissions", 86400, function () use ($user) {
return $user->permissions()->pluck('name')->toArray();
});
}

private function hasPermission($userPermissions, $permission)
{
return in_array($permission, $userPermissions);
}
}
2 changes: 1 addition & 1 deletion ProcessMaker/Http/Middleware/GenerateMenus.php
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ public static function userHasPermission($permission)
return $user && $user->can($permission) && $user->hasPermission($permission);
}

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

Expand Down
4 changes: 4 additions & 0 deletions ProcessMaker/ImportExport/DependentType.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ abstract class DependentType

const SCREENS = 'screens';

const ABE_EMAIL_SCREEN = 'abe_email_screen';

const ABE_COMPLETED_SCREEN = 'abe_completed_screen';

const INTERSTITIAL_SCREEN = 'interstitial_screen';

const NOTIFICATION_SETTINGS = 'process_notification_settings';
Expand Down
Loading

0 comments on commit a1ca7a7

Please sign in to comment.