Skip to content

Commit b3131cf

Browse files
author
Miguel Angel
authored
Merge branch 'feature/FOUR-11378' into FOUR-13092
2 parents 18f34fd + 8664aed commit b3131cf

69 files changed

Lines changed: 4505 additions & 10325 deletions

File tree

Some content is hidden

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

‎ProcessMaker/Http/Controllers/Api/ProcessCategoryController.php‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ public function index(Request $request)
8181
$query->where('status', 'like', $request->input('status'));
8282
}
8383
$query->orderBy(
84-
$request->input('order_by', 'name'),
84+
$request->input('order_by', 'id'),
8585
$request->input('order_direction', 'asc')
8686
);
8787
$response = $query->paginate($request->input('per_page', 10));

‎ProcessMaker/Http/Controllers/Auth/LoginController.php‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,8 @@ public function beforeLogout(Request $request)
245245

246246
// Always destroy 2fa flag
247247
session()->remove(TwoFactorAuthController::TFA_VALIDATED);
248+
session()->remove(TwoFactorAuthController::TFA_MESSAGE);
249+
session()->remove(TwoFactorAuthController::TFA_ERROR);
248250
}
249251

250252
return $this->logout($request);

‎ProcessMaker/Http/Controllers/Auth/TwoFactorAuthController.php‎

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public function displayTwoFactorAuthForm(Request $request)
3232
$user = $request->user();
3333

3434
// If not user not authenticated, redirect to login page
35-
if (empty($user)) {
35+
if (empty($user) || !config('password-policies.2fa_enabled', false)) {
3636
return redirect()->route('login');
3737
}
3838

@@ -46,7 +46,10 @@ public function displayTwoFactorAuthForm(Request $request)
4646
}
4747

4848
// Set informative message
49-
session()->put(self::TFA_MESSAGE, _('Enter the security code we sent you.'));
49+
$methodsNames = $this->friendlyMethodsNames();
50+
$message = __('Enter the security code from :methods. If incorrect, please retry with the latest code provided.',
51+
['methods' => $methodsNames]);
52+
session()->put(self::TFA_MESSAGE, $message);
5053
} catch (Exception $error) {
5154
session()->put(self::TFA_ERROR, $error->getMessage());
5255
}
@@ -106,7 +109,11 @@ public function sendCode(Request $request)
106109
$user = $request->user();
107110

108111
// Send the code
109-
$this->twoFactorAuthentication->sendCode($user);
112+
try {
113+
$this->twoFactorAuthentication->sendCode($user);
114+
} catch (Exception $error) {
115+
session()->put(self::TFA_ERROR, $error->getMessage());
116+
}
110117

111118
// Return to 2fa page
112119
return redirect()->route('2fa');
@@ -200,4 +207,32 @@ private function testSmsServer()
200207
}
201208
return true;
202209
}
210+
211+
private function friendlyMethodsNames()
212+
{
213+
// Define the friendly names for each method
214+
$friendlyNames = [
215+
TwoFactorAuthentication::EMAIL => __('Email'),
216+
TwoFactorAuthentication::SMS => __('SMS'),
217+
TwoFactorAuthentication::AUTH_APP => __('Google Authenticator'),
218+
];
219+
220+
// Get enabled methods to send the code
221+
$enabledMethods = config('password-policies.2fa_method', []);
222+
223+
// Return the friendly names for enabled methods
224+
$methods = array_map(function($method) use ($friendlyNames) {
225+
return $friendlyNames[$method] ?? $method;
226+
}, $enabledMethods);
227+
228+
// Build final string
229+
if (count($methods) > 1) {
230+
$lastMethod = array_pop($methods);
231+
$methods = implode(', ', $methods) . ' ' . __('or') . ' ' . $lastMethod;
232+
} else {
233+
$methods = $methods[0];
234+
}
235+
236+
return $methods;
237+
}
203238
}

‎ProcessMaker/Http/Controllers/Process/ModelerController.php‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ public function show(ModelerManager $manager, Process $process, Request $request
5252
asort($screenTypes);
5353
$countScreenCategories = ScreenCategory::where(['status' => 'ACTIVE', 'is_system' => false])->count();
5454
$isProjectsInstalled = PackageHelper::isPackageInstalled(PackageHelper::PM_PACKAGE_PROJECTS);
55+
$isPackageAiInstalled = hasPackage('package-ai');
5556

5657
// For create script modal in modeler
5758
$scriptExecutors = ScriptExecutor::list();
@@ -77,6 +78,7 @@ public function show(ModelerManager $manager, Process $process, Request $request
7778
'countScreenCategories' => $countScreenCategories,
7879
'countScriptCategories' => $countScriptCategories,
7980
'isProjectsInstalled' => $isProjectsInstalled,
81+
'isPackageAiInstalled' => $isPackageAiInstalled,
8082
'isAiGenerated' => request()->query('ai'),
8183
]);
8284
}

‎ProcessMaker/Http/Middleware/SessionControlBlock.php‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public function handle(Request $request, Closure $next): Response
3232
return $this->redirectToLogin();
3333
}
3434
// Check if the user's session should be blocked based on device restriction
35-
if ($this->blockedByDevice($user)) {
35+
if ($this->blockedByDevice($user, $request)) {
3636
return $this->redirectToLogin();
3737
}
3838
}
@@ -56,9 +56,10 @@ private function blockedByIp(User $user, Request $request): bool
5656
return Setting::configByKey(self::IP_RESTRICTION_KEY) === '1' && $this->blockSessionByIp($user, $request);
5757
}
5858

59-
private function blockedByDevice(User $user): bool
59+
private function blockedByDevice(User $user, Request $request): bool
6060
{
61-
return Setting::configByKey(self::DEVICE_RESTRICTION_KEY) === '1' && $this->blockSessionByDevice($user);
61+
return Setting::configByKey(self::DEVICE_RESTRICTION_KEY) === '1'
62+
&& $this->blockSessionByDevice($user, $request);
6263
}
6364

6465
/**
@@ -83,22 +84,25 @@ private function blockSessionByIp(User $user, Request $request): bool
8384
* Checks if the user's current session device matches the device used in the request
8485
*
8586
* @param User user
87+
* @param Request request
8688
*
8789
* @return bool
8890
*/
89-
private function blockSessionByDevice(User $user): bool
91+
private function blockSessionByDevice(User $user, Request $request): bool
9092
{
9193
$agent = new Agent();
9294
// Get the device details from the request
9395
$agentDevice = $agent->device() ? $agent->device() : 'Unknown';
9496
$requestDevice = $this->formatDeviceInfo($agentDevice, $agent->deviceType(), $agent->platform());
97+
// Get the user's current IP address
98+
$ip = $request->getClientIp() ?? $request->ip();
9599
// Get the user's most recent session
96100
$session = $user->sessions->sortByDesc('created_at')->first();
97101
$sessionDevice = $this->formatDeviceInfo(
98102
$session->device_name, $session->device_type, $session->device_platform
99103
);
100104

101-
return $requestDevice !== $sessionDevice;
105+
return $requestDevice !== $sessionDevice || $session->ip_address !== $ip;
102106
}
103107

104108
private function formatDeviceInfo(string $deviceName, string $deviceType, string $devicePlatform): string

‎ProcessMaker/Http/Middleware/SessionControlKill.php‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,13 @@ public function handle(Request $request, Closure $next): Response
4848

4949
private function getActiveSession(User $user, string $userSession): ?UserSession
5050
{
51-
return $user->sessions()->where('is_active', true)->where('token', $userSession)->first();
51+
return $user->sessions()
52+
->where([
53+
['is_active', true],
54+
['token', $userSession],
55+
['expired_date', '!=', null]
56+
])
57+
->first();
5258
}
5359

5460
/**

‎ProcessMaker/Jobs/SyncWizardTemplates.php‎

Lines changed: 9 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public function handle()
6565
'is_system' => 1,
6666
])->getKey();
6767

68-
// Fetch the default template list from Github
68+
// Fetch the guided template list from Github
6969
$response = Http::get($url);
7070

7171
// Check if the request was successful
@@ -82,8 +82,12 @@ public function handle()
8282
continue;
8383
}
8484

85-
foreach ($wizardTemplates as $template) {
86-
$this->importTemplate($template, $config, $wizardTemplateCategoryId);
85+
try {
86+
foreach ($wizardTemplates as $template) {
87+
$this->importTemplate($template, $config, $wizardTemplateCategoryId);
88+
}
89+
} catch (Exception $e) {
90+
Log::error("Error Importing Guided Templates: {$e->getMessage()}");
8791
}
8892
}
8993
} catch (Exception $e) {
@@ -101,22 +105,19 @@ public function handle()
101105
*/
102106
private function importTemplate($template, $config, $wizardTemplateCategoryId)
103107
{
104-
// Configure URLs for the helper process, process template, and collection
108+
// Configure URLs for the helper process, process template
105109
$helperProcessUrl = $this->buildTemplateUrl($config, $template['helper_process']);
106110
$processTemplateUrl = $this->buildTemplateUrl($config, $template['template_process']);
107-
$configCollectionUrl = $this->buildTemplateUrl($config, $template['config_collection']);
108111

109-
// Get manifests of the exported helper process, process template, and collection
112+
// Get manifests of the exported helper process, process template
110113
$helperProcessPayload = $this->fetchPayload($helperProcessUrl);
111114
$templateProcessPayload = $this->fetchPayload($processTemplateUrl);
112-
$configCollectionPayload = $this->fetchPayload($configCollectionUrl);
113115

114116
// Update process categories for the helper process and process template
115117
$this->updateProcessCategories($helperProcessPayload, $templateProcessPayload, $wizardTemplateCategoryId);
116118

117119
// Import the helper process and get the new ID
118120
$newHelperProcessId = $this->importProcess($helperProcessPayload, 'WIZARD_HELPER_PROCESS');
119-
120121
// Import the process template and get the new ID
121122
$newProcessTemplateId = $this->importProcess($templateProcessPayload, 'WIZARD_PROCESS_TEMPLATE');
122123

@@ -129,22 +130,6 @@ private function importTemplate($template, $config, $wizardTemplateCategoryId)
129130
// Import template assets and associate with the media collection
130131
$this->importTemplateAssets($template, $config, $mediaCollectionName, $wizardTemplate);
131132

132-
// Import the config collection and update associations
133-
$this->importConfigCollection($configCollectionUrl, $configCollectionPayload, $wizardTemplate);
134-
// Update the wizard template with the config collection ID
135-
136-
// TODO: An issue occurs at this point as there is currently no direct way to update an existing collection when importing a collection.
137-
// When importing a collection from the wizard template, a new collection is created each time.
138-
// As a temporary solution, we could check if the wizard template already has a config_collection_id associated.
139-
// If so, we need to delete the existing collection along with all its assets (screens, process signals, etc.).
140-
// Subsequently, we import a new collection and associate it with the wizard template.
141-
// This approach prevents the database from being cluttered with orphaned collections and collection assets.
142-
// Considerations:
143-
// - Could this cause issues with already configured wizard templates?
144-
// - Could this cause issues with the helper process/process template if they reference a specific collection?
145-
// - Does deleting a collection automatically remove all associated assets?
146-
// - How can we implement an import method similar to processes for collections, and what is the expected duration of implementation?
147-
$wizardTemplate->config_collection_id = $this->newConfigCollectionId;
148133
$wizardTemplate->media_collection = $mediaCollectionName;
149134
$wizardTemplate->save();
150135
}
@@ -211,9 +196,6 @@ private function importProcess($payload, $assetType)
211196
private function updateOrCreateWizardTemplate($template, $newHelperProcessId, $newProcessTemplateId)
212197
{
213198
// Update or create the wizard template in the database
214-
// TODO: Currently, the index.json in the GitHub repository contains identical wizard template test data for each category,
215-
// resulting in a single record creation that gets updated for each template within the category.
216-
// This behavior will be corrected once we ensure that each category has only one instance of the test data.
217199

218200
return WizardTemplate::updateOrCreate([
219201
'name' => $template['template_details']['card-title'],
@@ -222,7 +204,6 @@ private function updateOrCreateWizardTemplate($template, $newHelperProcessId, $n
222204
'helper_process_id' => $newHelperProcessId,
223205
'process_template_id' => $newProcessTemplateId,
224206
'media_collection' => '',
225-
'config_collection_id' => null,
226207
'template_details' => json_encode($template['template_details']),
227208
]);
228209
}
@@ -258,116 +239,4 @@ private function importMedia($assetUrl, $customProperty, $mediaCollectionName, $
258239
// Import a media asset and associate it with the media collection
259240
$wizardTemplate->addMediaFromUrl($assetUrl)->withCustomProperties(['media_type' => $customProperty])->toMediaCollection($mediaCollectionName);
260241
}
261-
262-
private function importConfigCollection($configCollectionUrl, $configCollectionPayload, $wizardTemplate)
263-
{
264-
// Import the config collection and update associations
265-
if (class_exists('ProcessMaker\Plugins\Collections\Jobs\ImportCollection')) {
266-
// Obtain the user ID for the admin user
267-
$adminUserId = User::where('username', 'admin')->first()->value('id');
268-
// Create a temporary file to store the JSON payload
269-
$tempFileName = $this->createTempFile($configCollectionUrl, $configCollectionPayload);
270-
271-
// Store the temporary file in the storage directory
272-
$pathInStorage = $this->storeInStorage($tempFileName);
273-
274-
// Import the config collection using the Collection package
275-
$importedData = $this->importCollection($pathInStorage, $adminUserId);
276-
277-
// Process the imported data and update asset types and associations
278-
$this->processImportedData($importedData);
279-
} else {
280-
Log::debug('Error Syncing Guided Templates: The Collection package is not installed. Please install the Collection package to enable full functionality for Guided Templates.');
281-
}
282-
}
283-
284-
/**
285-
* Create a temporary file and store the JSON payload.
286-
*
287-
* @param string $configCollectionUrl
288-
* @param array $configCollectionPayload
289-
* @return string
290-
*/
291-
private function createTempFile($configCollectionUrl, $configCollectionPayload)
292-
{
293-
$info = pathinfo($configCollectionUrl);
294-
$tempFileName = '/tmp/' . $info['basename'];
295-
file_put_contents($tempFileName, json_encode($configCollectionPayload));
296-
297-
return $tempFileName;
298-
}
299-
300-
/**
301-
* Store the temporary file in the storage directory.
302-
*
303-
* @param string $tempFileName
304-
* @return string
305-
*/
306-
private function storeInStorage($tempFileName)
307-
{
308-
return Storage::putFile('imports', $tempFileName);
309-
}
310-
311-
/**
312-
* Import the config collection using the Collection package.
313-
*
314-
* @param string $pathInStorage
315-
* @param int $adminUserId
316-
* @return array
317-
*/
318-
private function importCollection($pathInStorage, $adminUserId)
319-
{
320-
$collectionImporterClass = 'ProcessMaker\Plugins\Collections\Jobs\ImportCollection';
321-
322-
return (new $collectionImporterClass($pathInStorage, $adminUserId))->handle();
323-
}
324-
325-
/**
326-
* Process the imported data and update asset types and associations.
327-
*
328-
* @param array $importedData
329-
* @return void
330-
*/
331-
private function processImportedData($importedData)
332-
{
333-
foreach ($importedData as $element => $value) {
334-
if (isset($value['uuids'])) {
335-
foreach ($value['uuids'] as $uuid) {
336-
if ($element === 'collections') {
337-
$this->updateCollectionAssetType($uuid);
338-
} elseif ($element === 'screens') {
339-
$this->updateScreenAssetType($uuid);
340-
}
341-
}
342-
}
343-
}
344-
}
345-
346-
/**
347-
* Update the asset type on the imported collection.
348-
*
349-
* @param string $uuid
350-
* @return void
351-
*/
352-
private function updateCollectionAssetType($uuid)
353-
{
354-
$collectionClass = 'ProcessMaker\Plugins\Collections\Models\Collection';
355-
$importedCollection = (new $collectionClass)->where('uuid', $uuid)->first();
356-
$importedCollection->asset_type = 'WIZARD_CONFIG_COLLECTION';
357-
$importedCollection->save();
358-
$this->newConfigCollectionId = $importedCollection->id;
359-
}
360-
361-
/**
362-
* Update the asset type for imported collection screens.
363-
*
364-
* @param string $uuid
365-
* @return void
366-
*/
367-
private function updateScreenAssetType($uuid)
368-
{
369-
$importedCollectionScreen = Screen::where('uuid', $uuid)->first();
370-
$importedCollectionScreen->asset_type = 'WIZARD_CONFIG_COLLECTION_SCREEN';
371-
$importedCollectionScreen->save();
372-
}
373242
}

0 commit comments

Comments
 (0)